summaryrefslogtreecommitdiff
path: root/net/bridge/br_vlan.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2013-02-21 06:58:50 +0400
committerLinus Torvalds <torvalds@linux-foundation.org>2013-02-21 06:58:50 +0400
commita0b1c42951dd06ec83cc1bc2c9788131d9fefcd8 (patch)
treea572f1523cf904c93020c9cdb32f3bc84ec3ac16 /net/bridge/br_vlan.c
parent8ec4942212a6d337982967778a3dc3b60aea782e (diff)
parentecd9883724b78cc72ed92c98bcb1a46c764fff21 (diff)
downloadlinux-a0b1c42951dd06ec83cc1bc2c9788131d9fefcd8.tar.xz
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next
Pull networking update from David Miller: 1) Checkpoint/restarted TCP sockets now can properly propagate the TCP timestamp offset. From Andrey Vagin. 2) VMWARE VM VSOCK layer, from Andy King. 3) Much improved support for virtual functions and SR-IOV in bnx2x, from Ariel ELior. 4) All protocols on ipv4 and ipv6 are now network namespace aware, and all the compatability checks for initial-namespace-only protocols is removed. Thanks to Tom Parkin for helping deal with the last major holdout, L2TP. 5) IPV6 support in netpoll and network namespace support in pktgen, from Cong Wang. 6) Multiple Registration Protocol (MRP) and Multiple VLAN Registration Protocol (MVRP) support, from David Ward. 7) Compute packet lengths more accurately in the packet scheduler, from Eric Dumazet. 8) Use per-task page fragment allocator in skb_append_datato_frags(), also from Eric Dumazet. 9) Add support for connection tracking labels in netfilter, from Florian Westphal. 10) Fix default multicast group joining on ipv6, and add anti-spoofing checks to 6to4 and 6rd. From Hannes Frederic Sowa. 11) Make ipv4/ipv6 fragmentation memory limits more reasonable in modern times, rearrange inet frag datastructures for better cacheline locality, and move more operations outside of locking. From Jesper Dangaard Brouer. 12) Instead of strict master <--> slave relationships, allow arbitrary scenerios with "upper device lists". From Jiri Pirko. 13) Improve rate limiting accuracy in TBF and act_police, also from Jiri Pirko. 14) Add a BPF filter netfilter match target, from Willem de Bruijn. 15) Orphan and delete a bunch of pre-historic networking drivers from Paul Gortmaker. 16) Add TSO support for GRE tunnels, from Pravin B SHelar. Although this still needs some minor bug fixing before it's %100 correct in all cases. 17) Handle unresolved IPSEC states like ARP, with a resolution packet queue. From Steffen Klassert. 18) Remove TCP Appropriate Byte Count support (ABC), from Stephen Hemminger. This was long overdue. 19) Support SO_REUSEPORT, from Tom Herbert. 20) Allow locking a socket BPF filter, so that it cannot change after a process drops capabilities. 21) Add VLAN filtering to bridge, from Vlad Yasevich. 22) Bring ipv6 on-par with ipv4 and do not cache neighbour entries in the ipv6 routes, from YOSHIFUJI Hideaki. * git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next: (1538 commits) ipv6: fix race condition regarding dst->expires and dst->from. net: fix a wrong assignment in skb_split() ip_gre: remove an extra dst_release() ppp: set qdisc_tx_busylock to avoid LOCKDEP splat atl1c: restore buffer state net: fix a build failure when !CONFIG_PROC_FS net: ipv4: fix waring -Wunused-variable net: proc: fix build failed when procfs is not configured Revert "xen: netback: remove redundant xenvif_put" net: move procfs code to net/core/net-procfs.c qmi_wwan, cdc-ether: add ADU960S bonding: set sysfs device_type to 'bond' bonding: fix bond_release_all inconsistencies b44: use netdev_alloc_skb_ip_align() xen: netback: remove redundant xenvif_put net: fec: Do a sanity check on the gpio number ip_gre: propogate target device GSO capability to the tunnel device ip_gre: allow CSUM capable devices to handle packets bonding: Fix initialize after use for 3ad machine state spinlock bonding: Fix race condition between bond_enslave() and bond_3ad_update_lacp_rate() ...
Diffstat (limited to 'net/bridge/br_vlan.c')
-rw-r--r--net/bridge/br_vlan.c415
1 files changed, 415 insertions, 0 deletions
diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
new file mode 100644
index 000000000000..93dde75923f0
--- /dev/null
+++ b/net/bridge/br_vlan.c
@@ -0,0 +1,415 @@
+#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 (vid) {
+ if (v->port_idx) {
+ p = v->parent.port;
+ br = p->br;
+ dev = p->dev;
+ } else {
+ br = v->parent.br;
+ dev = br->dev;
+ }
+
+ if (p && (dev->features & NETIF_F_HW_VLAN_FILTER)) {
+ /* 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 = dev->netdev_ops->ndo_vlan_rx_add_vid(dev, 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 && (dev->features & NETIF_F_HW_VLAN_FILTER))
+ dev->netdev_ops->ndo_vlan_rx_kill_vid(dev, 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 && vid) {
+ struct net_device *dev = v->parent.port->dev;
+
+ if (dev->features & NETIF_F_HW_VLAN_FILTER)
+ dev->netdev_ops->ndo_vlan_rx_kill_vid(dev, vid);
+ }
+
+ clear_bit(vid, v->vlan_bitmap);
+ v->num_vlans--;
+ if (bitmap_empty(v->vlan_bitmap, BR_VLAN_BITMAP_LEN)) {
+ if (v->port_idx)
+ rcu_assign_pointer(v->parent.port->vlan_info, NULL);
+ else
+ rcu_assign_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, BR_VLAN_BITMAP_LEN);
+ if (v->port_idx)
+ rcu_assign_pointer(v->parent.port->vlan_info, NULL);
+ else
+ rcu_assign_pointer(v->parent.br->vlan_info, NULL);
+ kfree_rcu(v, rcu);
+}
+
+/* Strip the tag from the packet. Will return skb with tci set 0. */
+static struct sk_buff *br_vlan_untag(struct sk_buff *skb)
+{
+ if (skb->protocol != htons(ETH_P_8021Q)) {
+ skb->vlan_tci = 0;
+ return skb;
+ }
+
+ skb->vlan_tci = 0;
+ skb = vlan_untag(skb);
+ if (skb)
+ skb->vlan_tci = 0;
+
+ return skb;
+}
+
+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;
+
+ /* 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 taged.
+ */
+ br_vlan_get_tag(skb, &vid);
+ if (test_bit(vid, pv->untagged_bitmap))
+ skb = br_vlan_untag(skb);
+ else {
+ /* Egress policy says "send tagged". If output device
+ * is the bridge, we need to add the VLAN header
+ * ourselves since we'll be going through the RX path.
+ * Sending to ports puts the frame on the TX path and
+ * we let dev_hard_start_xmit() add the header.
+ */
+ if (skb->protocol != htons(ETH_P_8021Q) &&
+ pv->port_idx == 0) {
+ /* vlan_put_tag expects skb->data to point to
+ * mac header.
+ */
+ skb_push(skb, ETH_HLEN);
+ skb = __vlan_put_tag(skb, skb->vlan_tci);
+ if (!skb)
+ goto out;
+ /* put skb->data back to where it was */
+ skb_pull(skb, ETH_HLEN);
+ 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)
+{
+ /* 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)
+ return false;
+
+ if (br_vlan_get_tag(skb, vid)) {
+ u16 pvid = br_get_pvid(v);
+
+ /* Frame did not have a tag. See if pvid is set
+ * on this port. That tells us which vlan untagged
+ * traffic belongs to.
+ */
+ if (pvid == VLAN_N_VID)
+ return false;
+
+ /* PVID is set on this port. Any untagged ingress
+ * frame is considered to belong to this vlan.
+ */
+ __vlan_hwaccel_put_tag(skb, pvid);
+ return true;
+ }
+
+ /* Frame had a valid vlan tag. See if vlan is allowed */
+ if (test_bit(*vid, v->vlan_bitmap))
+ return true;
+
+ 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 */
+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 */
+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;
+
+ if (vid) {
+ /* If the VID !=0 remove fdb for this vid. VID 0 is special
+ * in that it's the default and is always there in the fdb.
+ */
+ spin_lock_bh(&br->hash_lock);
+ fdb_delete_by_addr(br, br->dev->dev_addr, vid);
+ spin_unlock_bh(&br->hash_lock);
+ }
+
+ __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);
+}
+
+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;
+
+unlock:
+ rtnl_unlock();
+ return 0;
+}
+
+/* Must be protected by RTNL */
+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 */
+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;
+
+ if (vid) {
+ /* If the VID !=0 remove fdb for this vid. VID 0 is special
+ * in that it's the default and is always there in the fdb.
+ */
+ spin_lock_bh(&port->br->hash_lock);
+ fdb_delete_by_addr(port->br, port->dev->dev_addr, vid);
+ spin_unlock_bh(&port->br->hash_lock);
+ }
+
+ return __vlan_del(pv, vid);
+}
+
+void nbp_vlan_flush(struct net_bridge_port *port)
+{
+ struct net_port_vlans *pv;
+
+ ASSERT_RTNL();
+
+ pv = rtnl_dereference(port->vlan_info);
+ if (!pv)
+ return;
+
+ __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;
+}