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:
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);
"%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;
}
}
iface->lower = lower;
- iface->vlanid = ifv.u.VID;
+ iface->vlanids[0] = ifv.u.VID;
return 1;
}
#endif
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",
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);
}
/**
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
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) */
#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 {
{
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,
.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");
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.
*
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)");
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)",
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;
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");
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;
free(vlan);
}
TAILQ_INIT(&port->p_vlans);
+ port->p_pvid = 0;
}
void
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]
['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'])
@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",