467 lines
11 KiB
C
467 lines
11 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-only */
|
|
/*
|
|
* lib/route/mdb.c Multicast Database
|
|
*/
|
|
|
|
#include <netlink-private/netlink.h>
|
|
#include <netlink/netlink.h>
|
|
#include <netlink/route/mdb.h>
|
|
#include <netlink/utils.h>
|
|
#include <linux/if_bridge.h>
|
|
|
|
/** @cond SKIP */
|
|
#define MDB_ATTR_IFINDEX 0x000001
|
|
#define MDB_ATTR_ENTRIES 0x000002
|
|
|
|
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 MDB_DIFF(ATTR, EXPR) ATTR_DIFF(attrs, MDB_ATTR_##ATTR, a, b, EXPR)
|
|
diff |= MDB_DIFF(IFINDEX, a->ifindex != b->ifindex);
|
|
#undef MDB_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 __init mdb_init(void)
|
|
{
|
|
nl_cache_mngt_register(&rtnl_mdb_ops);
|
|
}
|
|
|
|
static void __exit mdb_exit(void)
|
|
{
|
|
nl_cache_mngt_unregister(&rtnl_mdb_ops);
|
|
}
|
|
|
|
/** @} */
|