summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Fietkau <nbd@nbd.name>2021-07-23 11:04:45 +0200
committerFelix Fietkau <nbd@nbd.name>2021-07-23 11:04:47 +0200
commit85f01c44a950be8518ce5a7d251b5bba219348cf (patch)
tree5b415838ce2606394aee5a37e61728a8290117ff
parent7f24a063475e1e2be4e0c516a5b62c3fae5ec542 (diff)
downloadnetifd-85f01c44a950be8518ce5a7d251b5bba219348cf.tar.gz
bridge: check bridge port vlan membership on link-up events
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>
-rw-r--r--bridge.c27
-rw-r--r--device.h6
-rw-r--r--system-dummy.c5
-rw-r--r--system-linux.c191
-rw-r--r--system.h1
5 files changed, 225 insertions, 5 deletions
diff --git a/bridge.c b/bridge.c
index 6c8e79a..32796bf 100644
--- 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
@@ -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);
diff --git a/device.h b/device.h
index 1da6e3f..275deb9 100644
--- 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 {
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);
diff --git a/system.h b/system.h
index 52161a8..d373b66 100644
--- 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);