diff --git a/configure.ac b/configure.ac index 0ed4d2d7..9586daef 100644 --- a/configure.ac +++ b/configure.ac @@ -312,7 +312,7 @@ if test "$enable_mpls_kernel" != no ; then fi fi -all_protocols="aggregator $proto_bfd babel bgp l3vpn mrt ospf perf pipe radv rip rpki static" +all_protocols="aggregator $proto_bfd babel bgp bridge l3vpn mrt ospf perf pipe radv rip rpki static" all_protocols=`echo $all_protocols | sed 's/ /,/g'` @@ -325,6 +325,7 @@ AH_TEMPLATE([CONFIG_BABEL], [Babel protocol]) AH_TEMPLATE([CONFIG_BFD], [BFD protocol]) AH_TEMPLATE([CONFIG_BGP], [BGP protocol]) AH_TEMPLATE([CONFIG_BMP], [BMP protocol]) +AH_TEMPLATE([CONFIG_BRIDGE], [Bridge protocol]) AH_TEMPLATE([CONFIG_L3VPN], [L3VPN protocol]) AH_TEMPLATE([CONFIG_MRT], [MRT protocol]) AH_TEMPLATE([CONFIG_OSPF], [OSPF protocol]) diff --git a/nest/protocol.h b/nest/protocol.h index c87d3814..16dc48be 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -45,6 +45,7 @@ enum protocol_class { PROTOCOL_BFD, PROTOCOL_BGP, PROTOCOL_BMP, + PROTOCOL_BRIDGE, PROTOCOL_DEVICE, PROTOCOL_DIRECT, PROTOCOL_KERNEL, @@ -106,7 +107,7 @@ void protos_dump_all(void); extern struct protocol proto_device, proto_radv, proto_rip, proto_static, proto_mrt, - proto_ospf, proto_perf, proto_l3vpn, proto_aggregator, + proto_ospf, proto_perf, proto_l3vpn, proto_aggregator, proto_bridge, proto_pipe, proto_bgp, proto_bmp, proto_bfd, proto_babel, proto_rpki; /* diff --git a/nest/route.h b/nest/route.h index e6f6c64a..128ba870 100644 --- a/nest/route.h +++ b/nest/route.h @@ -482,7 +482,8 @@ typedef struct rta { #define RTS_PERF 15 /* Perf checker */ #define RTS_L3VPN 16 /* MPLS L3VPN */ #define RTS_AGGREGATED 17 /* Aggregated route */ -#define RTS_MAX 18 +#define RTS_BRIDGE 18 +#define RTS_MAX 19 #define RTD_NONE 0 /* Undefined next hop */ #define RTD_UNICAST 1 /* Next hop is neighbor router */ diff --git a/nest/rt-attr.c b/nest/rt-attr.c index af864bdf..bc6864a3 100644 --- a/nest/rt-attr.c +++ b/nest/rt-attr.c @@ -78,6 +78,7 @@ const char * const rta_src_names[RTS_MAX] = { [RTS_PERF] = "Perf", [RTS_L3VPN] = "L3VPN", [RTS_AGGREGATED] = "aggregated", + [RTS_BRIDGE] = "bridge", }; const char * rta_dest_names[RTD_MAX] = { @@ -1311,7 +1312,7 @@ rta_dump(rta *a) "RTS_STAT_DEV", "RTS_REDIR", "RTS_RIP", "RTS_OSPF", "RTS_OSPF_IA", "RTS_OSPF_EXT1", "RTS_OSPF_EXT2", "RTS_BGP", "RTS_PIPE", "RTS_BABEL", - "RTS_RPKI", "RTS_PERF", "RTS_AGGREGATED", }; + "RTS_RPKI", "RTS_PERF", "RTS_AGGREGATED", "RTS_BRIDGE" }; static char *rtd[] = { "", " DEV", " HOLE", " UNREACH", " PROHIBIT" }; debug("pref=%d uc=%d %s %s%s h=%04x", diff --git a/proto/bridge/Doc b/proto/bridge/Doc new file mode 100644 index 00000000..ca0136f9 --- /dev/null +++ b/proto/bridge/Doc @@ -0,0 +1 @@ +S bridge.c diff --git a/proto/bridge/Makefile b/proto/bridge/Makefile new file mode 100644 index 00000000..82755ee7 --- /dev/null +++ b/proto/bridge/Makefile @@ -0,0 +1,6 @@ +src := bridge.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) + +tests_objs := $(tests_objs) $(src-o-files) diff --git a/proto/bridge/bridge.c b/proto/bridge/bridge.c new file mode 100644 index 00000000..5e0a8488 --- /dev/null +++ b/proto/bridge/bridge.c @@ -0,0 +1,285 @@ +/* + * BIRD -- Linux Bridge Interface + * + * (c) 2023 Ondrej Zajicek + * (c) 2023 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +/** + * DOC: Bridge + * + * The Bridge protocol is responsible for synchronization of BIRD ethernet + * table with Linux kernel bridge interface (although the code is mostly + * OS-independent, as Linux-specific parts are in the Netlink code). It is + * similar to (and based on) the Kernel protocol, but the differences are + * large enough to treat it as an independent protocol. + */ + +/* + * TODO: + * - Better two-way synchronization, including initial clean-up + * - Wait for existence (and active state) of the bridge device + * - Check for consistency of vlan_filtering flag + * - Channel should be R_ANY for BUM routes, but RA_OPTIMAL for others + * - Configuration of VIDs? + */ + +#undef LOCAL_DEBUG + +#include "nest/bird.h" +#include "nest/iface.h" +#include "nest/protocol.h" +#include "nest/route.h" +#include "nest/mpls.h" +#include "nest/cli.h" +#include "conf/conf.h" +#include "filter/filter.h" +#include "filter/data.h" +#include "lib/string.h" + +#include "bridge.h" + +void +kbr_got_route(struct kbr_proto *p, const net_addr *n, rte *e, int src UNUSED, int scan UNUSED) +{ + struct channel *c = p->p.main_channel; + + if (e) e->attrs->pref = c->preference; + rte_update2(c, n, e, p->p.main_source); +} + +static void +kbr_scan(timer *t) +{ + struct kbr_proto *p = t->data; + struct channel *c = p->p.main_channel; + + TRACE(D_EVENTS, "Scanning bridge table"); + + rt_refresh_begin(c->table, c); + kbr_do_scan(p); + rt_refresh_end(c->table, c); +} + +static void +kbr_rt_notify(struct proto *P, struct channel *c0 UNUSED, net *net, rte *new, rte *old) +{ + struct kbr_proto *p UNUSED = (void *) P; + + rte *new_gw = (new && ipa_nonzero(new->attrs->nh.gw)) ? new : NULL; + rte *old_gw = (old && ipa_nonzero(old->attrs->nh.gw)) ? old : NULL; + + /* + * This code handles peculiarities of Linux bridge behavior, where the bridge + * device has attached both network interfaces and a tunnel (VXLAN) device. + * For 'remote' MAC addresses, forwarding entries in the bridge device point + * to the tunnel device. The tunnel device has another forwarding table with + * forwarding entries, this time with IP addresses of remote endpoints. + * + * BUM frames are propagated by the bridge device to all attached devices, so + * there is no need to have a bridge forwarding entry, but they must have a + * tunnel forwarding entry for each destination. + */ + + if (mac_zero(net_mac_addr(net->n.addr))) + { + /* For BUM routes, we have multiple tunnel entries, but no bridge entry */ + kbr_update_fdb(net->n.addr, new_gw, old_gw, 1); + return; + } + + /* For regular routes, we have one bridge entry, perhaps also one tunnel entry */ + kbr_replace_fdb(net->n.addr, new, old, 0); + kbr_replace_fdb(net->n.addr, new_gw, old_gw, 1); +} + +static inline int +kbr_is_installed(struct channel *c, net *n) +{ + return n->routes && bmap_test(&c->export_map, n->routes->id); +} + +static void +kbr_flush_routes(struct kbr_proto *p) +{ + struct channel *c = p->p.main_channel; + + TRACE(D_EVENTS, "Flushing bridge routes"); + FIB_WALK(&c->table->fib, net, n) + { + if (kbr_is_installed(c, n)) + kbr_rt_notify(&p->p, c, n, NULL, n->routes); + } + FIB_WALK_END; +} + + +static int +kbr_preexport(struct channel *C, rte *e) +{ + struct kbr_proto *p = (void *) C->proto; + + /* Reject our own routes */ + if (e->src->proto == &p->p) + return -1; + + return 0; +} + +static void +kbr_reload_routes(struct channel *C) +{ + struct kbr_proto *p = (void *) C->proto; + + tm_start(p->scan_timer, 0); +} + +static inline u32 +kbr_metric(rte *e) +{ + u32 metric = ea_get_int(e->attrs->eattrs, EA_GEN_IGP_METRIC, e->attrs->igp_metric); + return MIN(metric, IGP_METRIC_UNKNOWN); +} + +static int +kbr_rte_better(rte *new, rte *old) +{ + /* This is hack, we should have full BGP-style comparison */ + return kbr_metric(new) < kbr_metric(old); +} + +static void +kbr_postconfig(struct proto_config *CF) +{ + struct kbr_config *cf = (void *) CF; + + if (! proto_cf_main_channel(CF)) + cf_error("Channel not specified"); + + if (!cf->bridge_dev) + cf_error("Bridge device not specified"); +} + +static struct proto * +kbr_init(struct proto_config *CF) +{ + struct proto *P = proto_new(CF); + // struct kbr_proto *p = (void *) P; + // struct kbr_config *cf = (void *) CF; + + P->main_channel = proto_add_channel(P, proto_cf_main_channel(CF)); + + P->rt_notify = kbr_rt_notify; + P->preexport = kbr_preexport; + P->reload_routes = kbr_reload_routes; + P->rte_better = kbr_rte_better; + + return P; +} + +static int +kbr_start(struct proto *P) +{ + struct kbr_proto *p = (void *) P; + struct kbr_config *cf = (void *) P->cf; + + p->bridge_dev = cf->bridge_dev; + p->vlan_filtering = cf->vlan_filtering; + + p->scan_timer = tm_new_init(p->p.pool, kbr_scan, p, cf->scan_time, 0); + tm_start(p->scan_timer, 100 MS); + + kbr_sys_start(p); + + return PS_UP; +} + +static int +kbr_shutdown(struct proto *P UNUSED) +{ + struct kbr_proto *p = (void *) P; + + kbr_flush_routes(p); + kbr_sys_shutdown(p); + + return PS_DOWN; +} + +static int +kbr_reconfigure(struct proto *P, struct proto_config *CF) +{ + struct kbr_proto *p = (void *) P; + struct kbr_config *cf = (void *) CF; + + if ((p->bridge_dev != cf->bridge_dev) || + (p->vlan_filtering != cf->vlan_filtering)) + return 0; + + if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF))) + return 0; + + return 1; +} + +static void +kbr_copy_config(struct proto_config *dest UNUSED, struct proto_config *src UNUSED) +{ + /* Just a shallow copy, not many items here */ +} + +const char * const kbr_src_names[KBR_SRC_MAX] = { + [KBR_SRC_BIRD] = "bird", + [KBR_SRC_LOCAL] = "local", + [KBR_SRC_STATIC] = "static", + [KBR_SRC_DYNAMIC] = "dynamic", +}; + +static int +kbr_get_attr(const eattr *a, byte *buf, int buflen UNUSED) +{ + switch (a->id) + { + case EA_KBR_SOURCE:; + const char *src = (a->u.data < KBR_SRC_MAX) ? kbr_src_names[a->u.data] : "?"; + bsprintf(buf, "source: %s", src); + return GA_FULL; + + default: + return GA_UNKNOWN; + } +} + +static void +kbr_get_route_info(rte *rte, byte *buf) +{ + eattr *a = ea_find(rte->attrs->eattrs, EA_KBR_SOURCE); + char src = (a && a->u.data < KBR_SRC_MAX) ? "BLSD"[a->u.data] : '?'; + + bsprintf(buf, " %c (%u)", src, rte->attrs->pref); +} + + +struct protocol proto_bridge = { + .name = "Bridge", + .template = "bridge%d", + .class = PROTOCOL_BRIDGE, + .channel_mask = NB_ETH, + .proto_size = sizeof(struct kbr_proto), + .config_size = sizeof(struct kbr_config), + .postconfig = kbr_postconfig, + .init = kbr_init, + .start = kbr_start, + .shutdown = kbr_shutdown, + .reconfigure = kbr_reconfigure, + .copy_config = kbr_copy_config, + .get_attr = kbr_get_attr, + .get_route_info = kbr_get_route_info, +}; + +void +bridge_build(void) +{ + proto_build(&proto_bridge); +} diff --git a/proto/bridge/bridge.h b/proto/bridge/bridge.h new file mode 100644 index 00000000..0022eae4 --- /dev/null +++ b/proto/bridge/bridge.h @@ -0,0 +1,53 @@ +/* + * BIRD -- Linux Bridge Interface + * + * (c) 2023 Ondrej Zajicek + * (c) 2023 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRD_BRIDGE_H_ +#define _BIRD_BRIDGE_H_ + + +#define EA_KBR_SOURCE EA_CODE(PROTOCOL_BRIDGE, 0) + +#define KBR_SRC_BIRD 0 +#define KBR_SRC_LOCAL 1 +#define KBR_SRC_STATIC 2 +#define KBR_SRC_DYNAMIC 3 +#define KBR_SRC_MAX 4 + + +struct kbr_config { + struct proto_config c; + + struct iface *bridge_dev; + btime scan_time; + int vlan_filtering; +}; + +struct kbr_proto { + struct proto p; + + struct iface *bridge_dev; + timer *scan_timer; + int vlan_filtering; + + struct kbr_proto *hash_next; +}; + +void kbr_got_route(struct kbr_proto *p, const net_addr *n, rte *e, int src, int scan); + + +/* krt sysdep */ + +int kbr_sys_start(struct kbr_proto *p); +void kbr_sys_shutdown(struct kbr_proto *p); + +void kbr_replace_fdb(const net_addr *n, rte *new, rte *old, int tunnel); +void kbr_update_fdb(const net_addr *n, rte *new, rte *old, int tunnel); +void kbr_do_scan(struct kbr_proto *p); + +#endif diff --git a/proto/bridge/config.Y b/proto/bridge/config.Y new file mode 100644 index 00000000..f978bda9 --- /dev/null +++ b/proto/bridge/config.Y @@ -0,0 +1,59 @@ +/* + * BIRD -- Linux Bridge Interface + * + * (c) 2023 Ondrej Zajicek + * (c) 2023 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +CF_HDR + +#include "proto/bridge/bridge.h" + + +CF_DEFINES + +#define KBR_CFG ((struct kbr_config *) this_proto) + + +CF_DECLS + +CF_KEYWORDS(BRIDGE, BRIDGE, DEVICE, VLAN, FILTERING, SCAN, TIME, KBR_SOURCE) + + +CF_GRAMMAR + +proto: kbr_proto; + + +kbr_proto_start: proto_start BRIDGE +{ + this_proto = proto_config_new(&proto_bridge, $1); + this_proto->net_type = NET_ETH; + + KBR_CFG->scan_time = 60 S_; +}; + +kbr_proto_item: + proto_item + | proto_channel { $1->ra_mode = RA_ANY; } + | BRIDGE DEVICE text { KBR_CFG->bridge_dev = if_get_by_name($3); } + | VLAN FILTERING bool { KBR_CFG->vlan_filtering = $3; } + | SCAN TIME expr_us { KBR_CFG->scan_time = $3; } + ; + +kbr_proto_opts: + /* empty */ + | kbr_proto_opts kbr_proto_item ';' + ; + +kbr_proto: + kbr_proto_start proto_name '{' kbr_proto_opts '}'; + + +dynamic_attr: KBR_SOURCE { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT, EA_KBR_SOURCE); } ; + +CF_CODE + +CF_END diff --git a/sysdep/linux/netlink.c b/sysdep/linux/netlink.c index 29446cab..a983b98c 100644 --- a/sysdep/linux/netlink.c +++ b/sysdep/linux/netlink.c @@ -27,6 +27,8 @@ #include "lib/hash.h" #include "conf/conf.h" +#include "proto/bridge/bridge.h" + #include CONFIG_INCLUDE_NLSYS_H #define krt_ipv4(p) ((p)->af == AF_INET) @@ -40,6 +42,7 @@ struct nl_parse_state int scan; u32 rta_flow; + u32 bridge_id; }; /* @@ -210,6 +213,34 @@ nl_request_dump_route(int af, int table_id) nl_scan.last_hdr = NULL; } +static void +nl_request_dump_neigh(int af, int bridge_id) +{ + struct { + struct nlmsghdr nh; + struct ndmsg ndm; + struct rtattr rta; + u32 master_id; + } req = { + .nh.nlmsg_type = RTM_GETNEIGH, + .nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)), + .nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, + .nh.nlmsg_seq = ++(nl_scan.seq), + .ndm.ndm_family = af, + }; + + if (bridge_id) + { + req.rta.rta_type = NDA_MASTER; + req.rta.rta_len = RTA_LENGTH(4); + req.master_id = bridge_id; + req.nh.nlmsg_len = NLMSG_ALIGN(req.nh.nlmsg_len) + req.rta.rta_len; + } + + send(nl_scan.fd, &req, req.nh.nlmsg_len, 0); + nl_scan.last_hdr = NULL; +} + static struct nlmsghdr * nl_get_reply(struct nl_sock *nl) @@ -444,6 +475,16 @@ static struct nl_want_attrs rtm_attr_want_mpls[BIRD_RTA_MAX] = { #endif +#define BIRD_NDA_MAX (NDA_MASTER+1) + +static struct nl_want_attrs ndm_attr_want[BIRD_NDA_MAX] = { + [NDA_LLADDR] = { 1, 1, sizeof(mac_addr) }, + [NDA_VLAN] = { 1, 1, sizeof(u16) }, + [NDA_VNI] = { 1, 1, sizeof(u32) }, + [NDA_MASTER] = { 1, 1, sizeof(u32) }, +}; + + static int nl_parse_attrs(struct rtattr *a, struct nl_want_attrs *want, struct rtattr **k, int ksize) { @@ -493,6 +534,9 @@ static inline ip_addr rta_get_ipa(struct rtattr *a) return ipa_from_ip6(rta_get_ip6(a)); } +static inline mac_addr rta_get_mac(struct rtattr *a) +{ return *(mac_addr *) RTA_DATA(a); } + static inline ip_addr rta_get_via(struct rtattr *a) { struct rtvia *v = RTA_DATA(a); @@ -1248,7 +1292,7 @@ static HASH(struct krt_proto) nl_table_map; #define RTH_FN(a,i) a ^ u32_hash(i) #define RTH_REHASH rth_rehash -#define RTH_PARAMS /8, *2, 2, 2, 6, 20 +#define RTH_PARAMS /8, *1, 2, 2, 4, 20 HASH_DEFINE_REHASH_FN(RTH, struct krt_proto) @@ -1858,6 +1902,207 @@ krt_do_scan(struct krt_proto *p) } } + +/* + * FDB entries + */ + +static inline u32 +kbr_bridge_id(struct kbr_proto *p) +{ + return p->bridge_dev->index; +} + +static HASH(struct kbr_proto) nl_bridge_map; + +#define BRH_KEY(p) kbr_bridge_id(p) +#define BRH_NEXT(p) p->hash_next +#define BRH_EQ(i1,i2) i1 == i2 +#define BRH_FN(i) u32_hash(i) + +#define BRH_REHASH brh_rehash +#define BRH_PARAMS /8, *1, 2, 2, 4, 20 + +HASH_DEFINE_REHASH_FN(BRH, struct kbr_proto); + +static int +nl_send_fdb(const net_addr *n0, rte *e, int op, int tunnel) +{ + const net_addr_eth *n = (void *) n0; + + struct { + struct nlmsghdr h; + struct ndmsg n; + char buf[0]; + } *r; + + int rsize = sizeof(*r) + 256; + r = alloca(rsize); + + memset(&r->h, 0, sizeof(r->h)); + memset(&r->n, 0, sizeof(r->n)); + r->h.nlmsg_type = op ? RTM_NEWNEIGH : RTM_DELNEIGH; + r->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)); + r->h.nlmsg_flags = op | NLM_F_REQUEST | NLM_F_ACK; + + r->n.ndm_family = AF_BRIDGE; + r->n.ndm_state = NUD_NOARP; + r->n.ndm_flags = (tunnel ? NTF_SELF : NTF_MASTER) | NTF_EXT_LEARNED; + + nl_add_attr(&r->h, rsize, NDA_LLADDR, n->mac.addr, 6); + + if (n->vid) + nl_add_attr_u16(&r->h, rsize, NDA_VLAN, n->vid); + + struct nexthop *nh = &e->attrs->nh; + ASSERT(e->attrs->dest == RTD_UNICAST && !nh->next); + r->n.ndm_ifindex = nh->iface->index; + + if (tunnel) + { + ASSERT(ipa_nonzero(nh->gw)); + nl_add_attr_ipa(&r->h, rsize, NDA_DST, nh->gw); + + if (nh->labels) + nl_add_attr_u32(&r->h, rsize, NDA_VNI, nh->label[0]); + + r->n.ndm_state |= NUD_PERMANENT; + } + + /* Ignore missing for DELETE */ + return nl_exchange(&r->h, (op == NL_OP_DELETE)); +} + +void +kbr_replace_fdb(const net_addr *n, rte *new, rte *old, int tunnel) +{ + int err = 0; + + if (old && new) + { + err = nl_send_fdb(n, new, NL_OP_REPLACE, tunnel); + } + else + { + if (old) + nl_send_fdb(n, old, NL_OP_DELETE, tunnel); + + if (new) + err = nl_send_fdb(n, new, NL_OP_ADD, tunnel); + } + + if (err < 0) + log(L_WARN "NL error %m"); +} + +void +kbr_update_fdb(const net_addr *n, rte *new, rte *old, int tunnel) +{ + int err = 0; + + if (old) + nl_send_fdb(n, old, NL_OP_DELETE, tunnel); + + if (new) + err = nl_send_fdb(n, new, NL_OP_APPEND, tunnel); + + if (err < 0) + log(L_WARN "NL error %m"); +} + +#ifndef NDM_RTA +#define NDM_RTA(n) (struct rtattr *)(((char *) n) + NLMSG_ALIGN(sizeof(struct ndmsg))) +#endif + +static void +nl_parse_fdb(struct nl_parse_state *s, struct nlmsghdr *h) +{ + struct ndmsg *nd; + struct rtattr *a[BIRD_NDA_MAX]; + int new = (h->nlmsg_type == RTM_NEWNEIGH); + + if (!(nd = nl_checkin(h, sizeof(*nd)))) + return; + + if (nd->ndm_family != AF_BRIDGE) + return; + + if (!nl_parse_attrs(NDM_RTA(nd), ndm_attr_want, a, sizeof(a))) + return; + + if (!a[NDA_LLADDR] || !a[NDA_MASTER]) + return; + + uint vid = a[NDA_VLAN] ? rta_get_u16(a[NDA_VLAN]) : 0; + + net_addr n; + net_fill_eth(&n, rta_get_mac(a[NDA_LLADDR]), vid); + + u32 bridge_id = rta_get_u32(a[NDA_MASTER]); + + /* Should be filtered by kernel */ + if (s->bridge_id && (bridge_id != s->bridge_id)) + return; + + /* Do we know this bridge? */ + struct kbr_proto *p = HASH_FIND(nl_bridge_map, BRH, bridge_id); + if (!p) + return; + + /* Accept VLAN-tagged entries when vlan filtering is enabled */ + if (!vid != !p->vlan_filtering) + return; + + struct iface *oif = if_find_by_index(nd->ndm_ifindex); + if (!oif) + return; + + rta ra = { + .source = RTS_BRIDGE, + .scope = SCOPE_UNIVERSE, + .dest = RTD_UNICAST, + .nh.iface = oif, + }; + + int src; + if (nd->ndm_flags & NTF_EXT_LEARNED) + // src = KBR_SRC_BIRD; + return; + else if (nd->ndm_state & NUD_PERMANENT) + src = KBR_SRC_LOCAL; + else if (nd->ndm_state & NUD_NOARP) + src = KBR_SRC_STATIC; + else + src = KBR_SRC_DYNAMIC; + + ea_set_attr_u32(&ra.eattrs, s->pool, EA_KBR_SOURCE, 0, EAF_TYPE_INT, src); + + rte *e = new ? rte_get_temp(&ra, p->p.main_source) : NULL; + + DBG("FDB %s %N dev %s [%x %x %x]\n", (new ? "add" : "del"), &n, oif->name, nd->ndm_state, nd->ndm_flags, nd->ndm_type); + kbr_got_route(p, &n, e, src, s->scan); +} + +void +kbr_do_scan(struct kbr_proto *p) +{ + struct nl_parse_state s = { + .pool = nl_linpool, + .scan = 1, + .bridge_id = kbr_bridge_id(p), + }; + + nl_request_dump_neigh(AF_BRIDGE, kbr_bridge_id(p)); + + struct nlmsghdr *h; + while (h = nl_get_scan()) + { + if (h->nlmsg_type == RTM_NEWNEIGH || h->nlmsg_type == RTM_DELNEIGH) + nl_parse_fdb(&s, h); + } +} + + /* * Asynchronous Netlink interface */ @@ -1883,18 +2128,25 @@ nl_async_msg(struct nlmsghdr *h) DBG("KRT: Received async route notification (%d)\n", h->nlmsg_type); nl_parse_route(&s, h); break; + case RTM_NEWLINK: case RTM_DELLINK: DBG("KRT: Received async link notification (%d)\n", h->nlmsg_type); if (kif_proto) nl_parse_link(h, 0); break; + case RTM_NEWADDR: case RTM_DELADDR: DBG("KRT: Received async address notification (%d)\n", h->nlmsg_type); if (kif_proto) nl_parse_addr(h, 0); break; + + case RTM_NEWNEIGH: + case RTM_DELNEIGH: + nl_parse_fdb(&s, h); + default: DBG("KRT: Received unknown async notification (%d)\n", h->nlmsg_type); } @@ -1981,7 +2233,7 @@ nl_open_async(void) bzero(&sa, sizeof(sa)); sa.nl_family = AF_NETLINK; - sa.nl_groups = RTMGRP_LINK | + sa.nl_groups = RTMGRP_LINK | RTMGRP_NEIGH | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE; @@ -2038,7 +2290,8 @@ void krt_sys_io_init(void) { nl_linpool = lp_new_default(krt_pool); - HASH_INIT(nl_table_map, krt_pool, 6); + HASH_INIT(nl_table_map, krt_pool, 4); + HASH_INIT(nl_bridge_map, krt_pool, 4); } int @@ -2141,6 +2394,32 @@ krt_sys_get_attr(const eattr *a, byte *buf, int buflen UNUSED) } +int +kbr_sys_start(struct kbr_proto *p) +{ + struct kbr_proto *old = HASH_FIND(nl_bridge_map, BRH, kbr_bridge_id(p)); + + if (old) + { + log(L_ERR "%s: Bridge device %s already registered by %s", + p->p.name, p->bridge_dev->name, old->p.name); + return 0; + } + + HASH_INSERT2(nl_bridge_map, BRH, krt_pool, p); + + nl_open(); + nl_open_async(); + + return 1; +} + +void +kbr_sys_shutdown(struct kbr_proto *p) +{ + HASH_REMOVE2(nl_bridge_map, BRH, krt_pool, p); +} + void kif_sys_start(struct kif_proto *p UNUSED)