cregit-Linux how code gets into the kernel

Release 4.11 net/bridge/br_vlan.c

Directory: net/bridge
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
#include <linux/slab.h>
#include <net/switchdev.h>

#include "br_private.h"
#include "br_private_tunnel.h"


static inline int br_vlan_cmp(struct rhashtable_compare_arg *arg, const void *ptr) { const struct net_bridge_vlan *vle = ptr; u16 vid = *(u16 *)arg->key; return vle->vid != vid; }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov44100.00%1100.00%
Total44100.00%1100.00%

static const struct rhashtable_params br_vlan_rht_params = { .head_offset = offsetof(struct net_bridge_vlan, vnode), .key_offset = offsetof(struct net_bridge_vlan, vid), .key_len = sizeof(u16), .nelem_hint = 3, .locks_mul = 1, .max_size = VLAN_N_VID, .obj_cmpfn = br_vlan_cmp, .automatic_shrinking = true, };
static struct net_bridge_vlan *br_vlan_lookup(struct rhashtable *tbl, u16 vid) { return rhashtable_lookup_fast(tbl, &vid, br_vlan_rht_params); }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov27100.00%1100.00%
Total27100.00%1100.00%


static void __vlan_add_pvid(struct net_bridge_vlan_group *vg, u16 vid) { if (vg->pvid == vid) return; smp_wmb(); vg->pvid = vid; }

Contributors

PersonTokensPropCommitsCommitProp
Vlad Yasevich2578.12%150.00%
Nikolay Aleksandrov721.88%150.00%
Total32100.00%2100.00%


static void __vlan_delete_pvid(struct net_bridge_vlan_group *vg, u16 vid) { if (vg->pvid != vid) return; smp_wmb(); vg->pvid = 0; }

Contributors

PersonTokensPropCommitsCommitProp
Vlad Yasevich2578.12%150.00%
Nikolay Aleksandrov721.88%150.00%
Total32100.00%2100.00%


static void __vlan_add_flags(struct net_bridge_vlan *v, u16 flags) { struct net_bridge_vlan_group *vg; if (br_vlan_is_master(v)) vg = br_vlan_group(v->br); else vg = nbp_vlan_group(v->port); if (flags & BRIDGE_VLAN_INFO_PVID) __vlan_add_pvid(vg, v->vid); else __vlan_delete_pvid(vg, v->vid); if (flags & BRIDGE_VLAN_INFO_UNTAGGED) v->flags |= BRIDGE_VLAN_INFO_UNTAGGED; else v->flags &= ~BRIDGE_VLAN_INFO_UNTAGGED; }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov5763.33%360.00%
Vlad Yasevich3336.67%240.00%
Total90100.00%5100.00%


static int __vlan_vid_add(struct net_device *dev, struct net_bridge *br, u16 vid, u16 flags) { struct switchdev_obj_port_vlan v = { .obj.orig_dev = dev, .obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN, .flags = flags, .vid_begin = vid, .vid_end = vid, }; int err; /* Try switchdev op first. In case it is not supported, fallback to * 8021q add. */ err = switchdev_port_obj_add(dev, &v.obj); if (err == -EOPNOTSUPP) return vlan_vid_add(dev, br->vlan_proto, vid); return err; }

Contributors

PersonTokensPropCommitsCommitProp
Scott Feldman5962.11%225.00%
Jiri Pirko2728.42%450.00%
Ido Schimmel77.37%112.50%
Vivien Didelot22.11%112.50%
Total95100.00%8100.00%


static void __vlan_add_list(struct net_bridge_vlan *v) { struct net_bridge_vlan_group *vg; struct list_head *headp, *hpos; struct net_bridge_vlan *vent; if (br_vlan_is_master(v)) vg = br_vlan_group(v->br); else vg = nbp_vlan_group(v->port); headp = &vg->vlan_list; list_for_each_prev(hpos, headp) { vent = list_entry(hpos, struct net_bridge_vlan, vlist); if (v->vid < vent->vid) continue; else break; } list_add_rcu(&v->vlist, hpos); }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov7066.67%342.86%
Vlad Yasevich3533.33%457.14%
Total105100.00%7100.00%


static void __vlan_del_list(struct net_bridge_vlan *v) { list_del_rcu(&v->vlist); }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov1263.16%228.57%
Vlad Yasevich631.58%457.14%
Toshiaki Makita15.26%114.29%
Total19100.00%7100.00%


