From 9cde070874b822d4677f4f01fe146991785813b1 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Wed, 21 Mar 2007 14:22:44 -0700 Subject: [PATCH] bridge: add support for user mode STP This patchset based on work by Aji_Srinivas@emc.com provides allows spanning tree to be controled from userspace. Like hotplug, it uses call_usermodehelper when spanning tree is enabled so there is no visible API change. If call to start usermode STP fails it falls back to existing kernel STP. Signed-off-by: Stephen Hemminger --- net/bridge/br_if.c | 2 +- net/bridge/br_ioctl.c | 5 ++-- net/bridge/br_netlink.c | 2 +- net/bridge/br_private.h | 15 ++++++++--- net/bridge/br_stp.c | 10 ++++--- net/bridge/br_stp_bpdu.c | 19 +++++++++----- net/bridge/br_stp_if.c | 56 ++++++++++++++++++++++++++++++++++++++++ net/bridge/br_sysfs_br.c | 4 ++- 8 files changed, 96 insertions(+), 17 deletions(-) diff --git a/net/bridge/br_if.c b/net/bridge/br_if.c index f3a2e29be4..cf10b8f2a1 100644 --- a/net/bridge/br_if.c +++ b/net/bridge/br_if.c @@ -203,7 +203,7 @@ static struct net_device *new_bridge_dev(const char *name) memcpy(br->group_addr, br_group_address, ETH_ALEN); br->feature_mask = dev->features; - br->stp_enabled = 0; + br->stp_enabled = BR_NO_STP; br->designated_root = br->bridge_id; br->root_path_cost = 0; br->root_port = 0; diff --git a/net/bridge/br_ioctl.c b/net/bridge/br_ioctl.c index 147015fe5c..eda0fbfc92 100644 --- a/net/bridge/br_ioctl.c +++ b/net/bridge/br_ioctl.c @@ -137,7 +137,8 @@ static int old_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) b.topology_change = br->topology_change; b.topology_change_detected = br->topology_change_detected; b.root_port = br->root_port; - b.stp_enabled = br->stp_enabled; + + b.stp_enabled = (br->stp_enabled != BR_NO_STP); b.ageing_time = jiffies_to_clock_t(br->ageing_time); b.hello_timer_value = br_timer_value(&br->hello_timer); b.tcn_timer_value = br_timer_value(&br->tcn_timer); @@ -251,7 +252,7 @@ static int old_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) if (!capable(CAP_NET_ADMIN)) return -EPERM; - br->stp_enabled = args[1]?1:0; + br_stp_set_enabled(br, args[1]); return 0; case BRCTL_SET_BRIDGE_PRIORITY: diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c index a14ac51753..5e84ade129 100644 --- a/net/bridge/br_netlink.c +++ b/net/bridge/br_netlink.c @@ -165,7 +165,7 @@ static int br_rtm_setlink(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) return -EINVAL; /* if kernel STP is running, don't allow changes */ - if (p->br->stp_enabled) + if (p->br->stp_enabled == BR_KERNEL_STP) return -EBUSY; if (!netif_running(dev) || diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index 3adacdf340..974feccd28 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -26,7 +26,10 @@ #define BR_PORT_BITS 10 #define BR_MAX_PORTS (1<bridge_id, &br->designated_root, 8); } - /* br_device.c */ extern void br_dev_setup(struct net_device *dev); extern int br_dev_xmit(struct sk_buff *skb, struct net_device *dev); @@ -209,6 +217,7 @@ extern void br_become_designated_port(struct net_bridge_port *p); /* br_stp_if.c */ extern void br_stp_enable_bridge(struct net_bridge *br); extern void br_stp_disable_bridge(struct net_bridge *br); +extern void br_stp_set_enabled(struct net_bridge *br, unsigned long val); extern void br_stp_enable_port(struct net_bridge_port *p); extern void br_stp_disable_port(struct net_bridge_port *p); extern void br_stp_recalculate_bridge_id(struct net_bridge *br); diff --git a/net/bridge/br_stp.c b/net/bridge/br_stp.c index f9ff4d57b0..ebb0861e9b 100644 --- a/net/bridge/br_stp.c +++ b/net/bridge/br_stp.c @@ -370,11 +370,11 @@ static void br_make_blocking(struct net_bridge_port *p) static void br_make_forwarding(struct net_bridge_port *p) { if (p->state == BR_STATE_BLOCKING) { - if (p->br->stp_enabled) { + if (p->br->stp_enabled == BR_KERNEL_STP) p->state = BR_STATE_LISTENING; - } else { + else p->state = BR_STATE_LEARNING; - } + br_log_state(p); mod_timer(&p->forward_delay_timer, jiffies + p->br->forward_delay); } } @@ -384,6 +384,10 @@ void br_port_state_selection(struct net_bridge *br) { struct net_bridge_port *p; + /* Don't change port states if userspace is handling STP */ + if (br->stp_enabled == BR_USER_STP) + return; + list_for_each_entry(p, &br->port_list, list) { if (p->state != BR_STATE_DISABLED) { if (p->port_no == br->root_port) { diff --git a/net/bridge/br_stp_bpdu.c b/net/bridge/br_stp_bpdu.c index b9fb0dc4ab..60112bce66 100644 --- a/net/bridge/br_stp_bpdu.c +++ b/net/bridge/br_stp_bpdu.c @@ -33,9 +33,6 @@ static void br_send_bpdu(struct net_bridge_port *p, { struct sk_buff *skb; - if (!p->br->stp_enabled) - return; - skb = dev_alloc_skb(length+LLC_RESERVE); if (!skb) return; @@ -75,6 +72,9 @@ void br_send_config_bpdu(struct net_bridge_port *p, struct br_config_bpdu *bpdu) { unsigned char buf[35]; + if (p->br->stp_enabled != BR_KERNEL_STP) + return; + buf[0] = 0; buf[1] = 0; buf[2] = 0; @@ -117,6 +117,9 @@ void br_send_tcn_bpdu(struct net_bridge_port *p) { unsigned char buf[4]; + if (p->br->stp_enabled != BR_KERNEL_STP) + return; + buf[0] = 0; buf[1] = 0; buf[2] = 0; @@ -157,9 +160,13 @@ int br_stp_rcv(struct sk_buff *skb, struct net_device *dev, br = p->br; spin_lock(&br->lock); - if (p->state == BR_STATE_DISABLED - || !br->stp_enabled - || !(br->dev->flags & IFF_UP)) + if (br->stp_enabled != BR_KERNEL_STP) + goto out; + + if (!(br->dev->flags & IFF_UP)) + goto out; + + if (p->state == BR_STATE_DISABLED) goto out; if (compare_ether_addr(dest, br->group_addr) != 0) diff --git a/net/bridge/br_stp_if.c b/net/bridge/br_stp_if.c index a285897a2f..1c1806d7c4 100644 --- a/net/bridge/br_stp_if.c +++ b/net/bridge/br_stp_if.c @@ -123,6 +123,62 @@ void br_stp_disable_port(struct net_bridge_port *p) br_become_root_bridge(br); } +static void br_stp_start(struct net_bridge *br) +{ + int r; + char *argv[] = { BR_STP_PROG, br->dev->name, "start", NULL }; + char *envp[] = { NULL }; + + r = call_usermodehelper(BR_STP_PROG, argv, envp, 1); + if (r == 0) { + br->stp_enabled = BR_USER_STP; + printk(KERN_INFO "%s: userspace STP started\n", br->dev->name); + } else { + br->stp_enabled = BR_KERNEL_STP; + printk(KERN_INFO "%s: starting userspace STP failed, " + "staring kernel STP\n", br->dev->name); + + /* To start timers on any ports left in blocking */ + spin_lock_bh(&br->lock); + br_port_state_selection(br); + spin_unlock_bh(&br->lock); + } +} + +static void br_stp_stop(struct net_bridge *br) +{ + int r; + char *argv[] = { BR_STP_PROG, br->dev->name, "stop", NULL }; + char *envp[] = { NULL }; + + if (br->stp_enabled == BR_USER_STP) { + r = call_usermodehelper(BR_STP_PROG, argv, envp, 1); + printk(KERN_INFO "%s: userspace STP stopped, return code %d\n", + br->dev->name, r); + + + /* To start timers on any ports left in blocking */ + spin_lock_bh(&br->lock); + br_port_state_selection(br); + spin_unlock_bh(&br->lock); + } + + br->stp_enabled = BR_NO_STP; +} + +void br_stp_set_enabled(struct net_bridge *br, unsigned long val) +{ + ASSERT_RTNL(); + + if (val) { + if (br->stp_enabled == BR_NO_STP) + br_stp_start(br); + } else { + if (br->stp_enabled != BR_NO_STP) + br_stp_stop(br); + } +} + /* called under bridge lock */ void br_stp_change_bridge_id(struct net_bridge *br, const unsigned char *addr) { diff --git a/net/bridge/br_sysfs_br.c b/net/bridge/br_sysfs_br.c index 6cc5cfe665..7ec0b76cdd 100644 --- a/net/bridge/br_sysfs_br.c +++ b/net/bridge/br_sysfs_br.c @@ -149,7 +149,9 @@ static ssize_t show_stp_state(struct device *d, static void set_stp_state(struct net_bridge *br, unsigned long val) { - br->stp_enabled = val; + spin_unlock_bh(&br->lock); + br_stp_set_enabled(br, val); + spin_lock_bh(&br->lock); } static ssize_t store_stp_state(struct device *d, -- 2.39.5