From: Vincent Bernat Date: Tue, 9 Jul 2019 15:17:42 +0000 (+0200) Subject: netlink: support VLAN-aware bridges X-Git-Tag: 1.0.5~36^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=011056b3a7143458a37d201cba52633d3c7a5dd8;p=thirdparty%2Flldpd.git netlink: support VLAN-aware bridges --- diff --git a/NEWS b/NEWS index 0a27324a..9ce0b94c 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@ lldpd (1.0.5) * Changes: + Interface names are also matched for management addresses. + + Add support for VLAN-aware bridges for Linux (no range support). lldpd (1.0.4) * Changes: diff --git a/src/client/display.c b/src/client/display.c index d0f792df..02be0771 100644 --- a/src/client/display.c +++ b/src/client/display.c @@ -522,14 +522,16 @@ display_vlans(struct writer *w, lldpctl_atom_t *port) lldpctl_atom_foreach(vlans, vlan) { vid = lldpctl_atom_get_int(vlan, lldpctl_k_vlan_id); - if (pvid == vid) - foundpvid = 1; tag_start(w, "vlan", "VLAN"); tag_attr(w, "vlan-id", "", lldpctl_atom_get_str(vlan, lldpctl_k_vlan_id)); - if (pvid == vid) + if (pvid == vid) { tag_attr(w, "pvid", "pvid", "yes"); + foundpvid = 1; + } else { + tag_attr(w, "pvid", "pvid", "no"); + } tag_data(w, lldpctl_atom_get_str(vlan, lldpctl_k_vlan_name)); tag_end(w); diff --git a/src/daemon/interfaces-bsd.c b/src/daemon/interfaces-bsd.c index 341490a5..8ebd1712 100644 --- a/src/daemon/interfaces-bsd.c +++ b/src/daemon/interfaces-bsd.c @@ -307,7 +307,7 @@ ifbsd_check_vlan(struct lldpd *cfg, "%s is VLAN %d of %s", vlan->name, vreq.vlr_tag, lower->name); vlan->lower = lower; - vlan->vlanid = vreq.vlr_tag; + vlan->vlanids[0] = vreq.vlr_tag; vlan->type |= IFACE_VLAN_T; } diff --git a/src/daemon/interfaces-linux.c b/src/daemon/interfaces-linux.c index b57758d3..091f2053 100644 --- a/src/daemon/interfaces-linux.c +++ b/src/daemon/interfaces-linux.c @@ -206,7 +206,7 @@ iflinux_is_vlan(struct lldpd *cfg, } iface->lower = lower; - iface->vlanid = ifv.u.VID; + iface->vlanids[0] = ifv.u.VID; return 1; } #endif diff --git a/src/daemon/interfaces.c b/src/daemon/interfaces.c index f9785f97..e936e766 100644 --- a/src/daemon/interfaces.c +++ b/src/daemon/interfaces.c @@ -211,6 +211,7 @@ iface_append_vlan(struct lldpd *cfg, lldpd_get_hardware(cfg, lower->name, lower->index); struct lldpd_port *port; struct lldpd_vlan *v; + char *name = NULL; if (hardware == NULL) { log_debug("interfaces", @@ -218,24 +219,42 @@ iface_append_vlan(struct lldpd *cfg, lower->name, vlan->name); return; } - - /* Check if the VLAN is already here. */ port = &hardware->h_lport; - TAILQ_FOREACH(v, &port->p_vlans, v_entries) - if (strncmp(vlan->name, v->v_name, IFNAMSIZ) == 0) - return; - if ((v = (struct lldpd_vlan *) - calloc(1, sizeof(struct lldpd_vlan))) == NULL) - return; - if ((v->v_name = strdup(vlan->name)) == NULL) { - free(v); - return; + + for (int i = 0; + i < sizeof(vlan->vlanids)/sizeof(vlan->vlanids[0]) && vlan->vlanids[i] != 0; + i++) { + /* Compute name */ + if (vlan->vlanids[1] == 0) { + /* Only one VLAN. */ + if ((name = strdup(vlan->name)) == NULL) + return; + } else { + if (asprintf(&name, "%s.%d", vlan->name, vlan->vlanids[i]) == -1) + return; + } + + /* Check if the VLAN is already here. */ + TAILQ_FOREACH(v, &port->p_vlans, v_entries) + if (strncmp(name, v->v_name, IFNAMSIZ) == 0) { + free(name); + return; + } + + if ((v = (struct lldpd_vlan *) + calloc(1, sizeof(struct lldpd_vlan))) == NULL) { + free(name); + return; + } + v->v_name = name; + v->v_vid = vlan->vlanids[i]; + if (vlan->pvid) + port->p_pvid = vlan->pvid; + log_debug("interfaces", "append VLAN %s for %s", + v->v_name, + hardware->h_ifname); + TAILQ_INSERT_TAIL(&port->p_vlans, v, v_entries); } - v->v_vid = vlan->vlanid; - log_debug("interfaces", "append VLAN %s for %s", - v->v_name, - hardware->h_ifname); - TAILQ_INSERT_TAIL(&port->p_vlans, v, v_entries); } /** @@ -303,9 +322,7 @@ interfaces_helper_vlan(struct lldpd *cfg, struct interfaces_device *iface; TAILQ_FOREACH(iface, interfaces, next) { - if (iface->ignore) - continue; - if (!(iface->type & IFACE_VLAN_T)) + if (!(iface->type & IFACE_VLAN_T) && iface->vlanids[0] == 0) continue; /* We need to find the physical interfaces of this diff --git a/src/daemon/lldpd.h b/src/daemon/lldpd.h index 12c745ed..ce4c3211 100644 --- a/src/daemon/lldpd.h +++ b/src/daemon/lldpd.h @@ -313,7 +313,8 @@ struct interfaces_device { int flags; /* Flags (IFF_*) */ int mtu; /* MTU */ int type; /* Type (see IFACE_*_T) */ - int vlanid; /* If a VLAN, what is the VLAN ID? */ + int vlanids[10]; /* If a VLAN, what are the VLAN ID? */ + int pvid; /* If a VLAN, what is the default VLAN? */ struct interfaces_device *lower; /* Lower interface (for a VLAN for example) */ struct interfaces_device *upper; /* Upper interface (for a bridge or a bond) */ diff --git a/src/daemon/netlink.c b/src/daemon/netlink.c index c2136877..6d69011d 100644 --- a/src/daemon/netlink.c +++ b/src/daemon/netlink.c @@ -25,12 +25,16 @@ #include #include #include +#include #define NETLINK_BUFFER 4096 struct netlink_req { struct nlmsghdr hdr; - struct rtgenmsg gen; + struct ifinfomsg ifm; + /* attribute has to be NLMSG aligned */ + struct rtattr ext_req __attribute__ ((aligned(NLMSG_ALIGNTO))); + __u32 ext_filter_mask; }; struct lldpd_netlink { @@ -141,12 +145,12 @@ netlink_send(int s, int type, int family, int seq) { struct netlink_req req = { .hdr = { - .nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)), + .nlmsg_len = sizeof(struct netlink_req), .nlmsg_type = type, .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, .nlmsg_seq = seq, .nlmsg_pid = getpid() }, - .gen = { .rtgen_family = family } + .ifm = { .ifi_family = family } }; struct iovec iov = { .iov_base = &req, @@ -160,6 +164,13 @@ netlink_send(int s, int type, int family, int seq) .msg_namelen = sizeof(struct sockaddr_nl) }; + if (family == AF_BRIDGE) { + /* request bridge vlan attributes */ + req.ext_req.rta_type = IFLA_EXT_MASK; + req.ext_req.rta_len = RTA_LENGTH(sizeof(__u32)); + req.ext_filter_mask = RTEXT_FILTER_BRVLAN; + } + /* Send netlink message. This is synchronous but we are guaranteed * to not block. */ log_debug("netlink", "sending netlink message"); @@ -226,15 +237,57 @@ netlink_parse_linkinfo(struct interfaces_device *iff, struct rtattr *rta, int le RTA_PAYLOAD(link_info_attrs[IFLA_INFO_DATA])); if (vlan_link_info_data_attrs[IFLA_VLAN_ID]) { - iff->vlanid = *(uint16_t *)RTA_DATA(vlan_link_info_data_attrs[IFLA_VLAN_ID]); + iff->vlanids[0] = *(uint16_t *)RTA_DATA(vlan_link_info_data_attrs[IFLA_VLAN_ID]); log_debug("netlink", "VLAN ID for interface %s is %d", - iff->name, iff->vlanid); + iff->name, iff->vlanids[0]); } } free(kind); } +/** + * Parse a `afspec` attributes. + * + * @param iff where to put the result + * @param rta afspec attribute + * @param len length of attributes + */ +static void +netlink_parse_afspec(struct interfaces_device *iff, struct rtattr *rta, int len) +{ + int i = 0; + while (RTA_OK(rta, len)) { + struct bridge_vlan_info *vinfo; + switch (rta->rta_type) { + case IFLA_BRIDGE_VLAN_INFO: + vinfo = RTA_DATA(rta); + log_debug("netlink", "found VLAN %d on interface %s", + vinfo->vid, iff->name ? iff->name : "(unknown)"); + if (i >= sizeof(iff->vlanids)/sizeof(iff->vlanids[0])) { + log_info("netlink", "too many VLANs on interface %s", + iff->name ? iff->name : "(unknown)"); + break; + } + iff->vlanids[i++] = vinfo->vid; + if (vinfo->flags & (BRIDGE_VLAN_INFO_PVID | BRIDGE_VLAN_INFO_UNTAGGED)) + iff->pvid = vinfo->vid; + break; + default: + log_debug("netlink", "unknown afspec attribute type %d for iface %s", + rta->rta_type, iff->name ? iff->name : "(unknown)"); + break; + } + rta = RTA_NEXT(rta, len); + } + /* All enbridged interfaces will have VLAN 1 by default, ignore it */ + if (iff->vlanids[0] == 1 && iff->vlanids[1] == 0 && iff->pvid == 1) { + log_debug("netlink", "found only default VLAN 1 on interface %s, removing", + iff->name ? iff->name : "(unknown)"); + iff->vlanids[0] = iff->pvid = 0; + } +} + /** * Parse a `link` netlink message. * @@ -309,6 +362,10 @@ netlink_parse_link(struct nlmsghdr *msg, case IFLA_LINKINFO: netlink_parse_linkinfo(iff, RTA_DATA(attribute), RTA_PAYLOAD(attribute)); break; + case IFLA_AF_SPEC: + if (ifi->ifi_family != AF_BRIDGE) break; + netlink_parse_afspec(iff, RTA_DATA(attribute), RTA_PAYLOAD(attribute)); + break; default: log_debug("netlink", "unhandled link attribute type %d for iface %s", attribute->rta_type, iff->name ? iff->name : "(unknown)"); @@ -334,10 +391,6 @@ netlink_parse_link(struct nlmsghdr *msg, iff->name, iff->upper_idx); msg->nlmsg_type = RTM_NEWLINK; iff->upper_idx = -1; - } else if (ifi->ifi_family != 0) { - log_debug("netlink", "skip non-generic message update %d at index %d", - ifi->ifi_type, ifi->ifi_index); - return -1; } log_debug("netlink", "parsed link %d (%s, flags: %d)", @@ -430,8 +483,8 @@ netlink_merge(struct interfaces_device *old, struct interfaces_device *new) new->mtu = old->mtu; if (new->type == 0) new->type = old->type; - if (new->vlanid == 0) - new->vlanid = old->vlanid; + if (new->vlanids[0] == 0 && new->type == IFACE_VLAN_T) + new->vlanids[0] = old->vlanids[0]; /* It's not possible for lower link to change */ new->lower_idx = old->lower_idx; @@ -769,6 +822,10 @@ netlink_change_cb(struct lldpd *cfg) static int netlink_initialize(struct lldpd *cfg) { +#ifdef ENABLE_DOT1 + struct interfaces_device *iff; +#endif + if (cfg->g_netlink) return 0; log_debug("netlink", "initialize netlink subsystem"); @@ -804,6 +861,18 @@ netlink_initialize(struct lldpd *cfg) if (netlink_send(cfg->g_netlink->nl_socket, RTM_GETLINK, AF_PACKET, 2) == -1) goto end; netlink_recv(cfg, ifs, NULL); +#ifdef ENABLE_DOT1 + /* If we have a bridge, search for VLAN-aware bridges */ + TAILQ_FOREACH(iff, ifs, next) { + if (iff->type & IFACE_BRIDGE_T) { + log_debug("netlink", "interface %s is a bridge, check for VLANs", iff->name); + if (netlink_send(cfg->g_netlink->nl_socket, RTM_GETLINK, AF_BRIDGE, 3) == -1) + goto end; + netlink_recv(cfg, ifs, NULL); + break; + } + } +#endif /* Listen to any future change */ cfg->g_iface_cb = netlink_change_cb; diff --git a/src/lldpd-structs.c b/src/lldpd-structs.c index 95cef9f1..f9db9ece 100644 --- a/src/lldpd-structs.c +++ b/src/lldpd-structs.c @@ -73,6 +73,7 @@ lldpd_vlan_cleanup(struct lldpd_port *port) free(vlan); } TAILQ_INIT(&port->p_vlans); + port->p_pvid = 0; } void diff --git a/tests/integration/fixtures/network.py b/tests/integration/fixtures/network.py index 64785e88..f867ea5a 100644 --- a/tests/integration/fixtures/network.py +++ b/tests/integration/fixtures/network.py @@ -129,6 +129,20 @@ class LinksFactory(object): ipr.link('set', index=idx, state='up') return idx + def bridge_vlan(self, iface, vid, tagged=True, pvid=False, remove=False): + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname=iface)[0] + flags = [] + if not tagged: + flags.append('untagged') + if pvid: + flags.append('pvid') + if not remove: + ipr.vlan_filter('del', index=idx, vlan_info={"vid": 1}) + ipr.vlan_filter('add' if not remove else 'del', index=idx, + vlan_info={'vid': vid, + 'flags': flags}) + def up(self, name): ipr = pyroute2.IPRoute() idx = ipr.link_lookup(ifname=name)[0] diff --git a/tests/integration/test_interfaces.py b/tests/integration/test_interfaces.py index 77fece6d..5247fd11 100644 --- a/tests/integration/test_interfaces.py +++ b/tests/integration/test_interfaces.py @@ -62,6 +62,34 @@ def test_bridge_with_vlan(lldpd1, lldpd, lldpcli, namespaces, links, when): ['100', '200', '300'] +@pytest.mark.skipif("'Dot1' not in config.lldpd.features", + reason="Dot1 not supported") +@pytest.mark.parametrize('when', ['before', 'after']) +def test_vlan_aware_bridge_with_vlan(lldpd1, lldpd, lldpcli, namespaces, links, + when): + links(namespaces(3), namespaces(2)) # Another link to setup a bridge + with namespaces(2): + if when == 'after': + lldpd() + links.bridge('br42', 'eth1', 'eth3') + links.bridge_vlan('eth1', 100, pvid=True) + links.bridge_vlan('eth1', 200) + links.bridge_vlan('eth1', 300) + if when == 'before': + lldpd() + else: + time.sleep(6) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert out['lldp.eth0.vlan'] == \ + ['eth1.100', 'eth1.200', 'eth1.300'] + assert out['lldp.eth0.vlan.vlan-id'] == \ + ['100', '200', '300'] + assert out['lldp.eth0.vlan.pvid'] == \ + ['yes', 'no', 'no'] + + @pytest.mark.skipif("'Dot3' not in config.lldpd.features", reason="Dot3 not supported") @pytest.mark.parametrize('when', ['before', 'after']) @@ -159,29 +187,40 @@ def test_just_vlan(lldpd1, lldpd, lldpcli, namespaces, links, when): @pytest.mark.skipif("'Dot1' not in config.lldpd.features", reason="Dot1 not supported") -@pytest.mark.parametrize('kind', ['plain', 'bridge', 'bond']) +@pytest.mark.parametrize('kind', ['plain', 'bridge', 'vlan-aware-bridge', 'bond']) def test_remove_vlan(lldpd1, lldpd, lldpcli, namespaces, links, kind): with namespaces(2): if kind == 'bond': iface = 'bond42' links.bond(iface, 'eth1') - elif kind == 'bridge': + elif kind in ('bridge', 'vlan-aware-bridge'): iface = 'bridge42' links.bridge(iface, 'eth1') else: assert kind == 'plain' iface = 'eth1' - links.vlan('vlan300', 300, iface) - links.vlan('vlan400', 400, iface) - links.vlan('vlan500', 500, iface) - lldpd() - links.remove('vlan300') + if kind != 'vlan-aware-bridge': + links.vlan('vlan300', 300, iface) + links.vlan('vlan400', 400, iface) + links.vlan('vlan500', 500, iface) + lldpd() + links.remove('vlan300') + else: + links.bridge_vlan('eth1', 300, pvid=True) + links.bridge_vlan('eth1', 400) + links.bridge_vlan('eth1', 500) + lldpd() + links.bridge_vlan('eth1', 300, remove=True) time.sleep(6) with namespaces(1): out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") assert out['lldp.eth0.port.descr'] == 'eth1' - assert out['lldp.eth0.vlan'] == ['vlan400', 'vlan500'] + if kind != 'vlan-aware-bridge': + assert out['lldp.eth0.vlan'] == ['vlan400', 'vlan500'] + else: + assert out['lldp.eth0.vlan'] == ['eth1.400', 'eth1.500'] assert out['lldp.eth0.vlan.vlan-id'] == ['400', '500'] + assert out['lldp.eth0.vlan.pvid'] == ['no', 'no'] @pytest.mark.skipif("'Dot3' not in config.lldpd.features",