2796d0c648
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>
431 lines
8.4 KiB
C
431 lines
8.4 KiB
C
#include <linux/kernel.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "br_private.h"
|
|
|
|
static void __vlan_add_pvid(struct net_port_vlans *v, u16 vid)
|
|
{
|
|
if (v->pvid == vid)
|
|
return;
|
|
|
|
smp_wmb();
|
|
v->pvid = vid;
|
|
}
|
|
|
|
static void __vlan_delete_pvid(struct net_port_vlans *v, u16 vid)
|
|
{
|
|
if (v->pvid != vid)
|
|
return;
|
|
|
|
smp_wmb();
|
|
v->pvid = 0;
|
|
}
|
|
|
|
static void __vlan_add_flags(struct net_port_vlans *v, u16 vid, u16 flags)
|
|
{
|
|
if (flags & BRIDGE_VLAN_INFO_PVID)
|
|
__vlan_add_pvid(v, vid);
|
|
|
|
if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
|
|
set_bit(vid, v->untagged_bitmap);
|
|
}
|
|
|
|
static int __vlan_add(struct net_port_vlans *v, u16 vid, u16 flags)
|
|
{
|
|
struct net_bridge_port *p = NULL;
|
|
struct net_bridge *br;
|
|
struct net_device *dev;
|
|
int err;
|
|
|
|
if (test_bit(vid, v->vlan_bitmap)) {
|
|
__vlan_add_flags(v, vid, flags);
|
|
return 0;
|
|
}
|
|
|
|
if (v->port_idx) {
|
|
p = v->parent.port;
|
|
br = p->br;
|
|
dev = p->dev;
|
|
} else {
|
|
br = v->parent.br;
|
|
dev = br->dev;
|
|
}
|
|
|
|
if (p) {
|
|
/* Add VLAN to the device filter if it is supported.
|
|
* Stricly speaking, this is not necessary now, since
|
|
* devices are made promiscuous by the bridge, but if
|
|
* that ever changes this code will allow tagged
|
|
* traffic to enter the bridge.
|
|
*/
|
|
err = vlan_vid_add(dev, htons(ETH_P_8021Q), vid);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
err = br_fdb_insert(br, p, dev->dev_addr, vid);
|
|
if (err) {
|
|
br_err(br, "failed insert local address into bridge "
|
|
"forwarding table\n");
|
|
goto out_filt;
|
|
}
|
|
|
|
set_bit(vid, v->vlan_bitmap);
|
|
v->num_vlans++;
|
|
__vlan_add_flags(v, vid, flags);
|
|
|
|
return 0;
|
|
|
|
out_filt:
|
|
if (p)
|
|
vlan_vid_del(dev, htons(ETH_P_8021Q), vid);
|
|
return err;
|
|
}
|
|
|
|
static int __vlan_del(struct net_port_vlans *v, u16 vid)
|
|
{
|
|
if (!test_bit(vid, v->vlan_bitmap))
|
|
return -EINVAL;
|
|
|
|
__vlan_delete_pvid(v, vid);
|
|
clear_bit(vid, v->untagged_bitmap);
|
|
|
|
if (v->port_idx)
|
|
vlan_vid_del(v->parent.port->dev, htons(ETH_P_8021Q), vid);
|
|
|
|
clear_bit(vid, v->vlan_bitmap);
|
|
v->num_vlans--;
|
|
if (bitmap_empty(v->vlan_bitmap, VLAN_N_VID)) {
|
|
if (v->port_idx)
|
|
RCU_INIT_POINTER(v->parent.port->vlan_info, NULL);
|
|
else
|
|
RCU_INIT_POINTER(v->parent.br->vlan_info, NULL);
|
|
kfree_rcu(v, rcu);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void __vlan_flush(struct net_port_vlans *v)
|
|
{
|
|
smp_wmb();
|
|
v->pvid = 0;
|
|
bitmap_zero(v->vlan_bitmap, VLAN_N_VID);
|
|
if (v->port_idx)
|
|
RCU_INIT_POINTER(v->parent.port->vlan_info, NULL);
|
|
else
|
|
RCU_INIT_POINTER(v->parent.br->vlan_info, NULL);
|
|
kfree_rcu(v, rcu);
|
|
}
|
|
|
|
struct sk_buff *br_handle_vlan(struct net_bridge *br,
|
|
const struct net_port_vlans *pv,
|
|
struct sk_buff *skb)
|
|
{
|
|
u16 vid;
|
|
|
|
if (!br->vlan_enabled)
|
|
goto out;
|
|
|
|
/* Vlan filter table must be configured at this point. The
|
|
* only exception is the bridge is set in promisc mode and the
|
|
* packet is destined for the bridge device. In this case
|
|
* pass the packet as is.
|
|
*/
|
|
if (!pv) {
|
|
if ((br->dev->flags & IFF_PROMISC) && skb->dev == br->dev) {
|
|
goto out;
|
|
} else {
|
|
kfree_skb(skb);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* At this point, we know that the frame was filtered and contains
|
|
* a valid vlan id. If the vlan id is set in the untagged bitmap,
|
|
* send untagged; otherwise, send tagged.
|
|
*/
|
|
br_vlan_get_tag(skb, &vid);
|
|
if (test_bit(vid, pv->untagged_bitmap))
|
|
skb->vlan_tci = 0;
|
|
|
|
out:
|
|
return skb;
|
|
}
|
|
|
|
/* Called under RCU */
|
|
bool br_allowed_ingress(struct net_bridge *br, struct net_port_vlans *v,
|
|
struct sk_buff *skb, u16 *vid)
|
|
{
|
|
int err;
|
|
|
|
/* If VLAN filtering is disabled on the bridge, all packets are
|
|
* permitted.
|
|
*/
|
|
if (!br->vlan_enabled)
|
|
return true;
|
|
|
|
/* If there are no vlan in the permitted list, all packets are
|
|
* rejected.
|
|
*/
|
|
if (!v)
|
|
goto drop;
|
|
|
|
/* If vlan tx offload is disabled on bridge device and frame was
|
|
* sent from vlan device on the bridge device, it does not have
|
|
* HW accelerated vlan tag.
|
|
*/
|
|
if (unlikely(!vlan_tx_tag_present(skb) &&
|
|
(skb->protocol == htons(ETH_P_8021Q) ||
|
|
skb->protocol == htons(ETH_P_8021AD)))) {
|
|
skb = vlan_untag(skb);
|
|
if (unlikely(!skb))
|
|
return false;
|
|
}
|
|
|
|
err = br_vlan_get_tag(skb, vid);
|
|
if (!*vid) {
|
|
u16 pvid = br_get_pvid(v);
|
|
|
|
/* Frame had a tag with VID 0 or did not have a tag.
|
|
* See if pvid is set on this port. That tells us which
|
|
* vlan untagged or priority-tagged traffic belongs to.
|
|
*/
|
|
if (pvid == VLAN_N_VID)
|
|
goto drop;
|
|
|
|
/* PVID is set on this port. Any untagged or priority-tagged
|
|
* ingress frame is considered to belong to this vlan.
|
|
*/
|
|
*vid = pvid;
|
|
if (likely(err))
|
|
/* Untagged Frame. */
|
|
__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), pvid);
|
|
else
|
|
/* Priority-tagged Frame.
|
|
* At this point, We know that skb->vlan_tci had
|
|
* VLAN_TAG_PRESENT bit and its VID field was 0x000.
|
|
* We update only VID field and preserve PCP field.
|
|
*/
|
|
skb->vlan_tci |= pvid;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Frame had a valid vlan tag. See if vlan is allowed */
|
|
if (test_bit(*vid, v->vlan_bitmap))
|
|
return true;
|
|
drop:
|
|
kfree_skb(skb);
|
|
return false;
|
|
}
|
|
|
|
/* Called under RCU. */
|
|
bool br_allowed_egress(struct net_bridge *br,
|
|
const struct net_port_vlans *v,
|
|
const struct sk_buff *skb)
|
|
{
|
|
u16 vid;
|
|
|
|
if (!br->vlan_enabled)
|
|
return true;
|
|
|
|
if (!v)
|
|
return false;
|
|
|
|
br_vlan_get_tag(skb, &vid);
|
|
if (test_bit(vid, v->vlan_bitmap))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Must be protected by RTNL.
|
|
* Must be called with vid in range from 1 to 4094 inclusive.
|
|
*/
|
|
int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags)
|
|
{
|
|
struct net_port_vlans *pv = NULL;
|
|
int err;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
pv = rtnl_dereference(br->vlan_info);
|
|
if (pv)
|
|
return __vlan_add(pv, vid, flags);
|
|
|
|
/* Create port vlan infomration
|
|
*/
|
|
pv = kzalloc(sizeof(*pv), GFP_KERNEL);
|
|
if (!pv)
|
|
return -ENOMEM;
|
|
|
|
pv->parent.br = br;
|
|
err = __vlan_add(pv, vid, flags);
|
|
if (err)
|
|
goto out;
|
|
|
|
rcu_assign_pointer(br->vlan_info, pv);
|
|
return 0;
|
|
out:
|
|
kfree(pv);
|
|
return err;
|
|
}
|
|
|
|
/* Must be protected by RTNL.
|
|
* Must be called with vid in range from 1 to 4094 inclusive.
|
|
*/
|
|
int br_vlan_delete(struct net_bridge *br, u16 vid)
|
|
{
|
|
struct net_port_vlans *pv;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
pv = rtnl_dereference(br->vlan_info);
|
|
if (!pv)
|
|
return -EINVAL;
|
|
|
|
br_fdb_find_delete_local(br, NULL, br->dev->dev_addr, vid);
|
|
|
|
__vlan_del(pv, vid);
|
|
return 0;
|
|
}
|
|
|
|
void br_vlan_flush(struct net_bridge *br)
|
|
{
|
|
struct net_port_vlans *pv;
|
|
|
|
ASSERT_RTNL();
|
|
pv = rtnl_dereference(br->vlan_info);
|
|
if (!pv)
|
|
return;
|
|
|
|
__vlan_flush(pv);
|
|
}
|
|
|
|
bool br_vlan_find(struct net_bridge *br, u16 vid)
|
|
{
|
|
struct net_port_vlans *pv;
|
|
bool found = false;
|
|
|
|
rcu_read_lock();
|
|
pv = rcu_dereference(br->vlan_info);
|
|
|
|
if (!pv)
|
|
goto out;
|
|
|
|
if (test_bit(vid, pv->vlan_bitmap))
|
|
found = true;
|
|
|
|
out:
|
|
rcu_read_unlock();
|
|
return found;
|
|
}
|
|
|
|
int br_vlan_filter_toggle(struct net_bridge *br, unsigned long val)
|
|
{
|
|
if (!rtnl_trylock())
|
|
return restart_syscall();
|
|
|
|
if (br->vlan_enabled == val)
|
|
goto unlock;
|
|
|
|
br->vlan_enabled = val;
|
|
br_manage_promisc(br);
|
|
|
|
unlock:
|
|
rtnl_unlock();
|
|
return 0;
|
|
}
|
|
|
|
/* Must be protected by RTNL.
|
|
* Must be called with vid in range from 1 to 4094 inclusive.
|
|
*/
|
|
int nbp_vlan_add(struct net_bridge_port *port, u16 vid, u16 flags)
|
|
{
|
|
struct net_port_vlans *pv = NULL;
|
|
int err;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
pv = rtnl_dereference(port->vlan_info);
|
|
if (pv)
|
|
return __vlan_add(pv, vid, flags);
|
|
|
|
/* Create port vlan infomration
|
|
*/
|
|
pv = kzalloc(sizeof(*pv), GFP_KERNEL);
|
|
if (!pv) {
|
|
err = -ENOMEM;
|
|
goto clean_up;
|
|
}
|
|
|
|
pv->port_idx = port->port_no;
|
|
pv->parent.port = port;
|
|
err = __vlan_add(pv, vid, flags);
|
|
if (err)
|
|
goto clean_up;
|
|
|
|
rcu_assign_pointer(port->vlan_info, pv);
|
|
return 0;
|
|
|
|
clean_up:
|
|
kfree(pv);
|
|
return err;
|
|
}
|
|
|
|
/* Must be protected by RTNL.
|
|
* Must be called with vid in range from 1 to 4094 inclusive.
|
|
*/
|
|
int nbp_vlan_delete(struct net_bridge_port *port, u16 vid)
|
|
{
|
|
struct net_port_vlans *pv;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
pv = rtnl_dereference(port->vlan_info);
|
|
if (!pv)
|
|
return -EINVAL;
|
|
|
|
br_fdb_find_delete_local(port->br, port, port->dev->dev_addr, vid);
|
|
|
|
return __vlan_del(pv, vid);
|
|
}
|
|
|
|
void nbp_vlan_flush(struct net_bridge_port *port)
|
|
{
|
|
struct net_port_vlans *pv;
|
|
u16 vid;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
pv = rtnl_dereference(port->vlan_info);
|
|
if (!pv)
|
|
return;
|
|
|
|
for_each_set_bit(vid, pv->vlan_bitmap, VLAN_N_VID)
|
|
vlan_vid_del(port->dev, htons(ETH_P_8021Q), vid);
|
|
|
|
__vlan_flush(pv);
|
|
}
|
|
|
|
bool nbp_vlan_find(struct net_bridge_port *port, u16 vid)
|
|
{
|
|
struct net_port_vlans *pv;
|
|
bool found = false;
|
|
|
|
rcu_read_lock();
|
|
pv = rcu_dereference(port->vlan_info);
|
|
|
|
if (!pv)
|
|
goto out;
|
|
|
|
if (test_bit(vid, pv->vlan_bitmap))
|
|
found = true;
|
|
|
|
out:
|
|
rcu_read_unlock();
|
|
return found;
|
|
}
|