diff options
Diffstat (limited to 'net/bridge')
-rw-r--r-- | net/bridge/br.c | 1 | ||||
-rw-r--r-- | net/bridge/br_device.c | 100 | ||||
-rw-r--r-- | net/bridge/br_fdb.c | 311 | ||||
-rw-r--r-- | net/bridge/br_if.c | 104 | ||||
-rw-r--r-- | net/bridge/br_input.c | 5 | ||||
-rw-r--r-- | net/bridge/br_ioctl.c | 40 | ||||
-rw-r--r-- | net/bridge/br_multicast.c | 12 | ||||
-rw-r--r-- | net/bridge/br_netfilter.c | 4 | ||||
-rw-r--r-- | net/bridge/br_netlink.c | 60 | ||||
-rw-r--r-- | net/bridge/br_notify.c | 11 | ||||
-rw-r--r-- | net/bridge/br_private.h | 22 | ||||
-rw-r--r-- | net/bridge/br_private_stp.h | 13 | ||||
-rw-r--r-- | net/bridge/br_stp.c | 48 | ||||
-rw-r--r-- | net/bridge/br_stp_if.c | 21 | ||||
-rw-r--r-- | net/bridge/br_sysfs_br.c | 39 | ||||
-rw-r--r-- | net/bridge/br_sysfs_if.c | 26 |
16 files changed, 543 insertions, 274 deletions
diff --git a/net/bridge/br.c b/net/bridge/br.c index 84bbb82599b2..f20c4fd915a8 100644 --- a/net/bridge/br.c +++ b/net/bridge/br.c @@ -104,3 +104,4 @@ module_init(br_init) module_exit(br_deinit) MODULE_LICENSE("GPL"); MODULE_VERSION(BR_VERSION); +MODULE_ALIAS_RTNL_LINK("bridge"); diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c index 21e5901186ea..a6b2f86378c7 100644 --- a/net/bridge/br_device.c +++ b/net/bridge/br_device.c @@ -74,13 +74,23 @@ out: return NETDEV_TX_OK; } +static int br_dev_init(struct net_device *dev) +{ + struct net_bridge *br = netdev_priv(dev); + + br->stats = alloc_percpu(struct br_cpu_netstats); + if (!br->stats) + return -ENOMEM; + + return 0; +} + static int br_dev_open(struct net_device *dev) { struct net_bridge *br = netdev_priv(dev); netif_carrier_off(dev); - - br_features_recompute(br); + netdev_update_features(dev); netif_start_queue(dev); br_stp_enable_bridge(br); br_multicast_open(br); @@ -177,48 +187,11 @@ static void br_getinfo(struct net_device *dev, struct ethtool_drvinfo *info) strcpy(info->bus_info, "N/A"); } -static int br_set_sg(struct net_device *dev, u32 data) -{ - struct net_bridge *br = netdev_priv(dev); - - if (data) - br->feature_mask |= NETIF_F_SG; - else - br->feature_mask &= ~NETIF_F_SG; - - br_features_recompute(br); - return 0; -} - -static int br_set_tso(struct net_device *dev, u32 data) -{ - struct net_bridge *br = netdev_priv(dev); - - if (data) - br->feature_mask |= NETIF_F_TSO; - else - br->feature_mask &= ~NETIF_F_TSO; - - br_features_recompute(br); - return 0; -} - -static int br_set_tx_csum(struct net_device *dev, u32 data) +static u32 br_fix_features(struct net_device *dev, u32 features) { struct net_bridge *br = netdev_priv(dev); - if (data) - br->feature_mask |= NETIF_F_NO_CSUM; - else - br->feature_mask &= ~NETIF_F_ALL_CSUM; - - br_features_recompute(br); - return 0; -} - -static int br_set_flags(struct net_device *netdev, u32 data) -{ - return ethtool_op_set_flags(netdev, data, ETH_FLAG_TXVLAN); + return br_features_recompute(br, features); } #ifdef CONFIG_NET_POLL_CONTROLLER @@ -319,21 +292,12 @@ static int br_del_slave(struct net_device *dev, struct net_device *slave_dev) static const struct ethtool_ops br_ethtool_ops = { .get_drvinfo = br_getinfo, .get_link = ethtool_op_get_link, - .get_tx_csum = ethtool_op_get_tx_csum, - .set_tx_csum = br_set_tx_csum, - .get_sg = ethtool_op_get_sg, - .set_sg = br_set_sg, - .get_tso = ethtool_op_get_tso, - .set_tso = br_set_tso, - .get_ufo = ethtool_op_get_ufo, - .set_ufo = ethtool_op_set_ufo, - .get_flags = ethtool_op_get_flags, - .set_flags = br_set_flags, }; static const struct net_device_ops br_netdev_ops = { .ndo_open = br_dev_open, .ndo_stop = br_dev_stop, + .ndo_init = br_dev_init, .ndo_start_xmit = br_dev_xmit, .ndo_get_stats64 = br_get_stats64, .ndo_set_mac_address = br_set_mac_address, @@ -347,6 +311,7 @@ static const struct net_device_ops br_netdev_ops = { #endif .ndo_add_slave = br_add_slave, .ndo_del_slave = br_del_slave, + .ndo_fix_features = br_fix_features, }; static void br_dev_free(struct net_device *dev) @@ -357,18 +322,49 @@ static void br_dev_free(struct net_device *dev) free_netdev(dev); } +static struct device_type br_type = { + .name = "bridge", +}; + void br_dev_setup(struct net_device *dev) { + struct net_bridge *br = netdev_priv(dev); + random_ether_addr(dev->dev_addr); ether_setup(dev); dev->netdev_ops = &br_netdev_ops; dev->destructor = br_dev_free; SET_ETHTOOL_OPS(dev, &br_ethtool_ops); + SET_NETDEV_DEVTYPE(dev, &br_type); dev->tx_queue_len = 0; dev->priv_flags = IFF_EBRIDGE; dev->features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA | NETIF_F_GSO_MASK | NETIF_F_NO_CSUM | NETIF_F_LLTX | - NETIF_F_NETNS_LOCAL | NETIF_F_GSO | NETIF_F_HW_VLAN_TX; + NETIF_F_NETNS_LOCAL | NETIF_F_HW_VLAN_TX; + dev->hw_features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA | + NETIF_F_GSO_MASK | NETIF_F_NO_CSUM | + NETIF_F_HW_VLAN_TX; + + br->dev = dev; + spin_lock_init(&br->lock); + INIT_LIST_HEAD(&br->port_list); + spin_lock_init(&br->hash_lock); + + br->bridge_id.prio[0] = 0x80; + br->bridge_id.prio[1] = 0x00; + + memcpy(br->group_addr, br_group_address, ETH_ALEN); + + br->stp_enabled = BR_NO_STP; + br->designated_root = br->bridge_id; + br->bridge_max_age = br->max_age = 20 * HZ; + br->bridge_hello_time = br->hello_time = 2 * HZ; + br->bridge_forward_delay = br->forward_delay = 15 * HZ; + br->ageing_time = 300 * HZ; + + br_netfilter_rtable_init(br); + br_stp_timer_init(br); + br_multicast_init(br); } diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c index cc4d3c5ab1c6..e0dfbc151dd7 100644 --- a/net/bridge/br_fdb.c +++ b/net/bridge/br_fdb.c @@ -28,6 +28,7 @@ static struct kmem_cache *br_fdb_cache __read_mostly; static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source, const unsigned char *addr); +static void fdb_notify(const struct net_bridge_fdb_entry *, int); static u32 fdb_salt __read_mostly; @@ -62,7 +63,7 @@ static inline int has_expired(const struct net_bridge *br, const struct net_bridge_fdb_entry *fdb) { return !fdb->is_static && - time_before_eq(fdb->ageing_timer + hold_time(br), jiffies); + time_before_eq(fdb->updated + hold_time(br), jiffies); } static inline int br_mac_hash(const unsigned char *mac) @@ -81,6 +82,7 @@ static void fdb_rcu_free(struct rcu_head *head) static inline void fdb_delete(struct net_bridge_fdb_entry *f) { + fdb_notify(f, RTM_DELNEIGH); hlist_del_rcu(&f->hlist); call_rcu(&f->rcu, fdb_rcu_free); } @@ -140,7 +142,7 @@ void br_fdb_cleanup(unsigned long _data) unsigned long this_timer; if (f->is_static) continue; - this_timer = f->ageing_timer + delay; + this_timer = f->updated + delay; if (time_before_eq(this_timer, jiffies)) fdb_delete(f); else if (time_before(this_timer, next_timer)) @@ -293,7 +295,7 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf, fe->is_local = f->is_local; if (!f->is_static) - fe->ageing_timer_value = jiffies_to_clock_t(jiffies - f->ageing_timer); + fe->ageing_timer_value = jiffies_to_clock_t(jiffies - f->updated); ++fe; ++num; } @@ -305,8 +307,21 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf, return num; } -static inline struct net_bridge_fdb_entry *fdb_find(struct hlist_head *head, - const unsigned char *addr) +static struct net_bridge_fdb_entry *fdb_find(struct hlist_head *head, + const unsigned char *addr) +{ + struct hlist_node *h; + struct net_bridge_fdb_entry *fdb; + + hlist_for_each_entry(fdb, h, head, hlist) { + if (!compare_ether_addr(fdb->addr.addr, addr)) + return fdb; + } + return NULL; +} + +static struct net_bridge_fdb_entry *fdb_find_rcu(struct hlist_head *head, + const unsigned char *addr) { struct hlist_node *h; struct net_bridge_fdb_entry *fdb; @@ -320,8 +335,7 @@ static inline struct net_bridge_fdb_entry *fdb_find(struct hlist_head *head, static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head, struct net_bridge_port *source, - const unsigned char *addr, - int is_local) + const unsigned char *addr) { struct net_bridge_fdb_entry *fdb; @@ -329,11 +343,11 @@ static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head, if (fdb) { memcpy(fdb->addr.addr, addr, ETH_ALEN); fdb->dst = source; - fdb->is_local = is_local; - fdb->is_static = is_local; - fdb->ageing_timer = jiffies; - + fdb->is_local = 0; + fdb->is_static = 0; + fdb->updated = fdb->used = jiffies; hlist_add_head_rcu(&fdb->hlist, head); + fdb_notify(fdb, RTM_NEWNEIGH); } return fdb; } @@ -360,12 +374,15 @@ static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source, fdb_delete(fdb); } - if (!fdb_create(head, source, addr, 1)) + fdb = fdb_create(head, source, addr); + if (!fdb) return -ENOMEM; + fdb->is_local = fdb->is_static = 1; return 0; } +/* Add entry for local address of interface */ int br_fdb_insert(struct net_bridge *br, struct net_bridge_port *source, const unsigned char *addr) { @@ -392,7 +409,7 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, source->state == BR_STATE_FORWARDING)) return; - fdb = fdb_find(head, addr); + fdb = fdb_find_rcu(head, addr); if (likely(fdb)) { /* attempt to update an entry for a local interface */ if (unlikely(fdb->is_local)) { @@ -403,15 +420,277 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, } else { /* fastpath: update of existing entry */ fdb->dst = source; - fdb->ageing_timer = jiffies; + fdb->updated = jiffies; } } else { spin_lock(&br->hash_lock); - if (!fdb_find(head, addr)) - fdb_create(head, source, addr, 0); + if (likely(!fdb_find(head, addr))) + fdb_create(head, source, addr); + /* else we lose race and someone else inserts * it first, don't bother updating */ spin_unlock(&br->hash_lock); } } + +static int fdb_to_nud(const struct net_bridge_fdb_entry *fdb) +{ + if (fdb->is_local) + return NUD_PERMANENT; + else if (fdb->is_static) + return NUD_NOARP; + else if (has_expired(fdb->dst->br, fdb)) + return NUD_STALE; + else + return NUD_REACHABLE; +} + +static int fdb_fill_info(struct sk_buff *skb, + const struct net_bridge_fdb_entry *fdb, + u32 pid, u32 seq, int type, unsigned int flags) +{ + unsigned long now = jiffies; + struct nda_cacheinfo ci; + struct nlmsghdr *nlh; + struct ndmsg *ndm; + + nlh = nlmsg_put(skb, pid, seq, type, sizeof(*ndm), flags); + if (nlh == NULL) + return -EMSGSIZE; + + + ndm = nlmsg_data(nlh); + ndm->ndm_family = AF_BRIDGE; + ndm->ndm_pad1 = 0; + ndm->ndm_pad2 = 0; + ndm->ndm_flags = 0; + ndm->ndm_type = 0; + ndm->ndm_ifindex = fdb->dst->dev->ifindex; + ndm->ndm_state = fdb_to_nud(fdb); + + NLA_PUT(skb, NDA_LLADDR, ETH_ALEN, &fdb->addr); + + ci.ndm_used = jiffies_to_clock_t(now - fdb->used); + ci.ndm_confirmed = 0; + ci.ndm_updated = jiffies_to_clock_t(now - fdb->updated); + ci.ndm_refcnt = 0; + NLA_PUT(skb, NDA_CACHEINFO, sizeof(ci), &ci); + + return nlmsg_end(skb, nlh); + +nla_put_failure: + nlmsg_cancel(skb, nlh); + return -EMSGSIZE; +} + +static inline size_t fdb_nlmsg_size(void) +{ + return NLMSG_ALIGN(sizeof(struct ndmsg)) + + nla_total_size(ETH_ALEN) /* NDA_LLADDR */ + + nla_total_size(sizeof(struct nda_cacheinfo)); +} + +static void fdb_notify(const struct net_bridge_fdb_entry *fdb, int type) +{ + struct net *net = dev_net(fdb->dst->dev); + struct sk_buff *skb; + int err = -ENOBUFS; + + skb = nlmsg_new(fdb_nlmsg_size(), GFP_ATOMIC); + if (skb == NULL) + goto errout; + + err = fdb_fill_info(skb, fdb, 0, 0, type, 0); + if (err < 0) { + /* -EMSGSIZE implies BUG in fdb_nlmsg_size() */ + WARN_ON(err == -EMSGSIZE); + kfree_skb(skb); + goto errout; + } + rtnl_notify(skb, net, 0, RTNLGRP_NEIGH, NULL, GFP_ATOMIC); + return; +errout: + if (err < 0) + rtnl_set_sk_err(net, RTNLGRP_NEIGH, err); +} + +/* Dump information about entries, in response to GETNEIGH */ +int br_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb) +{ + struct net *net = sock_net(skb->sk); + struct net_device *dev; + int idx = 0; + + rcu_read_lock(); + for_each_netdev_rcu(net, dev) { + struct net_bridge *br = netdev_priv(dev); + int i; + + if (!(dev->priv_flags & IFF_EBRIDGE)) + continue; + + for (i = 0; i < BR_HASH_SIZE; i++) { + struct hlist_node *h; + struct net_bridge_fdb_entry *f; + + hlist_for_each_entry_rcu(f, h, &br->hash[i], hlist) { + if (idx < cb->args[0]) + goto skip; + + if (fdb_fill_info(skb, f, + NETLINK_CB(cb->skb).pid, + cb->nlh->nlmsg_seq, + RTM_NEWNEIGH, + NLM_F_MULTI) < 0) + break; +skip: + ++idx; + } + } + } + rcu_read_unlock(); + + cb->args[0] = idx; + + return skb->len; +} + +/* Create new static fdb entry */ +static int fdb_add_entry(struct net_bridge_port *source, const __u8 *addr, + __u16 state) +{ + struct net_bridge *br = source->br; + struct hlist_head *head = &br->hash[br_mac_hash(addr)]; + struct net_bridge_fdb_entry *fdb; + + fdb = fdb_find(head, addr); + if (fdb) + return -EEXIST; + + fdb = fdb_create(head, source, addr); + if (!fdb) + return -ENOMEM; + + if (state & NUD_PERMANENT) + fdb->is_local = fdb->is_static = 1; + else if (state & NUD_NOARP) + fdb->is_static = 1; + return 0; +} + +/* Add new permanent fdb entry with RTM_NEWNEIGH */ +int br_fdb_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) +{ + struct net *net = sock_net(skb->sk); + struct ndmsg *ndm; + struct nlattr *tb[NDA_MAX+1]; + struct net_device *dev; + struct net_bridge_port *p; + const __u8 *addr; + int err; + + ASSERT_RTNL(); + err = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX, NULL); + if (err < 0) + return err; + + ndm = nlmsg_data(nlh); + if (ndm->ndm_ifindex == 0) { + pr_info("bridge: RTM_NEWNEIGH with invalid ifindex\n"); + return -EINVAL; + } + + dev = __dev_get_by_index(net, ndm->ndm_ifindex); + if (dev == NULL) { + pr_info("bridge: RTM_NEWNEIGH with unknown ifindex\n"); + return -ENODEV; + } + + if (!tb[NDA_LLADDR] || nla_len(tb[NDA_LLADDR]) != ETH_ALEN) { + pr_info("bridge: RTM_NEWNEIGH with invalid address\n"); + return -EINVAL; + } + + addr = nla_data(tb[NDA_LLADDR]); + if (!is_valid_ether_addr(addr)) { + pr_info("bridge: RTM_NEWNEIGH with invalid ether address\n"); + return -EINVAL; + } + + p = br_port_get_rtnl(dev); + if (p == NULL) { + pr_info("bridge: RTM_NEWNEIGH %s not a bridge port\n", + dev->name); + return -EINVAL; + } + + spin_lock_bh(&p->br->hash_lock); + err = fdb_add_entry(p, addr, ndm->ndm_state); + spin_unlock_bh(&p->br->hash_lock); + + return err; +} + +static int fdb_delete_by_addr(struct net_bridge_port *p, const u8 *addr) +{ + struct net_bridge *br = p->br; + struct hlist_head *head = &br->hash[br_mac_hash(addr)]; + struct net_bridge_fdb_entry *fdb; + + fdb = fdb_find(head, addr); + if (!fdb) + return -ENOENT; + + fdb_delete(fdb); + return 0; +} + +/* Remove neighbor entry with RTM_DELNEIGH */ +int br_fdb_delete(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) +{ + struct net *net = sock_net(skb->sk); + struct ndmsg *ndm; + struct net_bridge_port *p; + struct nlattr *llattr; + const __u8 *addr; + struct net_device *dev; + int err; + + ASSERT_RTNL(); + if (nlmsg_len(nlh) < sizeof(*ndm)) + return -EINVAL; + + ndm = nlmsg_data(nlh); + if (ndm->ndm_ifindex == 0) { + pr_info("bridge: RTM_DELNEIGH with invalid ifindex\n"); + return -EINVAL; + } + + dev = __dev_get_by_index(net, ndm->ndm_ifindex); + if (dev == NULL) { + pr_info("bridge: RTM_DELNEIGH with unknown ifindex\n"); + return -ENODEV; + } + + llattr = nlmsg_find_attr(nlh, sizeof(*ndm), NDA_LLADDR); + if (llattr == NULL || nla_len(llattr) != ETH_ALEN) { + pr_info("bridge: RTM_DELNEIGH with invalid address\n"); + return -EINVAL; + } + + addr = nla_data(llattr); + + p = br_port_get_rtnl(dev); + if (p == NULL) { + pr_info("bridge: RTM_DELNEIGH %s not a bridge port\n", + dev->name); + return -EINVAL; + } + + spin_lock_bh(&p->br->hash_lock); + err = fdb_delete_by_addr(p, addr); + spin_unlock_bh(&p->br->hash_lock); + + return err; +} diff --git a/net/bridge/br_if.c b/net/bridge/br_if.c index 718b60366dfe..5dbdfdfc3a34 100644 --- a/net/bridge/br_if.c +++ b/net/bridge/br_if.c @@ -36,8 +36,8 @@ static int port_cost(struct net_device *dev) if (dev->ethtool_ops && dev->ethtool_ops->get_settings) { struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET, }; - if (!dev->ethtool_ops->get_settings(dev, &ecmd)) { - switch(ecmd.speed) { + if (!dev_ethtool_get_settings(dev, &ecmd)) { + switch (ethtool_cmd_speed(&ecmd)) { case SPEED_10000: return 2; case SPEED_1000: @@ -175,56 +175,6 @@ static void del_br(struct net_bridge *br, struct list_head *head) unregister_netdevice_queue(br->dev, head); } -static struct net_device *new_bridge_dev(struct net *net, const char *name) -{ - struct net_bridge *br; - struct net_device *dev; - - dev = alloc_netdev(sizeof(struct net_bridge), name, - br_dev_setup); - - if (!dev) - return NULL; - dev_net_set(dev, net); - - br = netdev_priv(dev); - br->dev = dev; - - br->stats = alloc_percpu(struct br_cpu_netstats); - if (!br->stats) { - free_netdev(dev); - return NULL; - } - - spin_lock_init(&br->lock); - INIT_LIST_HEAD(&br->port_list); - spin_lock_init(&br->hash_lock); - - br->bridge_id.prio[0] = 0x80; - br->bridge_id.prio[1] = 0x00; - - memcpy(br->group_addr, br_group_address, ETH_ALEN); - - br->feature_mask = dev->features; - br->stp_enabled = BR_NO_STP; - br->designated_root = br->bridge_id; - br->root_path_cost = 0; - br->root_port = 0; - br->bridge_max_age = br->max_age = 20 * HZ; - br->bridge_hello_time = br->hello_time = 2 * HZ; - br->bridge_forward_delay = br->forward_delay = 15 * HZ; - br->topology_change = 0; - br->topology_change_detected = 0; - br->ageing_time = 300 * HZ; - - br_netfilter_rtable_init(br); - - br_stp_timer_init(br); - br_multicast_init(br); - - return dev; -} - /* find an available port number */ static int find_portno(struct net_bridge *br) { @@ -277,42 +227,19 @@ static struct net_bridge_port *new_nbp(struct net_bridge *br, return p; } -static struct device_type br_type = { - .name = "bridge", -}; - int br_add_bridge(struct net *net, const char *name) { struct net_device *dev; - int ret; - dev = new_bridge_dev(net, name); + dev = alloc_netdev(sizeof(struct net_bridge), name, + br_dev_setup); + if (!dev) return -ENOMEM; - rtnl_lock(); - if (strchr(dev->name, '%')) { - ret = dev_alloc_name(dev, dev->name); - if (ret < 0) - goto out_free; - } - - SET_NETDEV_DEVTYPE(dev, &br_type); - - ret = register_netdevice(dev); - if (ret) - goto out_free; - - ret = br_sysfs_addbr(dev); - if (ret) - unregister_netdevice(dev); - out: - rtnl_unlock(); - return ret; + dev_net_set(dev, net); -out_free: - free_netdev(dev); - goto out; + return register_netdev(dev); } int br_del_bridge(struct net *net, const char *name) @@ -364,15 +291,15 @@ int br_min_mtu(const struct net_bridge *br) /* * Recomputes features using slave's features */ -void br_features_recompute(struct net_bridge *br) +u32 br_features_recompute(struct net_bridge *br, u32 features) { struct net_bridge_port *p; - u32 features, mask; + u32 mask; - features = mask = br->feature_mask; if (list_empty(&br->port_list)) - goto done; + return features; + mask = features; features &= ~NETIF_F_ONE_FOR_ALL; list_for_each_entry(p, &br->port_list, list) { @@ -380,8 +307,7 @@ void br_features_recompute(struct net_bridge *br) p->dev->features, mask); } -done: - br->dev->features = netdev_fix_features(br->dev, features); + return features; } /* called with RTNL */ @@ -446,9 +372,10 @@ int br_add_if(struct net_bridge *br, struct net_device *dev) list_add_rcu(&p->list, &br->port_list); + netdev_update_features(br->dev); + spin_lock_bh(&br->lock); changed_addr = br_stp_recalculate_bridge_id(br); - br_features_recompute(br); if ((dev->flags & IFF_UP) && netif_carrier_ok(dev) && (br->dev->flags & IFF_UP)) @@ -496,9 +423,10 @@ int br_del_if(struct net_bridge *br, struct net_device *dev) spin_lock_bh(&br->lock); br_stp_recalculate_bridge_id(br); - br_features_recompute(br); spin_unlock_bh(&br->lock); + netdev_update_features(br->dev); + return 0; } diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c index 0c7badad62af..f3ac1e858ee1 100644 --- a/net/bridge/br_input.c +++ b/net/bridge/br_input.c @@ -98,9 +98,10 @@ int br_handle_frame_finish(struct sk_buff *skb) } if (skb) { - if (dst) + if (dst) { + dst->used = jiffies; br_forward(dst->dst, skb, skb2); - else + } else br_flood_forward(br, skb, skb2); } diff --git a/net/bridge/br_ioctl.c b/net/bridge/br_ioctl.c index 3d9fca0e3370..7222fe1d5460 100644 --- a/net/bridge/br_ioctl.c +++ b/net/bridge/br_ioctl.c @@ -181,40 +181,19 @@ static int old_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) if (!capable(CAP_NET_ADMIN)) return -EPERM; - spin_lock_bh(&br->lock); - br->bridge_forward_delay = clock_t_to_jiffies(args[1]); - if (br_is_root_bridge(br)) - br->forward_delay = br->bridge_forward_delay; - spin_unlock_bh(&br->lock); - return 0; + return br_set_forward_delay(br, args[1]); case BRCTL_SET_BRIDGE_HELLO_TIME: - { - unsigned long t = clock_t_to_jiffies(args[1]); if (!capable(CAP_NET_ADMIN)) return -EPERM; - if (t < HZ) - return -EINVAL; - - spin_lock_bh(&br->lock); - br->bridge_hello_time = t; - if (br_is_root_bridge(br)) - br->hello_time = br->bridge_hello_time; - spin_unlock_bh(&br->lock); - return 0; - } + return br_set_hello_time(br, args[1]); case BRCTL_SET_BRIDGE_MAX_AGE: if (!capable(CAP_NET_ADMIN)) return -EPERM; - spin_lock_bh(&br->lock); - br->bridge_max_age = clock_t_to_jiffies(args[1]); - if (br_is_root_bridge(br)) - br->max_age = br->bridge_max_age; - spin_unlock_bh(&br->lock); - return 0; + return br_set_max_age(br, args[1]); case BRCTL_SET_AGEING_TIME: if (!capable(CAP_NET_ADMIN)) @@ -275,19 +254,16 @@ static int old_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) case BRCTL_SET_PORT_PRIORITY: { struct net_bridge_port *p; - int ret = 0; + int ret; if (!capable(CAP_NET_ADMIN)) return -EPERM; - if (args[2] >= (1<<(16-BR_PORT_BITS))) - return -ERANGE; - spin_lock_bh(&br->lock); if ((p = br_get_port(br, args[1])) == NULL) ret = -EINVAL; else - br_stp_set_port_priority(p, args[2]); + ret = br_stp_set_port_priority(p, args[2]); spin_unlock_bh(&br->lock); return ret; } @@ -295,15 +271,17 @@ static int old_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) case BRCTL_SET_PATH_COST: { struct net_bridge_port *p; - int ret = 0; + int ret; if (!capable(CAP_NET_ADMIN)) return -EPERM; + spin_lock_bh(&br->lock); if ((p = br_get_port(br, args[1])) == NULL) ret = -EINVAL; else - br_stp_set_path_cost(p, args[2]); + ret = br_stp_set_path_cost(p, args[2]); + spin_unlock_bh(&br->lock); return ret; } diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 59660c909a7c..2f14eafdeeab 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -413,7 +413,7 @@ out: #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br, - struct in6_addr *group) + const struct in6_addr *group) { struct sk_buff *skb; struct ipv6hdr *ip6h; @@ -1115,7 +1115,7 @@ static int br_ip4_multicast_query(struct net_bridge *br, struct net_bridge_port *port, struct sk_buff *skb) { - struct iphdr *iph = ip_hdr(skb); + const struct iphdr *iph = ip_hdr(skb); struct igmphdr *ih = igmp_hdr(skb); struct net_bridge_mdb_entry *mp; struct igmpv3_query *ih3; @@ -1190,7 +1190,7 @@ static int br_ip6_multicast_query(struct net_bridge *br, struct net_bridge_port *port, struct sk_buff *skb) { - struct ipv6hdr *ip6h = ipv6_hdr(skb); + const struct ipv6hdr *ip6h = ipv6_hdr(skb); struct mld_msg *mld = (struct mld_msg *) icmp6_hdr(skb); struct net_bridge_mdb_entry *mp; struct mld2_query *mld2q; @@ -1198,7 +1198,7 @@ static int br_ip6_multicast_query(struct net_bridge *br, struct net_bridge_port_group __rcu **pp; unsigned long max_delay; unsigned long now = jiffies; - struct in6_addr *group = NULL; + const struct in6_addr *group = NULL; int err = 0; spin_lock(&br->multicast_lock); @@ -1356,7 +1356,7 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br, struct sk_buff *skb) { struct sk_buff *skb2 = skb; - struct iphdr *iph; + const struct iphdr *iph; struct igmphdr *ih; unsigned len; unsigned offset; @@ -1452,7 +1452,7 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br, struct sk_buff *skb) { struct sk_buff *skb2; - struct ipv6hdr *ip6h; + const struct ipv6hdr *ip6h; struct icmp6hdr *icmp6h; u8 nexthdr; unsigned len; diff --git a/net/bridge/br_netfilter.c b/net/bridge/br_netfilter.c index f3bc322c5891..5614907525e1 100644 --- a/net/bridge/br_netfilter.c +++ b/net/bridge/br_netfilter.c @@ -219,7 +219,7 @@ static inline void nf_bridge_update_protocol(struct sk_buff *skb) static int br_parse_ip_options(struct sk_buff *skb) { struct ip_options *opt; - struct iphdr *iph; + const struct iphdr *iph; struct net_device *dev = skb->dev; u32 len; @@ -554,7 +554,7 @@ static unsigned int br_nf_pre_routing_ipv6(unsigned int hook, const struct net_device *out, int (*okfn)(struct sk_buff *)) { - struct ipv6hdr *hdr; + const struct ipv6hdr *hdr; u32 pkt_len; if (skb->len < sizeof(struct ipv6hdr)) diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c index f8bf4c7f842c..ffb0dc4cc0e8 100644 --- a/net/bridge/br_netlink.c +++ b/net/bridge/br_netlink.c @@ -12,9 +12,11 @@ #include <linux/kernel.h> #include <linux/slab.h> +#include <linux/etherdevice.h> #include <net/rtnetlink.h> #include <net/net_namespace.h> #include <net/sock.h> + #include "br_private.h" static inline size_t br_nlmsg_size(void) @@ -118,8 +120,9 @@ static int br_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb) int idx; idx = 0; - for_each_netdev(net, dev) { - struct net_bridge_port *port = br_port_get_rtnl(dev); + rcu_read_lock(); + for_each_netdev_rcu(net, dev) { + struct net_bridge_port *port = br_port_get_rcu(dev); /* not a bridge port */ if (!port || idx < cb->args[0]) @@ -133,7 +136,7 @@ static int br_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb) skip: ++idx; } - + rcu_read_unlock(); cb->args[0] = idx; return skb->len; @@ -188,20 +191,61 @@ static int br_rtm_setlink(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) return 0; } +static int br_validate(struct nlattr *tb[], struct nlattr *data[]) +{ + if (tb[IFLA_ADDRESS]) { + if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN) + return -EINVAL; + if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS]))) + return -EADDRNOTAVAIL; + } + + return 0; +} + +static struct rtnl_link_ops br_link_ops __read_mostly = { + .kind = "bridge", + .priv_size = sizeof(struct net_bridge), + .setup = br_dev_setup, + .validate = br_validate, +}; int __init br_netlink_init(void) { - if (__rtnl_register(PF_BRIDGE, RTM_GETLINK, NULL, br_dump_ifinfo)) - return -ENOBUFS; + int err; - /* Only the first call to __rtnl_register can fail */ - __rtnl_register(PF_BRIDGE, RTM_SETLINK, br_rtm_setlink, NULL); + err = rtnl_link_register(&br_link_ops); + if (err < 0) + goto err1; + + err = __rtnl_register(PF_BRIDGE, RTM_GETLINK, NULL, br_dump_ifinfo); + if (err) + goto err2; + err = __rtnl_register(PF_BRIDGE, RTM_SETLINK, br_rtm_setlink, NULL); + if (err) + goto err3; + err = __rtnl_register(PF_BRIDGE, RTM_NEWNEIGH, br_fdb_add, NULL); + if (err) + goto err3; + err = __rtnl_register(PF_BRIDGE, RTM_DELNEIGH, br_fdb_delete, NULL); + if (err) + goto err3; + err = __rtnl_register(PF_BRIDGE, RTM_GETNEIGH, NULL, br_fdb_dump); + if (err) + goto err3; return 0; + +err3: + rtnl_unregister_all(PF_BRIDGE); +err2: + rtnl_link_unregister(&br_link_ops); +err1: + return err; } void __exit br_netlink_fini(void) { + rtnl_link_unregister(&br_link_ops); rtnl_unregister_all(PF_BRIDGE); } - diff --git a/net/bridge/br_notify.c b/net/bridge/br_notify.c index 7d337c9b6082..6545ee9591d1 100644 --- a/net/bridge/br_notify.c +++ b/net/bridge/br_notify.c @@ -36,6 +36,12 @@ static int br_device_event(struct notifier_block *unused, unsigned long event, v struct net_bridge *br; int err; + /* register of bridge completed, add sysfs entries */ + if ((dev->priv_flags & IFF_EBRIDGE) && event == NETDEV_REGISTER) { + br_sysfs_addbr(dev); + return NOTIFY_DONE; + } + /* not a port of a bridge */ p = br_port_get_rtnl(dev); if (!p) @@ -60,10 +66,7 @@ static int br_device_event(struct notifier_block *unused, unsigned long event, v break; case NETDEV_FEAT_CHANGE: - spin_lock_bh(&br->lock); - if (netif_running(br->dev)) - br_features_recompute(br); - spin_unlock_bh(&br->lock); + netdev_update_features(br->dev); break; case NETDEV_DOWN: diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index 387013d33745..54578f274d85 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -64,7 +64,8 @@ struct net_bridge_fdb_entry struct net_bridge_port *dst; struct rcu_head rcu; - unsigned long ageing_timer; + unsigned long updated; + unsigned long used; mac_addr addr; unsigned char is_local; unsigned char is_static; @@ -182,7 +183,6 @@ struct net_bridge struct br_cpu_netstats __percpu *stats; spinlock_t hash_lock; struct hlist_head hash[BR_HASH_SIZE]; - u32 feature_mask; #ifdef CONFIG_BRIDGE_NETFILTER struct rtable fake_rtable; bool nf_call_iptables; @@ -353,6 +353,9 @@ extern int br_fdb_insert(struct net_bridge *br, extern void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, const unsigned char *addr); +extern int br_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb); +extern int br_fdb_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg); +extern int br_fdb_delete(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg); /* br_forward.c */ extern void br_deliver(const struct net_bridge_port *to, @@ -375,7 +378,7 @@ extern int br_add_if(struct net_bridge *br, extern int br_del_if(struct net_bridge *br, struct net_device *dev); extern int br_min_mtu(const struct net_bridge *br); -extern void br_features_recompute(struct net_bridge *br); +extern u32 br_features_recompute(struct net_bridge *br, u32 features); /* br_input.c */ extern int br_handle_frame_finish(struct sk_buff *skb); @@ -491,6 +494,11 @@ extern struct net_bridge_port *br_get_port(struct net_bridge *br, extern void br_init_port(struct net_bridge_port *p); extern void br_become_designated_port(struct net_bridge_port *p); +extern int br_set_forward_delay(struct net_bridge *br, unsigned long x); +extern int br_set_hello_time(struct net_bridge *br, unsigned long x); +extern int br_set_max_age(struct net_bridge *br, unsigned long x); + + /* br_stp_if.c */ extern void br_stp_enable_bridge(struct net_bridge *br); extern void br_stp_disable_bridge(struct net_bridge *br); @@ -501,10 +509,10 @@ extern bool br_stp_recalculate_bridge_id(struct net_bridge *br); extern void br_stp_change_bridge_id(struct net_bridge *br, const unsigned char *a); extern void br_stp_set_bridge_priority(struct net_bridge *br, u16 newprio); -extern void br_stp_set_port_priority(struct net_bridge_port *p, - u8 newprio); -extern void br_stp_set_path_cost(struct net_bridge_port *p, - u32 path_cost); +extern int br_stp_set_port_priority(struct net_bridge_port *p, + unsigned long newprio); +extern int br_stp_set_path_cost(struct net_bridge_port *p, + unsigned long path_cost); extern ssize_t br_show_bridge_id(char *buf, const struct bridge_id *id); /* br_stp_bpdu.c */ diff --git a/net/bridge/br_private_stp.h b/net/bridge/br_private_stp.h index 8b650f7fbfa0..642ef47a867e 100644 --- a/net/bridge/br_private_stp.h +++ b/net/bridge/br_private_stp.h @@ -16,6 +16,19 @@ #define BPDU_TYPE_CONFIG 0 #define BPDU_TYPE_TCN 0x80 +/* IEEE 802.1D-1998 timer values */ +#define BR_MIN_HELLO_TIME (1*HZ) +#define BR_MAX_HELLO_TIME (10*HZ) + +#define BR_MIN_FORWARD_DELAY (2*HZ) +#define BR_MAX_FORWARD_DELAY (30*HZ) + +#define BR_MIN_MAX_AGE (6*HZ) +#define BR_MAX_MAX_AGE (40*HZ) + +#define BR_MIN_PATH_COST 1 +#define BR_MAX_PATH_COST 65535 + struct br_config_bpdu { unsigned topology_change:1; diff --git a/net/bridge/br_stp.c b/net/bridge/br_stp.c index 7370d14f634d..bb4383e84de9 100644 --- a/net/bridge/br_stp.c +++ b/net/bridge/br_stp.c @@ -484,3 +484,51 @@ void br_received_tcn_bpdu(struct net_bridge_port *p) br_topology_change_acknowledge(p); } } + +/* Change bridge STP parameter */ +int br_set_hello_time(struct net_bridge *br, unsigned long val) +{ + unsigned long t = clock_t_to_jiffies(val); + + if (t < BR_MIN_HELLO_TIME || t > BR_MAX_HELLO_TIME) + return -ERANGE; + + spin_lock_bh(&br->lock); + br->bridge_hello_time = t; + if (br_is_root_bridge(br)) + br->hello_time = br->bridge_hello_time; + spin_unlock_bh(&br->lock); + return 0; +} + +int br_set_max_age(struct net_bridge *br, unsigned long val) +{ + unsigned long t = clock_t_to_jiffies(val); + + if (t < BR_MIN_MAX_AGE || t > BR_MAX_MAX_AGE) + return -ERANGE; + + spin_lock_bh(&br->lock); + br->bridge_max_age = t; + if (br_is_root_bridge(br)) + br->max_age = br->bridge_max_age; + spin_unlock_bh(&br->lock); + return 0; + +} + +int br_set_forward_delay(struct net_bridge *br, unsigned long val) +{ + unsigned long t = clock_t_to_jiffies(val); + + if (br->stp_enabled != BR_NO_STP && + (t < BR_MIN_FORWARD_DELAY || t > BR_MAX_FORWARD_DELAY)) + return -ERANGE; + + spin_lock_bh(&br->lock); + br->bridge_forward_delay = t; + if (br_is_root_bridge(br)) + br->forward_delay = br->bridge_forward_delay; + spin_unlock_bh(&br->lock); + return 0; +} diff --git a/net/bridge/br_stp_if.c b/net/bridge/br_stp_if.c index 9b61d09de9b9..6f615b8192f4 100644 --- a/net/bridge/br_stp_if.c +++ b/net/bridge/br_stp_if.c @@ -20,7 +20,7 @@ /* Port id is composed of priority and port number. - * NB: least significant bits of priority are dropped to + * NB: some bits of priority are dropped to * make room for more ports. */ static inline port_id br_make_port_id(__u8 priority, __u16 port_no) @@ -29,6 +29,8 @@ static inline port_id br_make_port_id(__u8 priority, __u16 port_no) | (port_no & ((1<<BR_PORT_BITS)-1)); } +#define BR_MAX_PORT_PRIORITY ((u16)~0 >> BR_PORT_BITS) + /* called under bridge lock */ void br_init_port(struct net_bridge_port *p) { @@ -255,10 +257,14 @@ void br_stp_set_bridge_priority(struct net_bridge *br, u16 newprio) } /* called under bridge lock */ -void br_stp_set_port_priority(struct net_bridge_port *p, u8 newprio) +int br_stp_set_port_priority(struct net_bridge_port *p, unsigned long newprio) { - port_id new_port_id = br_make_port_id(newprio, p->port_no); + port_id new_port_id; + + if (newprio > BR_MAX_PORT_PRIORITY) + return -ERANGE; + new_port_id = br_make_port_id(newprio, p->port_no); if (br_is_designated_port(p)) p->designated_port = new_port_id; @@ -269,14 +275,21 @@ void br_stp_set_port_priority(struct net_bridge_port *p, u8 newprio) br_become_designated_port(p); br_port_state_selection(p->br); } + + return 0; } /* called under bridge lock */ -void br_stp_set_path_cost(struct net_bridge_port *p, u32 path_cost) +int br_stp_set_path_cost(struct net_bridge_port *p, unsigned long path_cost) { + if (path_cost < BR_MIN_PATH_COST || + path_cost > BR_MAX_PATH_COST) + return -ERANGE; + p->path_cost = path_cost; br_configuration_update(p->br); br_port_state_selection(p->br); + return 0; } ssize_t br_show_bridge_id(char *buf, const struct bridge_id *id) diff --git a/net/bridge/br_sysfs_br.c b/net/bridge/br_sysfs_br.c index 5c1e5559ebba..68b893ea8c3a 100644 --- a/net/bridge/br_sysfs_br.c +++ b/net/bridge/br_sysfs_br.c @@ -43,9 +43,7 @@ static ssize_t store_bridge_parm(struct device *d, if (endp == buf) return -EINVAL; - spin_lock_bh(&br->lock); err = (*set)(br, val); - spin_unlock_bh(&br->lock); return err ? err : len; } @@ -57,20 +55,11 @@ static ssize_t show_forward_delay(struct device *d, return sprintf(buf, "%lu\n", jiffies_to_clock_t(br->forward_delay)); } -static int set_forward_delay(struct net_bridge *br, unsigned long val) -{ - unsigned long delay = clock_t_to_jiffies(val); - br->forward_delay = delay; - if (br_is_root_bridge(br)) - br->bridge_forward_delay = delay; - return 0; -} - static ssize_t store_forward_delay(struct device *d, struct device_attribute *attr, const char *buf, size_t len) { - return store_bridge_parm(d, buf, len, set_forward_delay); + return store_bridge_parm(d, buf, len, br_set_forward_delay); } static DEVICE_ATTR(forward_delay, S_IRUGO | S_IWUSR, show_forward_delay, store_forward_delay); @@ -82,24 +71,11 @@ static ssize_t show_hello_time(struct device *d, struct device_attribute *attr, jiffies_to_clock_t(to_bridge(d)->hello_time)); } -static int set_hello_time(struct net_bridge *br, unsigned long val) -{ - unsigned long t = clock_t_to_jiffies(val); - - if (t < HZ) - return -EINVAL; - - br->hello_time = t; - if (br_is_root_bridge(br)) - br->bridge_hello_time = t; - return 0; -} - static ssize_t store_hello_time(struct device *d, struct device_attribute *attr, const char *buf, size_t len) { - return store_bridge_parm(d, buf, len, set_hello_time); + return store_bridge_parm(d, buf, len, br_set_hello_time); } static DEVICE_ATTR(hello_time, S_IRUGO | S_IWUSR, show_hello_time, store_hello_time); @@ -111,19 +87,10 @@ static ssize_t show_max_age(struct device *d, struct device_attribute *attr, jiffies_to_clock_t(to_bridge(d)->max_age)); } -static int set_max_age(struct net_bridge *br, unsigned long val) -{ - unsigned long t = clock_t_to_jiffies(val); - br->max_age = t; - if (br_is_root_bridge(br)) - br->bridge_max_age = t; - return 0; -} - static ssize_t store_max_age(struct device *d, struct device_attribute *attr, const char *buf, size_t len) { - return store_bridge_parm(d, buf, len, set_max_age); + return store_bridge_parm(d, buf, len, br_set_max_age); } static DEVICE_ATTR(max_age, S_IRUGO | S_IWUSR, show_max_age, store_max_age); diff --git a/net/bridge/br_sysfs_if.c b/net/bridge/br_sysfs_if.c index fd5799c9bc8d..6229b62749e8 100644 --- a/net/bridge/br_sysfs_if.c +++ b/net/bridge/br_sysfs_if.c @@ -23,7 +23,7 @@ struct brport_attribute { struct attribute attr; ssize_t (*show)(struct net_bridge_port *, char *); - ssize_t (*store)(struct net_bridge_port *, unsigned long); + int (*store)(struct net_bridge_port *, unsigned long); }; #define BRPORT_ATTR(_name,_mode,_show,_store) \ @@ -38,27 +38,17 @@ static ssize_t show_path_cost(struct net_bridge_port *p, char *buf) { return sprintf(buf, "%d\n", p->path_cost); } -static ssize_t store_path_cost(struct net_bridge_port *p, unsigned long v) -{ - br_stp_set_path_cost(p, v); - return 0; -} + static BRPORT_ATTR(path_cost, S_IRUGO | S_IWUSR, - show_path_cost, store_path_cost); + show_path_cost, br_stp_set_path_cost); static ssize_t show_priority(struct net_bridge_port *p, char *buf) { return sprintf(buf, "%d\n", p->priority); } -static ssize_t store_priority(struct net_bridge_port *p, unsigned long v) -{ - if (v >= (1<<(16-BR_PORT_BITS))) - return -ERANGE; - br_stp_set_port_priority(p, v); - return 0; -} + static BRPORT_ATTR(priority, S_IRUGO | S_IWUSR, - show_priority, store_priority); + show_priority, br_stp_set_port_priority); static ssize_t show_designated_root(struct net_bridge_port *p, char *buf) { @@ -136,7 +126,7 @@ static ssize_t show_hold_timer(struct net_bridge_port *p, } static BRPORT_ATTR(hold_timer, S_IRUGO, show_hold_timer, NULL); -static ssize_t store_flush(struct net_bridge_port *p, unsigned long v) +static int store_flush(struct net_bridge_port *p, unsigned long v) { br_fdb_delete_by_port(p->br, p, 0); // Don't delete local entry return 0; @@ -148,7 +138,7 @@ static ssize_t show_hairpin_mode(struct net_bridge_port *p, char *buf) int hairpin_mode = (p->flags & BR_HAIRPIN_MODE) ? 1 : 0; return sprintf(buf, "%d\n", hairpin_mode); } -static ssize_t store_hairpin_mode(struct net_bridge_port *p, unsigned long v) +static int store_hairpin_mode(struct net_bridge_port *p, unsigned long v) { if (v) p->flags |= BR_HAIRPIN_MODE; @@ -165,7 +155,7 @@ static ssize_t show_multicast_router(struct net_bridge_port *p, char *buf) return sprintf(buf, "%d\n", p->multicast_router); } -static ssize_t store_multicast_router(struct net_bridge_port *p, +static int store_multicast_router(struct net_bridge_port *p, unsigned long v) { return br_multicast_set_port_router(p, v); |