| /* SPDX-License-Identifier: LGPL-2.1-only */ |
| /* |
| * lib/route/mdb.c Multicast Database |
| */ |
| |
| #include "nl-default.h" |
| |
| #include <linux/if_bridge.h> |
| |
| #include <netlink/netlink.h> |
| #include <netlink/route/mdb.h> |
| #include <netlink/route/nexthop.h> |
| #include <netlink/utils.h> |
| #include <netlink/route/rtnl.h> |
| |
| #include "nl-route.h" |
| #include "nl-aux-route/nl-route.h" |
| #include "nl-priv-dynamic-core/object-api.h" |
| #include "nl-priv-dynamic-core/cache-api.h" |
| |
| /** @cond SKIP */ |
| #define MDB_ATTR_IFINDEX 0x000001 |
| #define MDB_ATTR_ENTRIES 0x000002 |
| |
| struct rtnl_mdb { |
| NLHDR_COMMON |
| uint32_t ifindex; |
| |
| struct nl_list_head mdb_entry_list; |
| }; |
| |
| struct rtnl_mdb_entry { |
| struct nl_list_head mdb_list; |
| struct nl_addr *addr; |
| uint32_t ifindex; |
| uint16_t vid; |
| uint16_t proto; |
| uint8_t state; |
| }; |
| |
| static struct rtnl_mdb_entry *rtnl_mdb_entry_alloc(void); |
| static void rtnl_mdb_entry_free(struct rtnl_mdb_entry *mdb_entry); |
| |
| static struct nl_cache_ops rtnl_mdb_ops; |
| static struct nl_object_ops mdb_obj_ops; |
| /** @endcond */ |
| |
| static void mdb_constructor(struct nl_object *obj) |
| { |
| struct rtnl_mdb *_mdb = (struct rtnl_mdb *) obj; |
| |
| nl_init_list_head(&_mdb->mdb_entry_list); |
| } |
| |
| static void mdb_free_data(struct nl_object *obj) |
| { |
| struct rtnl_mdb *mdb = (struct rtnl_mdb *)obj; |
| struct rtnl_mdb_entry *mdb_entry; |
| struct rtnl_mdb_entry *mdb_entry_safe; |
| |
| nl_list_for_each_entry_safe(mdb_entry, mdb_entry_safe, |
| &mdb->mdb_entry_list, mdb_list) |
| rtnl_mdb_entry_free(mdb_entry); |
| } |
| |
| static int mdb_entry_equal(struct rtnl_mdb_entry *a, struct rtnl_mdb_entry *b) |
| { |
| return a->ifindex == b->ifindex |
| && a->vid == b->vid |
| && a->proto == b->proto |
| && a->state == b->state |
| && nl_addr_cmp(a->addr, b->addr) == 0; |
| } |
| |
| static uint64_t mdb_compare(struct nl_object *_a, struct nl_object *_b, |
| uint64_t attrs, int flags) |
| { |
| struct rtnl_mdb *a = (struct rtnl_mdb *) _a; |
| struct rtnl_mdb *b = (struct rtnl_mdb *) _b; |
| struct rtnl_mdb_entry *a_entry, *b_entry; |
| uint64_t diff = 0; |
| |
| #define _DIFF(ATTR, EXPR) ATTR_DIFF(attrs, ATTR, a, b, EXPR) |
| diff |= _DIFF(MDB_ATTR_IFINDEX, a->ifindex != b->ifindex); |
| #undef _DIFF |
| |
| a_entry = nl_list_entry(a->mdb_entry_list.next, struct rtnl_mdb_entry, mdb_list); |
| b_entry = nl_list_entry(b->mdb_entry_list.next, struct rtnl_mdb_entry, mdb_list); |
| while (1) { |
| if ( &a_entry->mdb_list == &a->mdb_entry_list |
| || &b_entry->mdb_list == &b->mdb_entry_list) { |
| if ( &a_entry->mdb_list != &a->mdb_entry_list |
| || &b_entry->mdb_list != &b->mdb_entry_list) |
| diff |= MDB_ATTR_ENTRIES; |
| break; |
| } |
| if (!mdb_entry_equal(a_entry, b_entry)) { |
| diff |= MDB_ATTR_ENTRIES; |
| break; |
| } |
| a_entry = nl_list_entry(a_entry->mdb_list.next, struct rtnl_mdb_entry, mdb_list); |
| b_entry = nl_list_entry(b_entry->mdb_list.next, struct rtnl_mdb_entry, mdb_list); |
| } |
| |
| return diff; |
| } |
| |
| static struct rtnl_mdb_entry *mdb_entry_clone(struct rtnl_mdb_entry *src) |
| { |
| struct rtnl_mdb_entry *dst = rtnl_mdb_entry_alloc(); |
| if (!dst) |
| return NULL; |
| |
| dst->ifindex = src->ifindex; |
| dst->state = src->state; |
| dst->vid = src->vid; |
| dst->proto = src->proto; |
| |
| dst->addr = nl_addr_clone(src->addr); |
| if (dst->addr == NULL) { |
| free(dst); |
| return NULL; |
| } |
| |
| return dst; |
| } |
| |
| static int mdb_clone(struct nl_object *_dst, struct nl_object *_src) |
| { |
| struct rtnl_mdb *dst = nl_object_priv(_dst); |
| struct rtnl_mdb *src = nl_object_priv(_src); |
| struct rtnl_mdb_entry *entry; |
| |
| nl_init_list_head(&dst->mdb_entry_list); |
| |
| nl_list_for_each_entry(entry, &src->mdb_entry_list, mdb_list) { |
| struct rtnl_mdb_entry *copy = mdb_entry_clone(entry); |
| |
| if (!copy) |
| return -NLE_NOMEM; |
| |
| rtnl_mdb_add_entry(dst, copy); |
| } |
| |
| return 0; |
| } |
| |
| static int mdb_update(struct nl_object *old_obj, struct nl_object *new_obj) |
| { |
| struct rtnl_mdb *old = (struct rtnl_mdb *) old_obj; |
| struct rtnl_mdb *new = (struct rtnl_mdb *) new_obj; |
| struct rtnl_mdb_entry *entry, *old_entry; |
| int action = new_obj->ce_msgtype; |
| |
| if (new->ifindex != old->ifindex) |
| return -NLE_OPNOTSUPP; |
| |
| switch (action) { |
| case RTM_NEWMDB: |
| nl_list_for_each_entry(entry, &new->mdb_entry_list, mdb_list) { |
| struct rtnl_mdb_entry *copy = mdb_entry_clone(entry); |
| |
| if (!copy) |
| return -NLE_NOMEM; |
| |
| rtnl_mdb_add_entry(old, copy); |
| } |
| break; |
| case RTM_DELMDB: |
| entry = nl_list_first_entry(&new->mdb_entry_list, |
| struct rtnl_mdb_entry, |
| mdb_list); |
| nl_list_for_each_entry(old_entry, &old->mdb_entry_list, mdb_list) { |
| if ( old_entry->ifindex == entry->ifindex |
| && !nl_addr_cmp(old_entry->addr, entry->addr)) { |
| nl_list_del(&old_entry->mdb_list); |
| break; |
| } |
| } |
| break; |
| } |
| |
| return NLE_SUCCESS; |
| } |
| |
| static struct nla_policy mdb_policy[MDBA_MAX + 1] = { |
| [MDBA_MDB] = {.type = NLA_NESTED}, |
| }; |
| |
| static struct nla_policy mdb_db_policy[MDBA_MDB_MAX + 1] = { |
| [MDBA_MDB_ENTRY] = {.type = NLA_NESTED}, |
| }; |
| |
| static int mdb_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who, |
| struct nlmsghdr *nlh, struct nl_parser_param *pp) |
| { |
| int err = 0; |
| int rem = 0; |
| struct nlattr *tb[MDBA_MAX + 1]; |
| struct br_port_msg *port; |
| struct nlattr *nla; |
| struct br_mdb_entry *e; |
| _nl_auto_rtnl_mdb struct rtnl_mdb *mdb = rtnl_mdb_alloc(); |
| |
| if (!mdb) |
| return -NLE_NOMEM; |
| |
| err = nlmsg_parse(nlh, sizeof(struct br_port_msg), tb, MDBA_MAX, |
| mdb_policy); |
| if (err < 0) |
| return err; |
| |
| mdb->ce_msgtype = nlh->nlmsg_type; |
| |
| port = nlmsg_data(nlh); |
| mdb->ifindex = port->ifindex; |
| mdb->ce_mask |= MDB_ATTR_IFINDEX; |
| |
| if (tb[MDBA_MDB]) { |
| struct nlattr *db_attr[MDBA_MDB_MAX+1]; |
| |
| err = nla_parse_nested(db_attr, MDBA_MDB_MAX, tb[MDBA_MDB], |
| mdb_db_policy); |
| if (err < 0) |
| return err; |
| rem = nla_len(tb[MDBA_MDB]); |
| |
| for (nla = nla_data(tb[MDBA_MDB]); nla_ok(nla, rem); |
| nla = nla_next(nla, &rem)) { |
| int rm = nla_len(nla); |
| struct nlattr *nla2; |
| |
| for (nla2 = nla_data(nla); nla_ok(nla2, rm); |
| nla2 = nla_next(nla2, &rm)) { |
| _nl_auto_nl_addr struct nl_addr *addr = NULL; |
| struct rtnl_mdb_entry *entry; |
| uint16_t proto; |
| |
| e = nla_data(nla2); |
| |
| proto = ntohs(e->addr.proto); |
| |
| if (proto == ETH_P_IP) { |
| addr = nl_addr_build( |
| AF_INET, &e->addr.u.ip4, |
| sizeof(e->addr.u.ip4)); |
| } else if (proto == ETH_P_IPV6) { |
| addr = nl_addr_build( |
| AF_INET6, &e->addr.u.ip6, |
| sizeof(e->addr.u.ip6)); |
| } else { |
| addr = nl_addr_build( |
| AF_LLC, e->addr.u.mac_addr, |
| sizeof(e->addr.u.mac_addr)); |
| } |
| if (!addr) |
| return -NLE_NOMEM; |
| |
| entry = rtnl_mdb_entry_alloc(); |
| if (!entry) |
| return -NLE_NOMEM; |
| |
| mdb->ce_mask |= MDB_ATTR_ENTRIES; |
| |
| entry->ifindex = e->ifindex; |
| entry->vid = e->vid; |
| entry->state = e->state; |
| entry->proto = ntohs(e->addr.proto); |
| entry->addr = _nl_steal_pointer(&addr); |
| rtnl_mdb_add_entry(mdb, entry); |
| } |
| } |
| } |
| |
| return pp->pp_cb((struct nl_object *) mdb, pp); |
| } |
| |
| static int mdb_request_update(struct nl_cache *cache, struct nl_sock *sk) |
| { |
| return nl_rtgen_request(sk, RTM_GETMDB, AF_BRIDGE, NLM_F_DUMP); |
| } |
| |
| static void mdb_entry_dump_line(struct rtnl_mdb_entry *entry, |
| struct nl_dump_params *p) |
| { |
| char buf[INET6_ADDRSTRLEN]; |
| |
| nl_dump(p, "port %d ", entry->ifindex); |
| nl_dump(p, "vid %d ", entry->vid); |
| nl_dump(p, "proto 0x%04x ", entry->proto); |
| nl_dump(p, "address %s\n", nl_addr2str(entry->addr, buf, sizeof(buf))); |
| } |
| |
| static void mdb_dump_line(struct nl_object *obj, struct nl_dump_params *p) |
| { |
| struct rtnl_mdb *mdb = (struct rtnl_mdb *) obj; |
| struct rtnl_mdb_entry *_mdb; |
| |
| nl_dump(p, "dev %d \n", mdb->ifindex); |
| |
| nl_list_for_each_entry(_mdb, &mdb->mdb_entry_list, mdb_list) { |
| p->dp_ivar = NH_DUMP_FROM_ONELINE; |
| mdb_entry_dump_line(_mdb, p); |
| } |
| } |
| |
| static void mdb_dump_details(struct nl_object *obj, struct nl_dump_params *p) |
| { |
| mdb_dump_line(obj, p); |
| } |
| |
| static void mdb_dump_stats(struct nl_object *obj, struct nl_dump_params *p) |
| { |
| mdb_dump_details(obj, p); |
| } |
| |
| void rtnl_mdb_put(struct rtnl_mdb *mdb) |
| { |
| nl_object_put((struct nl_object *) mdb); |
| } |
| |
| /** @} */ |
| |
| /** |
| * @name Cache Management |
| * @{ |
| */ |
| int rtnl_mdb_alloc_cache(struct nl_sock *sk, struct nl_cache **result) |
| { |
| return nl_cache_alloc_and_fill(&rtnl_mdb_ops, sk, result); |
| } |
| |
| /** |
| * Build a neighbour cache including all MDB entries currently configured in the kernel. |
| * @arg sock Netlink socket. |
| * @arg result Pointer to store resulting cache. |
| * @arg flags Flags to apply to cache before filling |
| * |
| * Allocates a new MDB cache, initializes it properly and updates it |
| * to include all Multicast Database entries currently configured in the kernel. |
| * |
| * @return 0 on success or a negative error code. |
| */ |
| int rtnl_mdb_alloc_cache_flags(struct nl_sock *sock, struct nl_cache **result, |
| unsigned int flags) |
| { |
| struct nl_cache *cache; |
| int err; |
| |
| cache = nl_cache_alloc(&rtnl_mdb_ops); |
| if (!cache) |
| return -NLE_NOMEM; |
| |
| nl_cache_set_flags(cache, flags); |
| |
| if (sock && (err = nl_cache_refill(sock, cache)) < 0) { |
| nl_cache_free(cache); |
| return err; |
| } |
| |
| *result = cache; |
| return 0; |
| } |
| |
| /** @} */ |
| |
| /** |
| * @name Attributes |
| * @{ |
| */ |
| uint32_t rtnl_mdb_get_ifindex(struct rtnl_mdb *mdb) |
| { |
| return mdb->ifindex; |
| } |
| |
| void rtnl_mdb_add_entry(struct rtnl_mdb *mdb, struct rtnl_mdb_entry *entry) |
| { |
| nl_list_add_tail(&entry->mdb_list, &mdb->mdb_entry_list); |
| } |
| |
| void rtnl_mdb_foreach_entry(struct rtnl_mdb *mdb, |
| void (*cb)(struct rtnl_mdb_entry *, void *), |
| void *arg) |
| { |
| struct rtnl_mdb_entry *entry; |
| |
| nl_list_for_each_entry(entry, &mdb->mdb_entry_list, mdb_list) { |
| cb(entry, arg); |
| } |
| } |
| |
| int rtnl_mdb_entry_get_ifindex(struct rtnl_mdb_entry *mdb_entry) |
| { |
| return mdb_entry->ifindex; |
| } |
| |
| int rtnl_mdb_entry_get_vid(struct rtnl_mdb_entry *mdb_entry) |
| { |
| return mdb_entry->vid; |
| } |
| |
| int rtnl_mdb_entry_get_state(struct rtnl_mdb_entry *mdb_entry) |
| { |
| return mdb_entry->state; |
| } |
| |
| struct nl_addr *rtnl_mdb_entry_get_addr(struct rtnl_mdb_entry *mdb_entry) |
| { |
| return mdb_entry->addr; |
| } |
| |
| uint16_t rtnl_mdb_entry_get_proto(struct rtnl_mdb_entry *mdb_entry) |
| { |
| return mdb_entry->proto; |
| } |
| |
| /** @} */ |
| |
| static struct nl_object_ops mdb_obj_ops = { |
| .oo_name = "route/mdb", |
| .oo_size = sizeof(struct rtnl_mdb), |
| .oo_constructor = mdb_constructor, |
| .oo_dump = { |
| [NL_DUMP_LINE] = mdb_dump_line, |
| [NL_DUMP_DETAILS] = mdb_dump_details, |
| [NL_DUMP_STATS] = mdb_dump_stats, |
| }, |
| .oo_clone = mdb_clone, |
| .oo_compare = mdb_compare, |
| .oo_update = mdb_update, |
| .oo_free_data = mdb_free_data, |
| }; |
| |
| struct rtnl_mdb *rtnl_mdb_alloc(void) |
| { |
| return (struct rtnl_mdb *) nl_object_alloc(&mdb_obj_ops); |
| } |
| |
| static struct rtnl_mdb_entry *rtnl_mdb_entry_alloc(void) |
| { |
| struct rtnl_mdb_entry *mdb; |
| |
| mdb = calloc(1, sizeof(struct rtnl_mdb_entry)); |
| if (!mdb) |
| return NULL; |
| |
| nl_init_list_head(&mdb->mdb_list); |
| |
| return mdb; |
| |
| } |
| |
| static void rtnl_mdb_entry_free(struct rtnl_mdb_entry *mdb_entry) |
| { |
| nl_list_del(&mdb_entry->mdb_list); |
| nl_addr_put(mdb_entry->addr); |
| free(mdb_entry); |
| } |
| |
| static struct nl_af_group mdb_groups[] = { |
| {AF_BRIDGE, RTNLGRP_MDB}, |
| {END_OF_GROUP_LIST}, |
| }; |
| |
| static struct nl_cache_ops rtnl_mdb_ops = { |
| .co_name = "route/mdb", |
| .co_hdrsize = sizeof(struct br_port_msg), |
| .co_msgtypes = { |
| { RTM_NEWMDB, NL_ACT_NEW, "new"}, |
| { RTM_DELMDB, NL_ACT_DEL, "del"}, |
| { RTM_GETMDB, NL_ACT_GET, "get"}, |
| END_OF_MSGTYPES_LIST, |
| }, |
| .co_protocol = NETLINK_ROUTE, |
| .co_groups = mdb_groups, |
| .co_request_update = mdb_request_update, |
| .co_msg_parser = mdb_msg_parser, |
| .co_obj_ops = &mdb_obj_ops, |
| }; |
| |
| static void _nl_init mdb_init(void) |
| { |
| nl_cache_mngt_register(&rtnl_mdb_ops); |
| } |
| |
| static void _nl_exit mdb_exit(void) |
| { |
| nl_cache_mngt_unregister(&rtnl_mdb_ops); |
| } |
| |
| /** @} */ |