static int __vlan_vid_del(struct net_device *dev, struct net_bridge *br, u16 vid) { struct switchdev_obj_port_vlan v = { .obj.orig_dev = dev, .obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN, .vid_begin = vid, .vid_end = vid, }; int err; /* Try switchdev op first. In case it is not supported, fallback to * 8021q del. */ err = switchdev_port_obj_del(dev, &v.obj); if (err == -EOPNOTSUPP) { vlan_vid_del(dev, br->vlan_proto, vid); return 0; } return err; }

Contributors

PersonTokensPropCommitsCommitProp
Scott Feldman3942.86%222.22%
Jiri Pirko2931.87%444.44%
Vivien Didelot1617.58%222.22%
Ido Schimmel77.69%111.11%
Total91100.00%9100.00%

/* Returns a master vlan, if it didn't exist it gets created. In all cases a * a reference is taken to the master vlan before returning. */
static struct net_bridge_vlan *br_vlan_get_master(struct net_bridge *br, u16 vid) { struct net_bridge_vlan_group *vg; struct net_bridge_vlan *masterv; vg = br_vlan_group(br); masterv = br_vlan_find(vg, vid); if (!masterv) { /* missing global ctx, create it now */ if (br_vlan_add(br, vid, 0)) return NULL; masterv = br_vlan_find(vg, vid); if (WARN_ON(!masterv)) return NULL; } atomic_inc(&masterv->refcnt); return masterv; }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov95100.00%2100.00%
Total95100.00%2100.00%


static void br_master_vlan_rcu_free(struct rcu_head *rcu) { struct net_bridge_vlan *v; v = container_of(rcu, struct net_bridge_vlan, rcu); WARN_ON(!br_vlan_is_master(v)); free_percpu(v->stats); v->stats = NULL; kfree(v); }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov55100.00%1100.00%
Total55100.00%1100.00%


static void br_vlan_put_master(struct net_bridge_vlan *masterv) { struct net_bridge_vlan_group *vg; if (!br_vlan_is_master(masterv)) return; vg = br_vlan_group(masterv->br); if (atomic_dec_and_test(&masterv->refcnt)) { rhashtable_remove_fast(&vg->vlan_hash, &masterv->vnode, br_vlan_rht_params); __vlan_del_list(masterv); call_rcu(&masterv->rcu, br_master_vlan_rcu_free); } }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov76100.00%3100.00%
Total76100.00%3100.00%

/* This is the shared VLAN add function which works for both ports and bridge * devices. There are four possible calls to this function in terms of the * vlan entry type: * 1. vlan is being added on a port (no master flags, global entry exists) * 2. vlan is being added on a bridge (both master and brentry flags) * 3. vlan is being added on a port, but a global entry didn't exist which * is being created right now (master flag set, brentry flag unset), the * global entry is used for global per-vlan features, but not for filtering * 4. same as 3 but with both master and brentry flags set so the entry * will be used for filtering in both the port and the bridge */
static int __vlan_add(struct net_bridge_vlan *v, u16 flags) { struct net_bridge_vlan *masterv = NULL; struct net_bridge_port *p = NULL; struct net_bridge_vlan_group *vg; struct net_device *dev; struct net_bridge *br; int err; if (br_vlan_is_master(v)) { br = v->br; dev = br->dev; vg = br_vlan_group(br); } else { p = v->port; br = p->br; dev = p->dev; vg = nbp_vlan_group(p); } if (p) { /* Add VLAN to the device filter if it is supported. * This ensures tagged traffic enters the bridge when * promiscuous mode is disabled by br_manage_promisc(). */ err = __vlan_vid_add(dev, br, v->vid, flags); if (err) goto out; /* need to work on the master vlan too */ if (flags & BRIDGE_VLAN_INFO_MASTER) { err = br_vlan_add(br, v->vid, flags | BRIDGE_VLAN_INFO_BRENTRY); if (err) goto out_filt; } masterv = br_vlan_get_master(br, v->vid); if (!masterv) goto out_filt; v->brvlan = masterv; v->stats = masterv->stats; } /* Add the dev mac and count the vlan only if it's usable */ if (br_vlan_should_use(v)) { err = br_fdb_insert(br, p, dev->dev_addr, v->vid); if (err) { br_err(br, "failed insert local address into bridge forwarding table\n"); goto out_filt; } vg->num_vlans++; } err = rhashtable_lookup_insert_fast(&vg->vlan_hash, &v->vnode, br_vlan_rht_params); if (err) goto out_fdb_insert; __vlan_add_list(v); __vlan_add_flags(v, flags); out: return err; out_fdb_insert: if (br_vlan_should_use(v)) { br_fdb_find_delete_local(br, p, dev->dev_addr, v->vid); vg->num_vlans--; } out_filt: if (p) { __vlan_vid_del(dev, br, v->vid); if (masterv) { br_vlan_put_master(masterv); v->brvlan = NULL; } } goto out; }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov33895.48%685.71%
Vlad Yasevich164.52%114.29%
Total354100.00%7100.00%


