Commit 9aba624d authored by Nikolay Aleksandrov's avatar Nikolay Aleksandrov Committed by David S. Miller
Browse files

net: bridge: vlan: notify when global options change



Add support for global options notifications. They use only RTM_NEWVLAN
since global options can only be set and are contained in a separate
vlan global options attribute. Notifications are compressed in ranges
where possible, i.e. the sequential vlan options are equal.

Signed-off-by: default avatarNikolay Aleksandrov <nikolay@nvidia.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 743a53d9
Loading
Loading
Loading
Loading
+79 −1
Original line number Diff line number Diff line
@@ -290,6 +290,57 @@ bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
	return false;
}

static size_t rtnl_vlan_global_opts_nlmsg_size(void)
{
	return NLMSG_ALIGN(sizeof(struct br_vlan_msg))
		+ nla_total_size(0) /* BRIDGE_VLANDB_GLOBAL_OPTIONS */
		+ nla_total_size(sizeof(u16)) /* BRIDGE_VLANDB_GOPTS_ID */
		+ nla_total_size(sizeof(u16)); /* BRIDGE_VLANDB_GOPTS_RANGE */
}

static void br_vlan_global_opts_notify(const struct net_bridge *br,
				       u16 vid, u16 vid_range)
{
	struct net_bridge_vlan *v;
	struct br_vlan_msg *bvm;
	struct nlmsghdr *nlh;
	struct sk_buff *skb;
	int err = -ENOBUFS;

	/* right now notifications are done only with rtnl held */
	ASSERT_RTNL();

	skb = nlmsg_new(rtnl_vlan_global_opts_nlmsg_size(), GFP_KERNEL);
	if (!skb)
		goto out_err;

	err = -EMSGSIZE;
	nlh = nlmsg_put(skb, 0, 0, RTM_NEWVLAN, sizeof(*bvm), 0);
	if (!nlh)
		goto out_err;
	bvm = nlmsg_data(nlh);
	memset(bvm, 0, sizeof(*bvm));
	bvm->family = AF_BRIDGE;
	bvm->ifindex = br->dev->ifindex;

	/* need to find the vlan due to flags/options */
	v = br_vlan_find(br_vlan_group(br), vid);
	if (!v)
		goto out_kfree;

	if (!br_vlan_global_opts_fill(skb, vid, vid_range, v))
		goto out_err;

	nlmsg_end(skb, nlh);
	rtnl_notify(skb, dev_net(br->dev), 0, RTNLGRP_BRVLAN, NULL, GFP_KERNEL);
	return;

out_err:
	rtnl_set_sk_err(dev_net(br->dev), RTNLGRP_BRVLAN, err);
out_kfree:
	kfree_skb(skb);
}

static int br_vlan_process_global_one_opts(const struct net_bridge *br,
					   struct net_bridge_vlan_group *vg,
					   struct net_bridge_vlan *v,
@@ -311,9 +362,9 @@ int br_vlan_rtm_process_global_options(struct net_device *dev,
				       int cmd,
				       struct netlink_ext_ack *extack)
{
	struct net_bridge_vlan *v, *curr_start = NULL, *curr_end = NULL;
	struct nlattr *tb[BRIDGE_VLANDB_GOPTS_MAX + 1];
	struct net_bridge_vlan_group *vg;
	struct net_bridge_vlan *v;
	u16 vid, vid_range = 0;
	struct net_bridge *br;
	int err = 0;
@@ -370,7 +421,34 @@ int br_vlan_rtm_process_global_options(struct net_device *dev,
						      extack);
		if (err)
			break;

		if (changed) {
			/* vlan options changed, check for range */
			if (!curr_start) {
				curr_start = v;
				curr_end = v;
				continue;
			}

			if (!br_vlan_global_opts_can_enter_range(v, curr_end)) {
				br_vlan_global_opts_notify(br, curr_start->vid,
							   curr_end->vid);
				curr_start = v;
			}
			curr_end = v;
		} else {
			/* nothing changed and nothing to notify yet */
			if (!curr_start)
				continue;

			br_vlan_global_opts_notify(br, curr_start->vid,
						   curr_end->vid);
			curr_start = NULL;
			curr_end = NULL;
		}
	}
	if (curr_start)
		br_vlan_global_opts_notify(br, curr_start->vid, curr_end->vid);

	return err;
}