]> git.ipfire.org Git - thirdparty/lldpd.git/commitdiff
netlink: support VLAN-aware bridges feature/vlan-enabled-bridge 353/head
authorVincent Bernat <vincent@bernat.ch>
Tue, 9 Jul 2019 15:17:42 +0000 (17:17 +0200)
committerVincent Bernat <vincent@bernat.ch>
Tue, 24 Sep 2019 06:16:03 +0000 (08:16 +0200)
NEWS
src/client/display.c
src/daemon/interfaces-bsd.c
src/daemon/interfaces-linux.c
src/daemon/interfaces.c
src/daemon/lldpd.h
src/daemon/netlink.c
src/lldpd-structs.c
tests/integration/fixtures/network.py
tests/integration/test_interfaces.py

diff --git a/NEWS b/NEWS
index 0a27324a5b6c46d87dcd3473ceb36f54b7d06062..9ce0b94c2b7de9001151f789036773575fbf5770 100644 (file)
--- 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:
index d0f792df6d4179004e6b0848ebfac5d5b3ed75bb..02be07714ad71964de21b6d8892a00172b0088ec 100644 (file)
@@ -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);
index 341490a5ab6031a79d3491d205c3501faede2572..8ebd17122014fb3a8bf3f49d3f174970b52a9a17 100644 (file)
@@ -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;
 }
 
index b57758d37191ec4e49b3da59eb215b2b49984c9c..091f2053768670aaf9fb1d4ec71af3dd4fcabfe1 100644 (file)
@@ -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
index f9785f97217b7c41ac26d50af02c28f936d24739..e936e766f7216d46bd9d717ce2f2df786b23b230 100644 (file)
@@ -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
index 12c745edd19547a8607337456cad266d66a8b95a..ce4c321124088458e5e557d7571cd9bd296d8b93 100644 (file)
@@ -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) */
 
index c21368779b09b281e90b970ed15b0a5a13442ed9..6d69011d1d70844be836070d031497af023dfcd5 100644 (file)
 #include <net/if_arp.h>
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
+#include <linux/if_bridge.h>
 
 #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;
index 95cef9f11a1169f6310e7fc1f9cba7fdfd9beb11..f9db9ece2743519f890c5a03f43d6b390d187a55 100644 (file)
@@ -73,6 +73,7 @@ lldpd_vlan_cleanup(struct lldpd_port *port)
                free(vlan);
        }
        TAILQ_INIT(&port->p_vlans);
+       port->p_pvid = 0;
 }
 
 void
index 64785e88b063c1de31cb1901bbde293f2c21e6a4..f867ea5a843b3d66c6cb130762ed41d8b0dac691 100644 (file)
@@ -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]
index 77fece6d54f2f9b504d1b77e06da536b3f81dec7..5247fd11da7094e3b694c41180759e11c1f255df 100644 (file)
@@ -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",