diff options
-rw-r--r-- | bridge.c | 27 | ||||
-rw-r--r-- | device.h | 6 | ||||
-rw-r--r-- | system-dummy.c | 5 | ||||
-rw-r--r-- | system-linux.c | 191 | ||||
-rw-r--r-- | system.h | 1 |
5 files changed, 225 insertions, 5 deletions
@@ -122,17 +122,13 @@ struct bridge_member { struct vlist_node node; struct bridge_state *bst; struct device_user dev; + struct uloop_timeout check_timer; uint16_t pvid; bool present; bool active; char name[]; }; -struct bridge_vlan_hotplug_port { - struct list_head list; - struct bridge_vlan_port port; -}; - static void bridge_reset_primary(struct bridge_state *bst) { @@ -477,6 +473,7 @@ restart: device_lock(); device_remove_user(&bm->dev); + uloop_timeout_cancel(&bm->check_timer); /* * When reloading the config and moving a device from one bridge to @@ -505,6 +502,22 @@ bridge_check_retry(struct bridge_state *bst) } static void +bridge_member_check_cb(struct uloop_timeout *t) +{ + struct bridge_member *bm; + struct bridge_state *bst; + + bm = container_of(t, struct bridge_member, check_timer); + bst = bm->bst; + + if (!system_bridge_vlan_check(&bst->dev, bm->dev.dev->ifname)) + return; + + bridge_disable_member(bm, true); + bridge_enable_member(bm); +} + +static void bridge_member_cb(struct device_user *dep, enum device_event ev) { struct bridge_member *bm = container_of(dep, struct bridge_member, dev); @@ -521,6 +534,9 @@ bridge_member_cb(struct device_user *dep, enum device_event ev) if (bst->n_present == 1) device_set_present(&bst->dev, true); fallthrough; + case DEV_EVENT_LINK_UP: + uloop_timeout_set(&bm->check_timer, 1000); + break; case DEV_EVENT_AUTH_UP: if (!bst->dev.active) break; @@ -634,6 +650,7 @@ bridge_create_member(struct bridge_state *bst, const char *name, bm->bst = bst; bm->dev.cb = bridge_member_cb; bm->dev.hotplug = hotplug; + bm->check_timer.cb = bridge_member_check_cb; strcpy(bm->name, name); bm->dev.dev = dev; vlist_add(&bst->members, &bm->node, bm->name); @@ -266,6 +266,12 @@ enum bridge_vlan_flags { struct bridge_vlan_port { const char *ifname; uint16_t flags; + int8_t check; +}; + +struct bridge_vlan_hotplug_port { + struct list_head list; + struct bridge_vlan_port port; }; struct bridge_vlan { diff --git a/system-dummy.c b/system-dummy.c index 6bf0f8b..b6b0050 100644 --- a/system-dummy.c +++ b/system-dummy.c @@ -66,6 +66,11 @@ int system_bridge_vlan(const char *iface, uint16_t vid, bool add, unsigned int v return 0; } +int system_bridge_vlan_check(struct device *dev, char *ifname) +{ + return 0; +} + int system_link_netns_move(struct device *dev, int netns_fd, const char *target_ifname) { D(SYSTEM, "ip link set %s name %s netns %d\n", dev->ifname, target_ifname, netns_fd); diff --git a/system-linux.c b/system-linux.c index d914a20..5ea9558 100644 --- a/system-linux.c +++ b/system-linux.c @@ -1858,6 +1858,197 @@ static int cb_if_check_error(struct sockaddr_nl *nla, struct nlmsgerr *err, void return NL_STOP; } +struct bridge_vlan_check_data { + struct device *check_dev; + int ifindex; + int ret; + bool pending; +}; + +static void bridge_vlan_check_port(struct bridge_vlan_check_data *data, + struct bridge_vlan_port *port, + struct bridge_vlan_info *vinfo) +{ + uint16_t flags = 0, diff, mask; + + if (port->flags & BRVLAN_F_PVID) + flags |= BRIDGE_VLAN_INFO_PVID; + if (port->flags & BRVLAN_F_UNTAGGED) + flags |= BRIDGE_VLAN_INFO_UNTAGGED; + + diff = vinfo->flags ^ flags; + mask = BRVLAN_F_UNTAGGED | (flags & BRIDGE_VLAN_INFO_PVID); + if (diff & mask) { + data->ret = 1; + data->pending = false; + } + + port->check = 1; +} + +static void bridge_vlan_check_attr(struct bridge_vlan_check_data *data, + struct rtattr *attr) +{ + struct bridge_vlan_hotplug_port *port; + struct bridge_vlan_info *vinfo; + struct bridge_vlan *vlan; + struct rtattr *cur; + int rem = RTA_PAYLOAD(attr); + int i; + + for (cur = RTA_DATA(attr); RTA_OK(cur, rem); cur = RTA_NEXT(cur, rem)) { + if (cur->rta_type != IFLA_BRIDGE_VLAN_INFO) + continue; + + vinfo = RTA_DATA(cur); + vlan = vlist_find(&data->check_dev->vlans, &vinfo->vid, vlan, node); + if (!vlan) { + data->ret = 1; + data->pending = false; + return; + } + + for (i = 0; i < vlan->n_ports; i++) + if (!vlan->ports[i].check) + bridge_vlan_check_port(data, &vlan->ports[i], vinfo); + + list_for_each_entry(port, &vlan->hotplug_ports, list) + if (!port->port.check) + bridge_vlan_check_port(data, &port->port, vinfo); + } +} + +static int bridge_vlan_check_cb(struct nl_msg *msg, void *arg) +{ + struct bridge_vlan_check_data *data = arg; + struct nlmsghdr *nh = nlmsg_hdr(msg); + struct ifinfomsg *ifi = NLMSG_DATA(nh); + struct rtattr *attr; + int rem; + + if (nh->nlmsg_type != RTM_NEWLINK) + return NL_SKIP; + + if (ifi->ifi_family != AF_BRIDGE) + return NL_SKIP; + + if (ifi->ifi_index != data->ifindex) + return NL_SKIP; + + attr = IFLA_RTA(ifi); + rem = nh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi)); + while (RTA_OK(attr, rem)) { + if (attr->rta_type == IFLA_AF_SPEC) + bridge_vlan_check_attr(data, attr); + + attr = RTA_NEXT(attr, rem); + } + + return NL_SKIP; +} + +static int bridge_vlan_ack_cb(struct nl_msg *msg, void *arg) +{ + struct bridge_vlan_check_data *data = arg; + data->pending = false; + return NL_STOP; +} + +static int bridge_vlan_error_cb(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) +{ + struct bridge_vlan_check_data *data = arg; + data->pending = false; + return NL_STOP; +} + +int system_bridge_vlan_check(struct device *dev, char *ifname) +{ + struct bridge_vlan_check_data data = { + .check_dev = dev, + .ifindex = if_nametoindex(ifname), + .ret = -1, + .pending = true, + }; + static struct ifinfomsg ifi = { + .ifi_family = AF_BRIDGE + }; + static struct rtattr ext_req = { + .rta_type = IFLA_EXT_MASK, + .rta_len = RTA_LENGTH(sizeof(uint32_t)), + }; + uint32_t filter = RTEXT_FILTER_BRVLAN; + struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT); + struct bridge_vlan *vlan; + struct nl_msg *msg; + int i; + + if (!data.ifindex) + return 0; + + msg = nlmsg_alloc_simple(RTM_GETLINK, NLM_F_DUMP); + + if (nlmsg_append(msg, &ifi, sizeof(ifi), 0) || + nlmsg_append(msg, &ext_req, sizeof(ext_req), NLMSG_ALIGNTO) || + nlmsg_append(msg, &filter, sizeof(filter), 0)) + goto free; + + vlist_for_each_element(&dev->vlans, vlan, node) { + struct bridge_vlan_hotplug_port *port; + + for (i = 0; i < vlan->n_ports; i++) { + if (!strcmp(vlan->ports[i].ifname, ifname)) + vlan->ports[i].check = 0; + else + vlan->ports[i].check = -1; + } + + list_for_each_entry(port, &vlan->hotplug_ports, list) { + if (!strcmp(port->port.ifname, ifname)) + port->port.check = 0; + else + port->port.check = -1; + } + } + + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, bridge_vlan_check_cb, &data); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, bridge_vlan_ack_cb, &data); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, bridge_vlan_ack_cb, &data); + nl_cb_err(cb, NL_CB_CUSTOM, bridge_vlan_error_cb, &data); + + if (nl_send_auto_complete(sock_rtnl, msg) < 0) + goto free; + + data.ret = 0; + while (data.pending) + nl_recvmsgs(sock_rtnl, cb); + + vlist_for_each_element(&dev->vlans, vlan, node) { + struct bridge_vlan_hotplug_port *port; + + for (i = 0; i < vlan->n_ports; i++) { + if (!vlan->ports[i].check) { + data.ret = 1; + break; + } + } + + list_for_each_entry(port, &vlan->hotplug_ports, list) { + if (!port->port.check) { + data.ret = 1; + break; + } + } + } + + goto out; + +free: + nlmsg_free(msg); +out: + nl_cb_put(cb); + return data.ret; +} + int system_if_check(struct device *dev) { struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT); @@ -207,6 +207,7 @@ int system_bridge_delbr(struct device *bridge); int system_bridge_addif(struct device *bridge, struct device *dev); int system_bridge_delif(struct device *bridge, struct device *dev); int system_bridge_vlan(const char *iface, uint16_t vid, bool add, unsigned int vflags); +int system_bridge_vlan_check(struct device *dev, char *ifname); int system_macvlan_add(struct device *macvlan, struct device *dev, struct macvlan_config *cfg); int system_macvlan_del(struct device *macvlan); |