|
@@ -2295,13 +2295,60 @@ static int rtnl_bridge_getlink(struct sk_buff *skb, struct netlink_callback *cb)
|
|
|
return skb->len;
|
|
|
}
|
|
|
|
|
|
+static inline size_t bridge_nlmsg_size(void)
|
|
|
+{
|
|
|
+ return NLMSG_ALIGN(sizeof(struct ifinfomsg))
|
|
|
+ + nla_total_size(IFNAMSIZ) /* IFLA_IFNAME */
|
|
|
+ + nla_total_size(MAX_ADDR_LEN) /* IFLA_ADDRESS */
|
|
|
+ + nla_total_size(sizeof(u32)) /* IFLA_MASTER */
|
|
|
+ + nla_total_size(sizeof(u32)) /* IFLA_MTU */
|
|
|
+ + nla_total_size(sizeof(u32)) /* IFLA_LINK */
|
|
|
+ + nla_total_size(sizeof(u32)) /* IFLA_OPERSTATE */
|
|
|
+ + nla_total_size(sizeof(u8)) /* IFLA_PROTINFO */
|
|
|
+ + nla_total_size(sizeof(struct nlattr)) /* IFLA_AF_SPEC */
|
|
|
+ + nla_total_size(sizeof(u16)) /* IFLA_BRIDGE_FLAGS */
|
|
|
+ + nla_total_size(sizeof(u16)); /* IFLA_BRIDGE_MODE */
|
|
|
+}
|
|
|
+
|
|
|
+static int rtnl_bridge_notify(struct net_device *dev, u16 flags)
|
|
|
+{
|
|
|
+ struct net *net = dev_net(dev);
|
|
|
+ struct net_device *master = dev->master;
|
|
|
+ struct sk_buff *skb;
|
|
|
+ int err = -EOPNOTSUPP;
|
|
|
+
|
|
|
+ skb = nlmsg_new(bridge_nlmsg_size(), GFP_ATOMIC);
|
|
|
+ if (!skb) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto errout;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!flags && master && master->netdev_ops->ndo_bridge_getlink)
|
|
|
+ err = master->netdev_ops->ndo_bridge_getlink(skb, 0, 0, dev);
|
|
|
+ else if (dev->netdev_ops->ndo_bridge_getlink)
|
|
|
+ err = dev->netdev_ops->ndo_bridge_getlink(skb, 0, 0, dev);
|
|
|
+
|
|
|
+ if (err < 0)
|
|
|
+ goto errout;
|
|
|
+
|
|
|
+ rtnl_notify(skb, net, 0, RTNLGRP_LINK, NULL, GFP_ATOMIC);
|
|
|
+ return 0;
|
|
|
+errout:
|
|
|
+ WARN_ON(err == -EMSGSIZE);
|
|
|
+ kfree_skb(skb);
|
|
|
+ rtnl_set_sk_err(net, RTNLGRP_LINK, err);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
static int rtnl_bridge_setlink(struct sk_buff *skb, struct nlmsghdr *nlh,
|
|
|
void *arg)
|
|
|
{
|
|
|
struct net *net = sock_net(skb->sk);
|
|
|
struct ifinfomsg *ifm;
|
|
|
struct net_device *dev;
|
|
|
- int err = -EINVAL;
|
|
|
+ struct nlattr *br_spec, *attr = NULL;
|
|
|
+ int rem, err = -EOPNOTSUPP;
|
|
|
+ u16 flags = 0;
|
|
|
|
|
|
if (nlmsg_len(nlh) < sizeof(*ifm))
|
|
|
return -EINVAL;
|
|
@@ -2316,15 +2363,45 @@ static int rtnl_bridge_setlink(struct sk_buff *skb, struct nlmsghdr *nlh,
|
|
|
return -ENODEV;
|
|
|
}
|
|
|
|
|
|
- if (dev->master && dev->master->netdev_ops->ndo_bridge_setlink) {
|
|
|
+ br_spec = nlmsg_find_attr(nlh, sizeof(struct ifinfomsg), IFLA_AF_SPEC);
|
|
|
+ if (br_spec) {
|
|
|
+ nla_for_each_nested(attr, br_spec, rem) {
|
|
|
+ if (nla_type(attr) == IFLA_BRIDGE_FLAGS) {
|
|
|
+ flags = nla_get_u16(attr);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!flags || (flags & BRIDGE_FLAGS_MASTER)) {
|
|
|
+ if (!dev->master ||
|
|
|
+ !dev->master->netdev_ops->ndo_bridge_setlink) {
|
|
|
+ err = -EOPNOTSUPP;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
err = dev->master->netdev_ops->ndo_bridge_setlink(dev, nlh);
|
|
|
if (err)
|
|
|
goto out;
|
|
|
+
|
|
|
+ flags &= ~BRIDGE_FLAGS_MASTER;
|
|
|
}
|
|
|
|
|
|
- if (dev->netdev_ops->ndo_bridge_setlink)
|
|
|
- err = dev->netdev_ops->ndo_bridge_setlink(dev, nlh);
|
|
|
+ if ((flags & BRIDGE_FLAGS_SELF)) {
|
|
|
+ if (!dev->netdev_ops->ndo_bridge_setlink)
|
|
|
+ err = -EOPNOTSUPP;
|
|
|
+ else
|
|
|
+ err = dev->netdev_ops->ndo_bridge_setlink(dev, nlh);
|
|
|
+
|
|
|
+ if (!err)
|
|
|
+ flags &= ~BRIDGE_FLAGS_SELF;
|
|
|
+ }
|
|
|
|
|
|
+ if (attr && nla_type(attr) == IFLA_BRIDGE_FLAGS)
|
|
|
+ memcpy(nla_data(attr), &flags, sizeof(flags));
|
|
|
+ /* Generate event to notify upper layer of bridge change */
|
|
|
+ if (!err)
|
|
|
+ err = rtnl_bridge_notify(dev, flags);
|
|
|
out:
|
|
|
return err;
|
|
|
}
|