bridge: check bridge port vlan membership on link-up events
authorFelix Fietkau <nbd@nbd.name>
Fri, 23 Jul 2021 09:04:45 +0000 (11:04 +0200)
committerFelix Fietkau <nbd@nbd.name>
Fri, 23 Jul 2021 09:04:47 +0000 (11:04 +0200)
When changing to a dfs channel, hostapd can bring down wlan interfaces and
reset their bridge membership. If that happens, the port loses its vlan
membership settings and needs to be reconfigured by netifd.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
bridge.c
device.h
system-dummy.c
system-linux.c
system.h

index 6c8e79a47399ca88bdb7fa17f6a0fd0d7c008c9a..32796bf9ebb070f9c1c463cc8d427b8078a4cd25 100644 (file)
--- a/bridge.c
+++ b/bridge.c
@@ -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
@@ -504,6 +501,22 @@ bridge_check_retry(struct bridge_state *bst)
        uloop_timeout_set(&bst->retry, 100);
 }
 
+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)
 {
@@ -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);
index 1da6e3ff866038a5e15d9623ce445d61dd0265cb..275deb96f94aaca2b0a1891923690d0905413d98 100644 (file)
--- a/device.h
+++ b/device.h
@@ -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 {
index 6bf0f8b31fdd25c1a6ed9f273f4afb52cdf0c573..b6b00508d70d34bea6764768f1972699ad1a1c86 100644 (file)
@@ -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);
index d914a20da10a6fbb48a0459e1d8d619a6b668855..5ea95582cb006da0d80069038aa887b2810482fe 100644 (file)
@@ -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);
index 52161a8f573d6e254f80cc032d9deebca2e2a160..d373b66805d84beb546c1c733330fcae8eac86d2 100644 (file)
--- a/system.h
+++ b/system.h
@@ -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);