--- /dev/null
+/*
+ * BIRD -- Linux Bridge Interface
+ *
+ * (c) 2023--2026 Ondrej Zajicek <santiago@crfreenet.org>
+ * (c) 2023--2026 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:
+ * - Wait for existence (and active state) of the bridge device
+ * - Channel should be R_ANY for BUM routes, but RA_OPTIMAL for others
+ */
+
+#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 "sysdep/unix/krt.h"
+
+#include "bridge.h"
+
+static struct kbr_vlan * kbr_find_vlan(struct kbr_proto *p, uint ifi, uint vid);
+static void kbr_prune_vlans0(struct kbr_proto *p);
+static void kbr_prune_vlans1(struct kbr_proto *p);
+
+
+/*
+ * Bridge entries
+ */
+
+static void
+kbr_trace(struct kbr_proto *p, const net_addr *n, rte *e, char *msg, bool tunnel)
+{
+ if (p->p.debug & D_PACKETS)
+ {
+ struct nexthop *nh = &e->attrs->nh;
+
+ if (!tunnel || ipa_zero(nh->gw))
+ log(L_TRACE "%s: %N dev %s: %s",
+ p->p.name, n, nh->iface->name, msg);
+ else
+ log(L_TRACE "%s: %N dev %s dst %I vni %u: %s",
+ p->p.name, n, nh->iface->name, nh->gw, (uint) nh->label[0], msg);
+ }
+}
+
+static inline void
+kbr_trace_in(struct kbr_proto *p, const net_addr *n, rte *e, char *msg)
+{
+ if (!e)
+ return;
+
+ kbr_trace(p, n, e, msg, true);
+}
+
+static void
+kbr_trace_out(struct kbr_proto *p, const net_addr *n, rte *new, rte *old, bool tunnel)
+{
+ if (new)
+ kbr_trace(p, n, new, (old ? "updating" : "installing"), tunnel);
+ else if (old)
+ kbr_trace(p, n, old, "deleting", tunnel);
+}
+
+static bool
+kbr_match_fdb(const rte *x, const rte *y)
+{
+ if (x->attrs->dest != RTD_UNICAST)
+ return false;
+
+ struct nexthop *xn = &x->attrs->nh, *yn = &y->attrs->nh;
+ return (xn->iface == yn->iface) && ipa_equal(xn->gw, yn->gw);
+}
+
+/* Find matching route in the BIRD table */
+static struct rte *
+kbr_find_fdb(struct kbr_proto *p UNUSED, struct channel *c, const net_addr *n, const rte *ref)
+{
+ net *net = net_find(c->table, n);
+ if (!net)
+ return NULL;
+
+ /* For BUM routes, find matching entry */
+ if (mac_zero(net_mac_addr(n)))
+ {
+ for (rte *rt = net->routes; rt; rt = rt->next)
+ if (rte_is_valid(rt) && kbr_match_fdb(rt, ref))
+ return rt;
+
+ return NULL;
+ }
+
+ /* For regular routes, just return the best route */
+ rte *rt = net->routes;
+ return rte_is_valid(rt) ? rt : NULL;
+}
+
+/* Export BIRD route through filters */
+static struct rte *
+kbr_export_fdb(struct kbr_proto *p UNUSED, struct channel *c, rte *rt0, rte **rt_free)
+{
+ rte *rt = rt0;
+ *rt_free = NULL;
+
+ /* Route should be already exported */
+ if (!bmap_test(&c->export_map, rt->id))
+ return NULL;
+
+ /* We could run krt_preexport() here, but it is already handled by export_map */
+ const struct filter *filter = c->out_filter;
+
+ if (filter == FILTER_REJECT)
+ return NULL;
+
+ if (filter == FILTER_ACCEPT)
+ goto accept;
+
+ if (f_run(filter, &rt, tmp_linpool, FF_SILENT) > F_ACCEPT)
+ goto reject;
+
+accept:
+ if (rt != rt0)
+ *rt_free = rt;
+ return rt;
+
+reject:
+ if (rt != rt0)
+ rte_free(rt);
+ return NULL;
+}
+
+
+/*
+ * bird async -> ignore
+ * bird scan -> review
+ * non-bird bridge -> import
+ * non-bird tunnel -> ignore
+ */
+
+/* Compare if two routes have the same destination */
+static bool
+kbr_same_dest(const rte *x, const rte *y, bool tunnel)
+{
+ rta *xa = x->attrs, *ya = y->attrs;
+
+ if (xa->dest != ya->dest)
+ return false;
+
+ if (xa->dest != RTD_UNICAST)
+ return true;
+
+ if (tunnel)
+ return nexthop_equal(&(xa->nh), &(ya->nh));
+ else
+ /* Bridge fdb entries only contain ifaces */
+ return xa->nh.iface == ya->nh.iface;
+}
+
+/* Process FDB entry received from the kernel */
+void
+kbr_got_fdb(struct kbr_proto *p, const net_addr *n, rte *e, int src, bool scan, bool tunnel)
+{
+ struct channel *c = p->p.main_channel;
+ rte *rt_free = NULL;
+
+ /* Non-BIRD routes are just imported, both scan and async */
+ if (src != KBR_SRC_BIRD)
+ {
+ /* Import of tunnel FDBs is not supported */
+ if (tunnel)
+ goto skip;
+
+ if (e) e->attrs->pref = c->preference;
+ rte_update2(c, n, e, p->p.main_source);
+ return;
+ }
+
+ /* BIRD routes are silently ignored in async, that is just echo */
+ if (!scan)
+ goto skip;
+
+ /* BIRD routes received during scan */
+ /* We wait for the initial feed to have correct export state */
+ if (!p->ready)
+ goto ignore;
+
+ rte *rt0 = kbr_find_fdb(p, c, n, e);
+
+ /* No matching route in BIRD table */
+ if (!rt0)
+ goto delete;
+
+ rte *new = kbr_export_fdb(p, c, rt0, &rt_free);
+
+ /* Rejected by filters */
+ if (!new)
+ goto delete;
+
+ /*
+ * One BIRD route can represent two kernel FDB entries, bridge entry and tunnel
+ * entry (distinguished by tunnel arg). See kbr_rt_notify() for details.
+ *
+ * In short:
+ * MAC is non-zero <-> there should be bridge entry
+ * gateway is non-zero <-> there should be tunnel entry
+ * (We only check <- direction here)
+ */
+
+ if (!tunnel && mac_zero(net_mac_addr(n)))
+ goto delete;
+
+ if (tunnel && ipa_zero(new->attrs->nh.gw))
+ goto delete;
+
+ /* Route to this destination was already seen. Strange, but it happens... */
+ if (bmap_test(&p->seen_map[tunnel], new->id))
+ goto aseen;
+
+ /* Mark route as seen */
+ bmap_set(&p->seen_map[tunnel], new->id);
+
+ /* TODO: There also may be changes in route eattrs, we ignore that for now */
+ if (!bmap_test(&p->sync_map[tunnel], new->id) || !kbr_same_dest(e, new, tunnel))
+ goto update;
+
+ goto seen;
+
+seen:
+ kbr_trace_in(p, n, e, "seen");
+ goto done;
+
+aseen:
+ kbr_trace_in(p, n, e, "already seen");
+ goto done;
+
+ignore:
+ kbr_trace_in(p, n, e, "ignored");
+ goto done;
+
+update:
+ kbr_trace_in(p, n, new, "updating");
+ if (mac_zero(net_mac_addr(n)))
+ kbr_update_fdb(p, n, new, e, tunnel);
+ else
+ kbr_replace_fdb(p, n, new, e, tunnel);
+ goto done;
+
+delete:
+ kbr_trace_in(p, n, e, "deleting");
+ if (mac_zero(net_mac_addr(n)))
+ kbr_update_fdb(p, n, NULL, e, tunnel);
+ else
+ kbr_replace_fdb(p, n, NULL, e, tunnel);
+ goto done;
+
+skip:
+done:
+ if (e)
+ rte_free(e);
+
+ if (rt_free)
+ rte_free(rt_free);
+}
+
+/* Install missing FDB entries during pruning phase */
+static void
+kbr_prune_fdb(struct kbr_proto *p, struct channel *c, const net_addr *n, rte *e)
+{
+ rte *rt_free = NULL;
+ rte *new = kbr_export_fdb(p, c, e, &rt_free);
+
+ if (!new)
+ return;
+
+ if (!mac_zero(net_mac_addr(n)) &&
+ !bmap_test(&p->seen_map[0], new->id))
+ {
+ kbr_trace(p, n, new, "installing", false);
+ kbr_replace_fdb(p, n, new, NULL, false);
+ }
+
+ if (ipa_nonzero(new->attrs->nh.gw) &&
+ !bmap_test(&p->seen_map[1], new->id))
+ {
+ kbr_trace(p, n, new, "installing", true);
+ if (mac_zero(net_mac_addr(n)))
+ kbr_update_fdb(p, n, new, NULL, true);
+ else
+ kbr_replace_fdb(p, n, new, NULL, true);
+ }
+
+ if (rt_free)
+ rte_free(rt_free);
+}
+
+static void
+kbr_prune_fdbs(struct kbr_proto *p)
+{
+ struct channel *c = p->p.main_channel;
+ struct rtable *t = c->table;
+
+ /* Should not happen */
+ if (!p->ready)
+ return;
+
+ TRACE(D_EVENTS, "Prunning bridge table");
+
+ FIB_WALK(&t->fib, net, net)
+ {
+ const net_addr *n = net->n.addr;
+
+ if (mac_zero(net_mac_addr(n)))
+ {
+ /* For BUM routes, handle all tunnel entries */
+ for (rte *rt = net->routes; rt; rt = rt->next)
+ if (rte_is_valid(rt))
+ kbr_prune_fdb(p, c, n, rt);
+ }
+ else
+ {
+ /* For regular routes, handle only the best route */
+ if (rte_is_valid(net->routes))
+ kbr_prune_fdb(p, c, n, net->routes);
+ }
+ }
+ FIB_WALK_END;
+}
+
+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");
+
+ bmap_reset(&p->seen_map[0], 1024);
+ bmap_reset(&p->seen_map[1], 1024);
+
+ if (p->vlan_filtering)
+ {
+ kbr_do_vlan_scan(p);
+ kbr_prune_vlans0(p);
+ }
+
+ rt_refresh_begin(c->table, c);
+ kbr_do_fdb_scan(p);
+ kbr_prune_fdbs(p);
+ rt_refresh_end(c->table, c);
+
+ if (p->vlan_filtering)
+ kbr_prune_vlans1(p);
+
+ if (!p->synced)
+ TRACE(D_EVENTS, "Synced");
+
+ p->synced = true;
+}
+
+static void
+kbr_rt_notify(struct proto *P, struct channel *c, net *net, rte *new, rte *old)
+{
+ struct kbr_proto *p = (void *) P;
+ const net_addr *n = net->n.addr;
+
+ /* Before first scan we do not touch the routes */
+ if (!p->synced)
+ return;
+
+ rte *r = (new ?: old);
+ rte *new_gw = (new && ipa_nonzero(new->attrs->nh.gw)) ? new : NULL;
+ rte *old_gw = (old && ipa_nonzero(old->attrs->nh.gw)) ? old : NULL;
+
+ /* For managed interfaces we ignore FDB entries when VLAN not active */
+ /* For now, we assume EVPN FDB entry <-> direct to managed iface */
+#ifdef CONFIG_EVPN
+ if (p->vlan_filtering && (r->attrs->source == RTS_EVPN))
+ {
+ struct iface *i = r->attrs->nh.iface;
+ struct kbr_vlan *v = kbr_find_vlan(p, i->index, net_vlan_id(n));
+
+ if (!v || !v->active)
+ {
+ TRACE(D_ROUTES, "%N %s ignored - vlan %u not active on %s",
+ n, (new ? "update" : "withdraw"), net_vlan_id(n), i->name);
+ return;
+ }
+ }
+#endif
+
+ /*
+ * 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(n)))
+ {
+ /* For BUM routes, we have multiple tunnel entries, but no bridge entry */
+ kbr_trace_out(p, n, new_gw, old_gw, 1);
+ kbr_update_fdb(p, n, new_gw, old_gw, 1);
+ return;
+ }
+
+ /*
+ * or regular routes, we need RA_OPTIMAL, but we are stuck with RA_ANY, so we
+ * need to find out new_best / old_best and replay rt_notify_basic()/
+ */
+ rte *rt_free = NULL;
+ rte *new_best = rte_is_valid(net->routes) ? net->routes : NULL;
+ rte *old_best = rte_find_old_best(net, new, old);
+
+ if (new_best == old_best)
+ return;
+
+ if (new_best)
+ new_best = kbr_export_fdb(p, c, new_best, &rt_free);
+
+ new_gw = (new_best && ipa_nonzero(new_best->attrs->nh.gw)) ? new_best : NULL;
+ old_gw = (old_best && ipa_nonzero(old_best->attrs->nh.gw)) ? old_best : NULL;
+
+ /* For regular routes, we have one bridge entry, perhaps also one tunnel entry */
+ kbr_trace_out(p, n, new_best, old_best, 0);
+ kbr_replace_fdb(p, n, new_best, old_best, 0);
+
+ kbr_trace_out(p, n, new_gw, old_gw, 1);
+ kbr_replace_fdb(p, n, new_gw, old_gw, 1);
+
+ if (rt_free)
+ rte_free(rt_free);
+}
+
+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;
+
+ if (p->ready)
+ tm_start(p->scan_timer, 0);
+}
+
+static void
+kbr_feed_end(struct channel *C)
+{
+ struct kbr_proto *p = (void *) C->proto;
+
+ p->ready = true;
+ 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);
+}
+
+
+/*
+ * Bridge VLANs
+ */
+
+#define VLAN_KEY(v) v->ifi, v->vid
+#define VLAN_NEXT(v) v->next
+#define VLAN_EQ(i1,v1,i2,v2) i1 == i2 && v1 == v2
+#define VLAN_FN(i,v) hash_value(u32_hash0(v, HASH_PARAM, u32_hash0(i, HASH_PARAM, 0)))
+
+#define VLAN_REHASH bridge_vlan_rehash
+#define VLAN_PARAMS /8, *2, 2, 2, 4, 16
+
+
+#define VLAN_VNI_KEY(v) v->ifi, v->vni
+#define VLAN_VNI_NEXT(v) v->next_vni
+#define VLAN_VNI_EQ(i1,v1,i2,v2) i1 == i2 && v1 == v2
+#define VLAN_VNI_FN(i,v) hash_value(u32_hash0(v, HASH_PARAM, u32_hash0(i, HASH_PARAM, 0)))
+
+#define VLAN_VNI_REHASH bridge_vlan_vni_rehash
+#define VLAN_VNI_PARAMS /8, *2, 2, 2, 4, 16
+
+
+HASH_DEFINE_REHASH_FN(VLAN, struct kbr_vlan)
+HASH_DEFINE_REHASH_FN(VLAN_VNI, struct kbr_vlan)
+
+
+static struct kbr_vlan *
+kbr_find_vlan(struct kbr_proto *p, uint ifi, uint vid)
+{
+ return HASH_FIND(p->vlan_hash, VLAN, ifi, vid);
+}
+
+struct kbr_vlan *
+kbr_find_vlan_by_vni(struct kbr_proto *p, uint ifi, uint vni)
+{
+ if (!p->vlan_vni_hash.data)
+ return NULL;
+
+ return HASH_FIND(p->vlan_vni_hash, VLAN_VNI, ifi, vni);
+}
+
+static void
+kbr_vlan_set_vni(struct kbr_proto *p, struct kbr_vlan *v, uint vni)
+{
+ if (v->vni_link && (v->vni == vni))
+ return;
+
+ struct kbr_vlan *old = HASH_DELETE(p->vlan_vni_hash, VLAN_VNI, v->ifi, v->vni);
+ if (old)
+ old->vni_link = false;
+
+ if (v->vni_link)
+ HASH_REMOVE(p->vlan_vni_hash, VLAN_VNI, v);
+
+ v->vni = vni;
+ v->vni_link = true;
+
+ HASH_INSERT2(p->vlan_vni_hash, VLAN_VNI, p->p.pool, v);
+}
+
+static void
+kbr_vlan_unset_vni(struct kbr_proto *p, struct kbr_vlan *v)
+{
+ if (v->vni_link)
+ HASH_REMOVE(p->vlan_vni_hash, VLAN_VNI, v);
+
+ v->vni = 0;
+ v->vni_link = false;
+}
+
+static struct kbr_vlan *
+kbr_get_vlan(struct kbr_proto *p, uint ifi, uint vid)
+{
+ struct kbr_vlan *v = kbr_find_vlan(p, ifi, vid);
+
+ if (v)
+ return v;
+
+ v = mb_allocz(p->p.pool, sizeof(struct kbr_vlan));
+
+ v->ifi = ifi;
+ v->vid = vid;
+
+ HASH_INSERT2(p->vlan_hash, VLAN, p->p.pool, v);
+
+ return v;
+}
+
+void
+kbr_got_vlan(struct kbr_proto *p, struct iface *i, uint vid, uint flags)
+{
+ struct kbr_vlan *v = kbr_get_vlan(p, i->index, vid);
+
+ v->flags = flags;
+ v->mark_vlan = true;
+}
+
+void
+kbr_got_vlan_tunnel(struct kbr_proto *p, struct iface *i, uint vid, uint vni, uint flags UNUSED)
+{
+ struct kbr_vlan *v = kbr_get_vlan(p, i->index, vid);
+
+ v->mark_tunnel = true;
+ kbr_vlan_set_vni(p, v, vni);
+}
+
+static inline void
+kbr_vlan_changed(struct kbr_proto *p)
+{
+ if (p->ready)
+ tm_start(p->scan_timer, 100 MS);
+}
+
+static void
+kbr_vlan_req_update(struct kbr_proto *p, struct iface *i, uintptr_t owner, uint vid, uint vni)
+{
+ struct kbr_vlan *v = kbr_get_vlan(p, i->index, vid);
+
+ if ((v->owner == owner) && (v->vni_req == vni))
+ return;
+
+ v->vni_req = vni;
+ v->owner = owner;
+ kbr_vlan_changed(p);
+
+ /* Register the interface as VLAN-managed */
+ kbr_get_vlan(p, i->index, 0);
+}
+
+static void
+kbr_vlan_req_withdraw(struct kbr_proto *p, struct iface *i, uintptr_t owner, uint vid)
+{
+ struct kbr_vlan *v = kbr_find_vlan(p, i->index, vid);
+
+ if (!v || (v->owner != owner))
+ return;
+
+ v->vni_req = 0;
+ v->owner = 0;
+ kbr_vlan_changed(p);
+}
+
+static void
+kbr_vlan_req_withdraw_all(struct kbr_proto *p, uintptr_t owner)
+{
+ bool changed = false;
+
+ HASH_WALK(p->vlan_hash, next, v)
+ {
+ if (v->owner != owner)
+ continue;
+
+ v->vni_req = 0;
+ v->owner = 0;
+ changed = true;
+ }
+ HASH_WALK_END;
+
+ if (changed)
+ kbr_vlan_changed(p);
+}
+
+static void
+kbr_vlan_req_notify(ps_subscriber *sub, void *msg, uint len)
+{
+ ASSERT(len >= sizeof(struct vlan_request));
+
+ struct kbr_proto *p = sub->data;
+ struct vlan_request *req = msg;
+
+ TRACE(D_EVENTS, "VLAN %s received for %d VLANs on %s",
+ (req->update ? "request" : "withdraw"), (int) req->vlan_count, req->iface->name);
+
+ if (req->update)
+ {
+ for (int i = 0; i < req->vlan_count; i++)
+ kbr_vlan_req_update(p, req->iface, req->owner, req->vlans[i].vid, req->vlans[i].vni);
+ }
+ else if (req->vlan_count > 0)
+ {
+ for (int i = 0; i < req->vlan_count; i++)
+ kbr_vlan_req_withdraw(p, req->iface, req->owner, req->vlans[i].vid);
+ }
+ else
+ kbr_vlan_req_withdraw_all(p, req->owner);
+}
+
+static void
+kbr_prune_vlans0(struct kbr_proto *p)
+{
+ if (!p->vlan_hash.data)
+ return;
+
+ HASH_WALK(p->vlan_hash, next, v)
+ {
+ if (v->owner && (!v->mark_vlan || !v->mark_tunnel || (v->vni_req != v->vni)))
+ {
+ struct iface *ifa = if_find_by_index(v->ifi);
+ if (!ifa) continue;
+
+ TRACE(D_EVENTS, "%s VLAN %u VNI %u on %s",
+ (!v->mark_vlan ? "Adding" : "Updating"), v->vid, v->vni, ifa->name);
+ kbr_update_vlan(ifa, v->vid, true, v->mark_vlan, true, v->mark_tunnel, v->vni_req, v->vni);
+ kbr_vlan_set_vni(p, v, v->vni_req);
+ }
+
+ v->active = !!v->owner;
+ }
+ HASH_WALK_END;
+}
+
+static void
+kbr_prune_vlans1(struct kbr_proto *p)
+{
+ if (!p->vlan_hash.data)
+ return;
+
+ HASH_WALK(p->vlan_hash, next, v)
+ {
+
+ if (!v->owner && (v->mark_vlan || v->mark_tunnel) && kbr_find_vlan(p, v->ifi, 0))
+ {
+ struct iface *ifa = if_find_by_index(v->ifi);
+ if (!ifa) continue;
+
+ TRACE(D_EVENTS, "Removing VLAN %u on %s", v->vid, ifa->name);
+ kbr_update_vlan(ifa, v->vid, false, v->mark_vlan, false, v->mark_tunnel, v->vni_req, v->vni);
+ kbr_vlan_unset_vni(p, v);
+ }
+
+ v->mark_vlan = false;
+ v->mark_tunnel = false;
+ }
+ HASH_WALK_END;
+}
+
+
+/*
+ * Bridge protocol glue
+ */
+
+static bool
+kbr_check_iface(struct kbr_proto *p, const struct iface *i)
+{
+ ea_list *attrs = i->attrs ? i->attrs->eattrs : NULL;
+
+ if (!(i->flags & IF_UP))
+ {
+ log(L_ERR "%s: Interface %s is down", p->p.name, i->name);
+ return false;
+ }
+
+ u32 if_type = ea_get_int(attrs, EA_IFACE_TYPE, IF_TYPE_UNDEF);
+ if (if_type != IF_TYPE_BRIDGE)
+ {
+ log(L_ERR "%s: Interface %s is not a bridge", p->p.name, i->name);
+ return false;
+ }
+
+ u32 if_vlan_filtering = ea_get_int(attrs, EA_IFACE_BRIDGE_VLAN_FILTERING, U32_UNDEF);
+ if (p->vlan_filtering && !if_vlan_filtering)
+ {
+ log(L_WARN "%s: Mismatch in VLAN filtering", p->p.name, i->name);
+ return false;
+ }
+
+ return true;
+}
+
+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->feed_end = kbr_feed_end;
+ 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;
+
+ memset(&p->vlan_hash, 0, sizeof(p->vlan_hash));
+ p->vlan_sub = NULL;
+
+ p->scan_timer = tm_new_init(p->p.pool, kbr_scan, p, cf->scan_time, 0);
+
+ bmap_init(&p->sync_map[0], p->p.pool, 1024);
+ bmap_init(&p->sync_map[1], p->p.pool, 1024);
+ bmap_init(&p->seen_map[0], p->p.pool, 1024);
+ bmap_init(&p->seen_map[1], p->p.pool, 1024);
+
+ if (p->vlan_filtering)
+ {
+ HASH_INIT(p->vlan_hash, p->p.pool, 4);
+ HASH_INIT(p->vlan_vni_hash, p->p.pool, 4);
+
+ p->vlan_sub = ps_subscriber_new(p->p.pool, kbr_vlan_req_notify, p);
+ ps_subscribe_topic(p->vlan_sub, &vlan_requests, p->bridge_dev->name);
+ }
+
+ kbr_check_iface(p, p->bridge_dev);
+
+ kbr_sys_start(p);
+
+ return PS_UP;
+}
+
+static int
+kbr_shutdown(struct proto *P UNUSED)
+{
+ struct kbr_proto *p = (void *) P;
+
+ if (p->synced)
+ kbr_flush_routes(p);
+
+ p->ready = false;
+ p->synced = false;
+
+ 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);
+}
#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)
int scan;
u32 rta_flow;
+ u32 bridge_id;
};
/*
}
static void
-nl_request_dump_link(void)
+nl_request_dump_link(int af, u32 ext_mask)
{
struct {
struct nlmsghdr nh;
struct ifinfomsg ifi;
+ struct rtattr rta;
+ u32 ext_mask;
} req = {
.nh.nlmsg_type = RTM_GETLINK,
.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
.nh.nlmsg_seq = ++(nl_scan.seq),
- .ifi.ifi_family = AF_UNSPEC,
+ .ifi.ifi_family = af,
};
+ if (ext_mask)
+ {
+ req.rta.rta_type = IFLA_EXT_MASK;
+ req.rta.rta_len = RTA_LENGTH(4);
+ req.ext_mask = ext_mask;
+ req.nh.nlmsg_len = NLMSG_ALIGN(req.nh.nlmsg_len) + req.rta.rta_len;
+ }
+
send(nl_scan.fd, &req, sizeof(req), 0);
nl_scan.last_hdr = NULL;
}
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)
};
-#define BIRD_IFLA_MAX (IFLA_LINKINFO+1)
+#define BIRD_IFLA_MAX (IFLA_AF_SPEC+1)
static struct nl_want_attrs ifla_attr_want[BIRD_IFLA_MAX] = {
[IFLA_IFNAME] = { 1, 0, 0 },
[IFLA_MASTER] = { 1, 1, sizeof(u32) },
[IFLA_WIRELESS] = { 1, 0, 0 },
[IFLA_LINKINFO] = { 1, 0, 0 },
+ [IFLA_AF_SPEC] = { 1, 0, 0 },
};
#define BIRD_INFO_MAX (IFLA_INFO_DATA+1)
};
+#define BIRD_IFBRIDGE_MAX (IFLA_BRIDGE_VLAN_TUNNEL_INFO+1)
+
+static struct nl_want_attrs ifbridge_attr_want[BIRD_IFBRIDGE_MAX] UNUSED = {
+ [IFLA_BRIDGE_VLAN_INFO] = { 1, 1, sizeof(struct bridge_vlan_info) },
+ [IFLA_BRIDGE_VLAN_TUNNEL_INFO] = { 1, 0, 0 },
+};
+
+#define BIRD_IFBRIDGE_VLAN_TUNNEL_MAX (IFLA_BRIDGE_VLAN_TUNNEL_FLAGS+1)
+
+static struct nl_want_attrs ifbridge_vlan_tunnel_attr_want[BIRD_IFBRIDGE_VLAN_TUNNEL_MAX] UNUSED = {
+ [IFLA_BRIDGE_VLAN_TUNNEL_ID] = { 1, 1, sizeof(u32) },
+ [IFLA_BRIDGE_VLAN_TUNNEL_VID] = { 1, 1, sizeof(u16) },
+ [IFLA_BRIDGE_VLAN_TUNNEL_FLAGS] = { 1, 1, sizeof(u16) },
+};
+
+
#define BIRD_IFA_MAX (IFA_FLAGS+1)
static struct nl_want_attrs ifa_attr_want4[BIRD_IFA_MAX] = {
#endif
+#define BIRD_NDA_MAX (NDA_SRC_VNI+1)
+
+static struct nl_want_attrs ndm_attr_want[BIRD_NDA_MAX] = {
+ [NDA_DST] = { 1, 0, 0 },
+ [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) },
+ [NDA_SRC_VNI] = { 1, 1, sizeof(u32) },
+};
+
+
static int
nl_parse_attrs(struct rtattr *a, struct nl_want_attrs *want, struct rtattr **k, int ksize)
{
{
if (RTA_PAYLOAD(a) == sizeof(ip4_addr))
return ipa_from_ip4(rta_get_ip4(a));
- else
+ else if (RTA_PAYLOAD(a) == sizeof(ip6_addr))
return ipa_from_ip6(rta_get_ip6(a));
+ else
+ return IPA_NONE;
}
+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);
if_start_update();
- nl_request_dump_link();
+ nl_request_dump_link(AF_UNSPEC, 0);
while (h = nl_get_scan())
if (h->nlmsg_type == RTM_NEWLINK || h->nlmsg_type == RTM_DELLINK)
nl_parse_link(h, 1);
if_end_update();
}
+
/*
* Routes
*/
#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)
}
}
+
+#ifdef CONFIG_BRIDGE
+
+/*
+ * 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]);
+
+ eattr *ea = ea_find(e->attrs->eattrs, EA_MPLS_LABEL);
+ if (ea)
+ nl_add_attr_u32(&r->h, rsize, NDA_SRC_VNI, ea->u.data);
+
+ r->n.ndm_state |= NUD_PERMANENT;
+ }
+
+ /* Ignore missing for DELETE */
+ return nl_exchange(&r->h, (op == NL_OP_DELETE));
+}
+
+void
+kbr_replace_fdb(struct kbr_proto *p, 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 (new)
+ {
+ if (err < 0)
+ bmap_clear(&p->sync_map[tunnel], new->id);
+ else
+ bmap_set(&p->sync_map[tunnel], new->id);
+ }
+
+ if (err < 0)
+ log(L_WARN "NL error %m");
+}
+
+void
+kbr_update_fdb(struct kbr_proto *p, 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)
+ bmap_clear(&p->sync_map[tunnel], new->id);
+ else
+ bmap_set(&p->sync_map[tunnel], new->id);
+ }
+
+ 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])
+ return;
+
+ mac_addr mac = rta_get_mac(a[NDA_LLADDR]);
+ uint vid = a[NDA_VLAN] ? rta_get_u16(a[NDA_VLAN]) : 0;
+
+ net_addr n;
+ net_fill_eth(&n, mac, vid);
+
+ /* Ignore multicast entries */
+ if (!mac_is_unicast(mac))
+ return;
+
+ struct iface *oif = if_find_by_index(nd->ndm_ifindex);
+ if (!oif)
+ return;
+
+ /* NDA_MASTER is not present on 'self' entries */
+ u32 bridge_id = oif->master_index;
+
+ /* Should be filtered by kernel */
+ if (s->bridge_id && (bridge_id != s->bridge_id))
+ return;
+
+ /* When NDA_MASTER is reported, it should be the same */
+ if (a[NDA_MASTER] && (bridge_id != rta_get_u32(a[NDA_MASTER])))
+ return;
+
+ /* Do we know this bridge? */
+ struct kbr_proto *p = HASH_FIND(nl_bridge_map, BRH, bridge_id);
+ if (!p)
+ return;
+
+ rta ra = {
+ .source = RTS_BRIDGE,
+ .scope = SCOPE_UNIVERSE,
+ .dest = RTD_UNICAST,
+ .nh.iface = oif,
+ };
+
+ bool tunnel = false;
+
+ /* FDB entries from port devices */
+ if (nd->ndm_flags & NTF_SELF)
+ {
+ /* FDB entries from VXLAN devices */
+ u32 type = ea_get_int((oif->attrs ? oif->attrs->eattrs : NULL), EA_IFACE_TYPE, IF_TYPE_UNDEF);
+ if (type != IF_TYPE_VXLAN)
+ return;
+
+ if (a[NDA_DST])
+ ra.nh.gw = rta_get_ipa(a[NDA_DST]);
+
+ if (a[NDA_VNI])
+ {
+ ra.nh.labels = 1;
+ ra.nh.label[0] = rta_get_u32(a[NDA_VNI]);
+ }
+
+ if (a[NDA_SRC_VNI])
+ {
+ struct kbr_vlan *vlan = kbr_find_vlan_by_vni(p, oif->index, rta_get_u32(a[NDA_SRC_VNI]));
+ if (!vlan)
+ {
+ log(L_TRACE "%s: FDB %N unkown vni %u", p->p.name, &n, rta_get_u32(a[NDA_SRC_VNI]));
+ return;
+ }
+
+ ((net_addr_eth *) &n)->vid = vid = vlan->vid;
+ }
+
+ tunnel = true;
+ }
+
+ /* Accept VLAN-tagged entries when vlan filtering is enabled */
+ if (!vid != !p->vlan_filtering)
+ return;
+
+ int src;
+ if (nd->ndm_flags & NTF_EXT_LEARNED)
+ src = KBR_SRC_BIRD;
+ 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] %d %d %d\n", (new ? "add" : "del"), &n, oif->name, nd->ndm_state, nd->ndm_flags, nd->ndm_type, (int) src, (int) s->scan, (int) tunnel);
+ kbr_got_fdb(p, &n, e, src, s->scan, tunnel);
+}
+
+void
+kbr_do_fdb_scan(struct kbr_proto *p)
+{
+ struct nl_parse_state s = {
+ .pool = nl_linpool,
+ .scan = 1,
+ .bridge_id = kbr_bridge_id(p),
+ };
+
+ struct nlmsghdr *h;
+
+ nl_request_dump_neigh(AF_BRIDGE, kbr_bridge_id(p));
+ while (h = nl_get_scan())
+ {
+ if (h->nlmsg_type == RTM_NEWNEIGH || h->nlmsg_type == RTM_DELNEIGH)
+ nl_parse_fdb(&s, h);
+ }
+}
+
+
+/*
+ * VLANs
+ */
+
+static int
+nl_send_vlan(struct iface *i, uint vid, uint tid, uint flags, int op, int tunnel)
+{
+ struct {
+ struct nlmsghdr h;
+ struct ifinfomsg i;
+ char buf[0];
+ } *r;
+
+ int rsize = sizeof(*r) + 256;
+ r = alloca(rsize);
+
+ memset(&r->h, 0, sizeof(r->h));
+ memset(&r->i, 0, sizeof(r->i));
+ r->h.nlmsg_type = op ? RTM_SETLINK : RTM_DELLINK;
+ r->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+ r->h.nlmsg_flags = op | NLM_F_REQUEST | NLM_F_ACK;
+
+ r->i.ifi_family = AF_BRIDGE;
+ r->i.ifi_index = i->index;
+
+ struct rtattr *afspec = nl_open_attr(&r->h, rsize, IFLA_AF_SPEC);
+
+ if (!tunnel)
+ {
+ struct bridge_vlan_info vinfo = { .flags = flags, .vid = vid, };
+ nl_add_attr(&r->h, rsize, IFLA_BRIDGE_VLAN_INFO, &vinfo, sizeof(vinfo));
+ }
+ else
+ {
+ struct rtattr *tinfo = nl_open_attr(&r->h, rsize, IFLA_BRIDGE_VLAN_TUNNEL_INFO);
+ nl_add_attr_u32(&r->h, rsize, IFLA_BRIDGE_VLAN_TUNNEL_ID, tid);
+ nl_add_attr_u16(&r->h, rsize, IFLA_BRIDGE_VLAN_TUNNEL_VID, vid);
+ nl_add_attr_u16(&r->h, rsize, IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, flags);
+ nl_close_attr(&r->h, tinfo);
+ }
+
+ nl_close_attr(&r->h, afspec);
+
+ /* Ignore missing for DELETE */
+ return nl_exchange(&r->h, (op == NL_OP_DELETE));
+}
+
+void
+kbr_update_vlan(struct iface *i, uint vid, bool new, bool old, bool new_tunnel, bool old_tunnel, uint new_vni, uint old_vni)
+{
+ int err = 0;
+
+ if ((old && new) && (old_tunnel == new_tunnel) && (old_vni == new_vni))
+ return;
+
+ if (old_tunnel)
+ nl_send_vlan(i, vid, old_vni, 0, NL_OP_DELETE, 1);
+
+ if (old && !new)
+ nl_send_vlan(i, vid, 0, 0, NL_OP_DELETE, 0);
+
+ if (new && !old)
+ err = nl_send_vlan(i, vid, 0, 0, NL_OP_ADD, 0);
+
+ if (new_tunnel && !err)
+ err = nl_send_vlan(i, vid, new_vni, 0, NL_OP_ADD, 1);
+
+ if (err < 0)
+ log(L_WARN "NL error %m");
+}
+
+
+static void
+nl_parse_vlan(struct nl_parse_state *s, struct nlmsghdr *h)
+{
+ struct ifinfomsg *i;
+ struct rtattr *a[BIRD_IFLA_MAX];
+ // int new = (h->nlmsg_type == RTM_NEWLINK);
+
+ if (!(i = nl_checkin(h, sizeof(*i))))
+ return;
+
+ struct iface *oif = if_find_by_index(i->ifi_index);
+ if (!oif)
+ return;
+
+ if (i->ifi_family != AF_BRIDGE)
+ return;
+
+ if (!nl_parse_attrs(IFLA_RTA(i), ifla_attr_want, a, sizeof(a)))
+ return;
+
+ if (!a[IFLA_AF_SPEC] || !a[IFLA_MASTER])
+ return;
+
+ u32 bridge_id = rta_get_u32(a[IFLA_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 VLANs when vlan filtering is enabled */
+ if (!p->vlan_filtering)
+ return;
+
+ struct rtattr *list = a[IFLA_AF_SPEC];
+ uint rem = RTA_PAYLOAD(list);
+
+ /* We have to iterate over rtattrs to handle multiple instances of VLAN info attributes */
+ for (struct rtattr *vi = RTA_DATA(list); RTA_OK(vi, rem); vi = RTA_NEXT(vi, rem))
+ {
+ if (vi->rta_type == IFLA_BRIDGE_VLAN_INFO)
+ {
+ if (RTA_PAYLOAD(vi) != sizeof(struct bridge_vlan_info))
+ {
+ log(L_ERR "nl_parse_attrs: Malformed attribute received");
+ continue;
+ }
+
+ struct bridge_vlan_info *info = RTA_DATA(vi);
+
+ DBG("VLAN %u dev %s flags %x\n", info->vid, oif->name, info->flags);
+ kbr_got_vlan(p, oif, info->vid, info->flags);
+ }
+
+ if (vi->rta_type == IFLA_BRIDGE_VLAN_TUNNEL_INFO)
+ {
+ struct rtattr *a0[BIRD_IFBRIDGE_VLAN_TUNNEL_MAX];
+
+ nl_attr_len = RTA_PAYLOAD(vi);
+ if (!nl_parse_attrs(RTA_DATA(vi), ifbridge_vlan_tunnel_attr_want, a0, sizeof(a0)))
+ continue;
+
+ uint tid = a0[IFLA_BRIDGE_VLAN_TUNNEL_ID] ? rta_get_u32(a0[IFLA_BRIDGE_VLAN_TUNNEL_ID]) : 0;
+ uint vid = a0[IFLA_BRIDGE_VLAN_TUNNEL_VID] ? rta_get_u16(a0[IFLA_BRIDGE_VLAN_TUNNEL_VID]) : 0;
+ uint flags = a0[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS] ? rta_get_u16(a0[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS]) : 0;
+
+ DBG("VLANtun %u dev %s tunnel-id %u flags %x\n", vid, oif->name, tid, flags);
+ kbr_got_vlan_tunnel(p, oif, vid, tid, flags);
+ }
+ }
+}
+
+void
+kbr_do_vlan_scan(struct kbr_proto *p)
+{
+ struct nl_parse_state s = {
+ .pool = nl_linpool,
+ .scan = 1,
+ .bridge_id = kbr_bridge_id(p),
+ };
+
+ struct nlmsghdr *h;
+
+ nl_request_dump_link(AF_BRIDGE, RTEXT_FILTER_BRVLAN);
+ while (h = nl_get_scan())
+ {
+ if (h->nlmsg_type == RTM_NEWLINK || h->nlmsg_type == RTM_DELLINK)
+ nl_parse_vlan(&s, h);
+ }
+}
+
+#else /* CONFIG_BRIDGE */
+
+static inline void nl_parse_fdb(struct nl_parse_state *s UNUSED, struct nlmsghdr *h UNUSED) { }
+
+#endif /* CONFIG_BRIDGE */
+
+
/*
* Asynchronous Netlink interface
*/
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:
+ DBG("KRT: Received async neighbor notification (%d)\n", h->nlmsg_type);
+ nl_parse_fdb(&s, h);
+ break;
+
default:
DBG("KRT: Received unknown async notification (%d)\n", h->nlmsg_type);
}
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;
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);
+
+#ifdef CONFIG_BRIDGE
+ HASH_INIT(nl_bridge_map, krt_pool, 4);
+#endif
}
int
}
+#ifdef CONFIG_BRIDGE
+
+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);
+}
+
+#endif /* CONFIG_BRIDGE */
+
void
kif_sys_start(struct kif_proto *p UNUSED)