Skip to content

Commit

Permalink
bridge: Automatically manage port promiscuous mode.
Browse files Browse the repository at this point in the history
There exist configurations where the administrator or another management
entity has the foreknowledge of all the mac addresses of end systems
that are being bridged together.

In these environments, the administrator can statically configure known
addresses in the bridge FDB and disable flooding and learning on ports.
This makes it possible to turn off promiscuous mode on the interfaces
connected to the bridge.

Here is why disabling flooding and learning allows us to control
promiscuity:
 Consider port X.  All traffic coming into this port from outside the
bridge (ingress) will be either forwarded through other ports of the
bridge (egress) or dropped.  Forwarding (egress) is defined by FDB
entries and by flooding in the event that no FDB entry exists.
In the event that flooding is disabled, only FDB entries define
the egress.  Once learning is disabled, only static FDB entries
provided by a management entity define the egress.  If we provide
information from these static FDBs to the ingress port X, then we'll
be able to accept all traffic that can be successfully forwarded and
drop all the other traffic sooner without spending CPU cycles to
process it.
 Another way to define the above is as following equations:
    ingress = egress + drop
 expanding egress
    ingress = static FDB + learned FDB + flooding + drop
 disabling flooding and learning we a left with
    ingress = static FDB + drop

By adding addresses from the static FDB entries to the MAC address
filter of an ingress port X, we fully define what the bridge can
process without dropping and can thus turn off promiscuous mode,
thus dropping packets sooner.

There have been suggestions that we may want to allow learning
and update the filters with learned addresses as well.  This
would require mac-level authentication similar to 802.1x to
prevent attacks against the hw filters as they are limited
resource.

Additionally, if the user places the bridge device in promiscuous mode,
all ports are placed in promiscuous mode regardless of the changes
to flooding and learning.

Since the above functionality depends on full static configuration,
we have also require that vlan filtering be enabled to take
advantage of this.  The reason is that the bridge has to be
able to receive and process VLAN-tagged frames and the there
are only 2 ways to accomplish this right now: promiscuous mode
or vlan filtering.

Suggested-by: Michael S. Tsirkin <mst@redhat.com>
Acked-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Vlad Yasevich <vyasevic@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Vlad Yasevich authored and davem330 committed May 16, 2014
1 parent 145beee commit 2796d0c
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 7 deletions.
7 changes: 7 additions & 0 deletions net/bridge/br_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ static void br_dev_set_multicast_list(struct net_device *dev)
{
}

static void br_dev_change_rx_flags(struct net_device *dev, int change)
{
if (change & IFF_PROMISC)
br_manage_promisc(netdev_priv(dev));
}

static int br_dev_stop(struct net_device *dev)
{
struct net_bridge *br = netdev_priv(dev);
Expand Down Expand Up @@ -309,6 +315,7 @@ static const struct net_device_ops br_netdev_ops = {
.ndo_get_stats64 = br_get_stats64,
.ndo_set_mac_address = br_set_mac_address,
.ndo_set_rx_mode = br_dev_set_multicast_list,
.ndo_change_rx_flags = br_dev_change_rx_flags,
.ndo_change_mtu = br_change_mtu,
.ndo_do_ioctl = br_dev_ioctl,
#ifdef CONFIG_NET_POLL_CONTROLLER
Expand Down
105 changes: 98 additions & 7 deletions net/bridge/br_if.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,82 @@ void br_port_carrier_check(struct net_bridge_port *p)
spin_unlock_bh(&br->lock);
}

static void br_port_set_promisc(struct net_bridge_port *p)
{
int err = 0;

if (br_promisc_port(p))
return;

err = dev_set_promiscuity(p->dev, 1);
if (err)
return;

br_fdb_unsync_static(p->br, p);
p->flags |= BR_PROMISC;
}

static void br_port_clear_promisc(struct net_bridge_port *p)
{
int err;

/* Check if the port is already non-promisc or if it doesn't
* support UNICAST filtering. Without unicast filtering support
* we'll end up re-enabling promisc mode anyway, so just check for
* it here.
*/
if (!br_promisc_port(p) || !(p->dev->priv_flags & IFF_UNICAST_FLT))
return;

/* Since we'll be clearing the promisc mode, program the port
* first so that we don't have interruption in traffic.
*/
err = br_fdb_sync_static(p->br, p);
if (err)
return;

dev_set_promiscuity(p->dev, -1);
p->flags &= ~BR_PROMISC;
}

/* When a port is added or removed or when certain port flags
* change, this function is called to automatically manage
* promiscuity setting of all the bridge ports. We are always called
* under RTNL so can skip using rcu primitives.
*/
void br_manage_promisc(struct net_bridge *br)
{
struct net_bridge_port *p;
bool set_all = false;

/* If vlan filtering is disabled or bridge interface is placed
* into promiscuous mode, place all ports in promiscuous mode.
*/
if ((br->dev->flags & IFF_PROMISC) || !br_vlan_enabled(br))
set_all = true;

list_for_each_entry(p, &br->port_list, list) {
if (set_all) {
br_port_set_promisc(p);
} else {
/* If the number of auto-ports is <= 1, then all other
* ports will have their output configuration
* statically specified through fdbs. Since ingress
* on the auto-port becomes forwarding/egress to other
* ports and egress configuration is statically known,
* we can say that ingress configuration of the
* auto-port is also statically known.
* This lets us disable promiscuous mode and write
* this config to hw.
*/
if (br->auto_cnt <= br_auto_port(p))
br_port_clear_promisc(p);
else
br_port_set_promisc(p);
}
}
}

static void nbp_update_port_count(struct net_bridge *br)
{
struct net_bridge_port *p;
Expand All @@ -94,7 +170,23 @@ static void nbp_update_port_count(struct net_bridge *br)
if (br_auto_port(p))
cnt++;
}
br->auto_cnt = cnt;
if (br->auto_cnt != cnt) {
br->auto_cnt = cnt;
br_manage_promisc(br);
}
}

