summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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);