static int __vlan_del(struct net_bridge_vlan *v) { struct net_bridge_vlan *masterv = v; struct net_bridge_vlan_group *vg; struct net_bridge_port *p = NULL; int err = 0; if (br_vlan_is_master(v)) { vg = br_vlan_group(v->br); } else { p = v->port; vg = nbp_vlan_group(v->port); masterv = v->brvlan; } __vlan_delete_pvid(vg, v->vid); if (p) { err = __vlan_vid_del(p->dev, p->br, v->vid); if (err) goto out; } if (br_vlan_should_use(v)) { v->flags &= ~BRIDGE_VLAN_INFO_BRENTRY; vg->num_vlans--; } if (masterv != v) { vlan_tunnel_info_del(vg, v); rhashtable_remove_fast(&vg->vlan_hash, &v->vnode, br_vlan_rht_params); __vlan_del_list(v); kfree_rcu(v, rcu); } br_vlan_put_master(masterv); out: return err; }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov12968.25%538.46%
Vlad Yasevich3820.11%323.08%
Toshiaki Makita73.70%17.69%
Roopa Prabhu73.70%17.69%
Vivien Didelot63.17%17.69%
Patrick McHardy10.53%17.69%
Scott Feldman10.53%17.69%
Total189100.00%13100.00%


static void __vlan_group_free(struct net_bridge_vlan_group *vg) { WARN_ON(!list_empty(&vg->vlan_list)); rhashtable_destroy(&vg->vlan_hash); vlan_tunnel_deinit(vg); kfree(vg); }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov3687.80%150.00%
Roopa Prabhu512.20%150.00%
Total41100.00%2100.00%


static void __vlan_flush(struct net_bridge_vlan_group *vg) { struct net_bridge_vlan *vlan, *tmp; __vlan_delete_pvid(vg, vg->pvid); list_for_each_entry_safe(vlan, tmp, &vg->vlan_list, vlist) __vlan_del(vlan); }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov2660.47%375.00%
Vlad Yasevich1739.53%125.00%
Total43100.00%4100.00%