static void nbp_delete_promisc(struct net_bridge_port *p)
{
/* If port is currently promiscous, unset promiscuity.
* Otherwise, it is a static port so remove all addresses
* from it.
*/
dev_set_allmulti(p->dev, -1);
if (br_promisc_port(p))
dev_set_promiscuity(p->dev, -1);
else
br_fdb_unsync_static(p->br, p);
}

static void release_nbp(struct kobject *kobj)
Expand Down Expand Up @@ -145,19 +237,18 @@ static void del_nbp(struct net_bridge_port *p)

sysfs_remove_link(br->ifobj, p->dev->name);

dev_set_promiscuity(dev, -1);
nbp_delete_promisc(p);

spin_lock_bh(&br->lock);
br_stp_disable_port(p);
spin_unlock_bh(&br->lock);

br_ifinfo_notify(RTM_DELLINK, p);

nbp_vlan_flush(p);
br_fdb_delete_by_port(br, p, 1);

list_del_rcu(&p->list);

nbp_vlan_flush(p);
br_fdb_delete_by_port(br, p, 1);
nbp_update_port_count(br);

dev->priv_flags &= ~IFF_BRIDGE_PORT;
Expand Down Expand Up @@ -238,7 +329,7 @@ static struct net_bridge_port *new_nbp(struct net_bridge *br,
p->path_cost = port_cost(dev);
p->priority = 0x8000 >> BR_PORT_BITS;
p->port_no = index;
p->flags = BR_LEARNING | BR_FLOOD | BR_PROMISC;
p->flags = BR_LEARNING | BR_FLOOD;
br_init_port(p);
p->state = BR_STATE_DISABLED;
br_stp_port_timer_init(p);
Expand Down Expand Up @@ -367,7 +458,7 @@ int br_add_if(struct net_bridge *br, struct net_device *dev)

call_netdevice_notifiers(NETDEV_JOIN, dev);

err = dev_set_promiscuity(dev, 1);
err = dev_set_allmulti(dev, 1);
if (err)
goto put_back;

Expand Down
10 changes: 10 additions & 0 deletions net/bridge/br_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ int br_min_mtu(const struct net_bridge *br);
netdev_features_t br_features_recompute(struct net_bridge *br,
netdev_features_t features);
void br_port_flags_change(struct net_bridge_port *port, unsigned long mask);
void br_manage_promisc(struct net_bridge *br);

/* br_input.c */
int br_handle_frame_finish(struct sk_buff *skb);
Expand Down Expand Up @@ -641,6 +642,10 @@ static inline u16 br_get_pvid(const struct net_port_vlans *v)
return v->pvid ?: VLAN_N_VID;
}

static inline int br_vlan_enabled(struct net_bridge *br)
{
return br->vlan_enabled;
}
#else
static inline bool br_allowed_ingress(struct net_bridge *br,
struct net_port_vlans *v,
Expand Down Expand Up @@ -721,6 +726,11 @@ static inline u16 br_get_pvid(const struct net_port_vlans *v)
{
return VLAN_N_VID; /* Returns invalid vid */
}

static inline int br_vlan_enabled(struct net_bridge *br);
{
return 0;
}
#endif

/* br_netfilter.c */
Expand Down
1 change: 1 addition & 0 deletions net/bridge/br_vlan.c
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ int br_vlan_filter_toggle(struct net_bridge *br, unsigned long val)
goto unlock;

br->vlan_enabled = val;
br_manage_promisc(br);

unlock:
rtnl_unlock();
Expand Down

0 comments on commit 2796d0c

Please sign in to comment.