struct sk_buff *br_handle_vlan(struct net_bridge *br, const struct net_bridge_port *p, struct net_bridge_vlan_group *vg, struct sk_buff *skb) { struct br_vlan_stats *stats; struct net_bridge_vlan *v; u16 vid; /* If this packet was not filtered at input, let it pass */ if (!BR_INPUT_SKB_CB(skb)->vlan_filtered) goto out; /* At this point, we know that the frame was filtered and contains * a valid vlan id. If the vlan id has untagged flag set, * send untagged; otherwise, send tagged. */ br_vlan_get_tag(skb, &vid); v = br_vlan_find(vg, vid); /* Vlan entry 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 (!v || !br_vlan_should_use(v)) { if ((br->dev->flags & IFF_PROMISC) && skb->dev == br->dev) { goto out; } else { kfree_skb(skb); return NULL; } } if (br->vlan_stats_enabled) { stats = this_cpu_ptr(v->stats); u64_stats_update_begin(&stats->syncp); stats->tx_bytes += skb->len; stats->tx_packets++; u64_stats_update_end(&stats->syncp); } if (v->flags & BRIDGE_VLAN_INFO_UNTAGGED) skb->vlan_tci = 0; if (p && (p->flags & BR_VLAN_TUNNEL) && br_handle_egress_vlan_tunnel(skb, v)) { kfree_skb(skb); return NULL; } out: return skb; }

Contributors

PersonTokensPropCommitsCommitProp
Vlad Yasevich9141.94%450.00%
Nikolay Aleksandrov8840.55%225.00%
Roopa Prabhu3516.13%112.50%
Toshiaki Makita31.38%112.50%
Total217100.00%8100.00%

/* Called under RCU */
static bool __allowed_ingress(const struct net_bridge *br, struct net_bridge_vlan_group *vg, struct sk_buff *skb, u16 *vid) { struct br_vlan_stats *stats; struct net_bridge_vlan *v; bool tagged; BR_INPUT_SKB_CB(skb)->vlan_filtered = true; /* 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(!skb_vlan_tag_present(skb) && skb->protocol == br->vlan_proto)) { skb = skb_vlan_untag(skb); if (unlikely(!skb)) return false; } if (!br_vlan_get_tag(skb, vid)) { /* Tagged frame */ if (skb->vlan_proto != br->vlan_proto) { /* Protocol-mismatch, empty out vlan_tci for new tag */ skb_push(skb, ETH_HLEN); skb = vlan_insert_tag_set_proto(skb, skb->vlan_proto, skb_vlan_tag_get(skb)); if (unlikely(!skb)) return false; skb_pull(skb, ETH_HLEN); skb_reset_mac_len(skb); *vid = 0; tagged = false; } else { tagged = true; } } else { /* Untagged frame */ tagged = false; } if (!*vid) { u16 pvid = br_get_pvid(vg); /* 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) 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(!tagged)) /* Untagged Frame. */ __vlan_hwaccel_put_tag(skb, br->vlan_proto, 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; /* if stats are disabled we can avoid the lookup */ if (!br->vlan_stats_enabled) return true; } v = br_vlan_find(vg, *vid); if (!v || !br_vlan_should_use(v)) goto drop; if (br->vlan_stats_enabled) { stats = this_cpu_ptr(v->stats); u64_stats_update_begin(&stats->syncp); stats->rx_bytes += skb->len; stats->rx_packets++; u64_stats_update_end(&stats->syncp); } return true; drop: kfree_skb(skb); return false; }

Contributors

PersonTokensPropCommitsCommitProp
Toshiaki Makita16248.07%531.25%
Nikolay Aleksandrov11032.64%318.75%
Vlad Yasevich6118.10%531.25%
Jiri Pirko30.89%212.50%
Patrick McHardy10.30%16.25%
Total337100.00%16100.00%


bool br_allowed_ingress(const struct net_bridge *br, struct net_bridge_vlan_group *vg, struct sk_buff *skb, u16 *vid) { /* If VLAN filtering is disabled on the bridge, all packets are * permitted. */ if (!br->vlan_enabled) { BR_INPUT_SKB_CB(skb)->vlan_filtered = false; return true; } return __allowed_ingress(br, vg, skb, vid); }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov3254.24%360.00%
Vlad Yasevich2745.76%240.00%
Total59100.00%5100.00%

/* Called under RCU. */
bool br_allowed_egress(struct net_bridge_vlan_group *vg, const struct sk_buff *skb) { const struct net_bridge_vlan *v; u16 vid; /* If this packet was not filtered at input, let it pass */ if (!BR_INPUT_SKB_CB(skb)->vlan_filtered) return true; br_vlan_get_tag(skb, &vid); v = br_vlan_find(vg, vid); if (v && br_vlan_should_use(v)) return true; return false; }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov4461.97%150.00%
Vlad Yasevich2738.03%150.00%
Total71100.00%2100.00%

/* Called under RCU */
bool br_should_learn(struct net_bridge_port *p, struct sk_buff *skb, u16 *vid) { struct net_bridge_vlan_group *vg; struct net_bridge *br = p->br; /* If filtering was disabled at input, let it pass. */ if (!br->vlan_enabled) return true; vg = nbp_vlan_group_rcu(p); if (!vg || !vg->num_vlans) return false; if (!br_vlan_get_tag(skb, vid) && skb->vlan_proto != br->vlan_proto) *vid = 0; if (!*vid) { *vid = br_get_pvid(vg); if (!*vid) return false; return true; } if (br_vlan_find(vg, *vid)) return true; return false; }

Contributors

PersonTokensPropCommitsCommitProp
Toshiaki Makita9975.57%220.00%
Nikolay Aleksandrov2720.61%440.00%
Vlad Yasevich43.05%330.00%
Ido Schimmel10.76%110.00%
Total131100.00%10100.00%

/* 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_bridge_vlan_group *vg; struct net_bridge_vlan *vlan; int ret; ASSERT_RTNL(); vg = br_vlan_group(br); vlan = br_vlan_find(vg, vid); if (vlan) { if (!br_vlan_is_brentry(vlan)) { /* Trying to change flags of non-existent bridge vlan */ if (!(flags & BRIDGE_VLAN_INFO_BRENTRY)) return -EINVAL; /* It was only kept for port vlans, now make it real */ ret = br_fdb_insert(br, NULL, br->dev->dev_addr, vlan->vid); if (ret) { br_err(br, "failed insert local address into bridge forwarding table\n"); return ret; } atomic_inc(&vlan->refcnt); vlan->flags |= BRIDGE_VLAN_INFO_BRENTRY; vg->num_vlans++; } __vlan_add_flags(vlan, flags); return 0; } vlan = kzalloc(sizeof(*vlan), GFP_KERNEL); if (!vlan) return -ENOMEM; vlan->stats = netdev_alloc_pcpu_stats(struct br_vlan_stats); if (!vlan->stats) { kfree(vlan); return -ENOMEM; } vlan->vid = vid; vlan->flags = flags | BRIDGE_VLAN_INFO_MASTER; vlan->flags &= ~BRIDGE_VLAN_INFO_PVID; vlan->br = br; if (flags & BRIDGE_VLAN_INFO_BRENTRY) atomic_set(&vlan->refcnt, 1); ret = __vlan_add(vlan, flags); if (ret) { free_percpu(vlan->stats); kfree(vlan); } return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov18770.30%360.00%
Vlad Yasevich7929.70%240.00%
Total266100.00%5100.00%

/* 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_bridge_vlan_group *vg; struct net_bridge_vlan *v; ASSERT_RTNL(); vg = br_vlan_group(br); v = br_vlan_find(vg, vid); if (!v || !br_vlan_is_brentry(v)) return -ENOENT; br_fdb_find_delete_local(br, NULL, br->dev->dev_addr, vid); br_fdb_delete_by_port(br, NULL, vid, 0); vlan_tunnel_info_del(vg, v); return __vlan_del(v); }

Contributors

PersonTokensPropCommitsCommitProp
Vlad Yasevich4647.92%228.57%
Nikolay Aleksandrov2930.21%228.57%
Roopa Prabhu1818.75%228.57%
Toshiaki Makita33.12%114.29%
Total96100.00%7100.00%


void br_vlan_flush(struct net_bridge *br) { struct net_bridge_vlan_group *vg; ASSERT_RTNL(); vg = br_vlan_group(br); __vlan_flush(vg); RCU_INIT_POINTER(br->vlgrp, NULL); synchronize_rcu(); __vlan_group_free(vg); }

Contributors

PersonTokensPropCommitsCommitProp
Nikolay Aleksandrov2961.70%266.67%
Vlad Yasevich1838.30%133.33%
Total47100.00%3100.00%


struct net_bridge_vlan *br_vlan_find(struct net_bridge_vlan_group *vg, u16 vid) { if (!vg) return NULL; return br_vlan_lookup(&vg->vlan_hash, vid); }

Contributors

PersonTokensPropCommitsCommitProp
Toshiaki Makita1852.94%150.00%
Nikolay Aleksandrov1647.06%150.00%
Total34100.00%2100.00%

/* Must be protected by RTNL. */
static void recalculate_group_addr(struct net_bridge *br) { if (br->group_addr_set) return; spin_lock_bh(&br->lock); if (!br->vlan_enabled || br->vlan_proto == htons(ETH_P_8021Q)) { /* Bridge Group Address */ br->group_addr[5] = 0x00; } else { /* vlan_enabled && ETH_P_8021AD */ /* Provider Bridge Group Address */ br->group_addr[5] = 0x08; } spin_unlock_bh(&br->lock); }

Contributors

PersonTokensPropCommitsCommitProp
Toshiaki Makita76100.00%1100.00%
Total76100.00%1100.00%

/* Must be protected by RTNL. */
void br_recalculate_fwd_mask(struct net_bridge *br) { if (!br->vlan_enabled || br->vlan_proto == htons(ETH_P_8021Q)) br->group_fwd_mask_required = BR_GROUPFWD_DEFAULT; else /* vlan_enabled && ETH_P_8021AD */ br->group_fwd_mask_required = BR_GROUPFWD_8021AD & ~(1u << br->group_addr[5]); }

Contributors

PersonTokensPropCommitsCommitProp
Toshiaki Makita52100.00%1100.00%
Total52100.00%1100.00%


int __br_vlan_filter_toggle(struct net_bridge *br, unsigned long val) { struct switchdev_attr attr = { .orig_dev = br->dev, .id = SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING, .flags = SWITCHDEV_F_SKIP_EOPNOTSUPP, .u.vlan_filtering = val, }; int err; if (br->vlan_enabled == val) return