net: vlan: add 802.1ad support
authorPatrick McHardy <kaber@trash.net>
Fri, 19 Apr 2013 02:04:31 +0000 (02:04 +0000)
committerDavid S. Miller <davem@davemloft.net>
Fri, 19 Apr 2013 18:46:06 +0000 (14:46 -0400)
Add support for 802.1ad VLAN devices. This mainly consists of checking for
ETH_P_8021AD in addition to ETH_P_8021Q in a couple of places and check
offloading capabilities based on the used protocol.

Configuration is done using "ip link":

# ip link add link eth0 eth0.1000 \
type vlan proto 802.1ad id 1000
# ip link add link eth0.1000 eth0.1000.1000 \
type vlan proto 802.1q id 1000

52:54:00:12:34:56 > 92:b1:54:28:e4:8c, ethertype 802.1Q (0x8100), length 106: vlan 1000, p 0, ethertype 802.1Q, vlan 1000, p 0, ethertype IPv4, (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto ICMP (1), length 84)
    20.1.0.2 > 20.1.0.1: ICMP echo request, id 3003, seq 8, length 64
92:b1:54:28:e4:8c > 52:54:00:12:34:56, ethertype 802.1Q-QinQ (0x88a8), length 106: vlan 1000, p 0, ethertype 802.1Q, vlan 1000, p 0, ethertype IPv4, (tos 0x0, ttl 64, id 47944, offset 0, flags [none], proto ICMP (1), length 84)
    20.1.0.1 > 20.1.0.2: ICMP echo reply, id 3003, seq 8, length 64

Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/if_vlan.h
include/linux/netdev_features.h
include/uapi/linux/if_link.h
net/8021q/Kconfig
net/8021q/vlan.h
net/8021q/vlan_core.c
net/8021q/vlan_netlink.c
net/core/dev.c

index 8086ff9988b10b73113137871a7a0cc908b63e57..a78f9390da87b9adb234a57198fa9ce225ccf0f7 100644 (file)
@@ -162,6 +162,8 @@ static inline bool vlan_hw_offload_capable(netdev_features_t features,
 {
        if (proto == htons(ETH_P_8021Q) && features & NETIF_F_HW_VLAN_CTAG_TX)
                return true;
+       if (proto == htons(ETH_P_8021AD) && features & NETIF_F_HW_VLAN_STAG_TX)
+               return true;
        return false;
 }
 
@@ -271,9 +273,9 @@ static inline int __vlan_get_tag(const struct sk_buff *skb, u16 *vlan_tci)
 {
        struct vlan_ethhdr *veth = (struct vlan_ethhdr *)skb->data;
 
-       if (veth->h_vlan_proto != htons(ETH_P_8021Q)) {
+       if (veth->h_vlan_proto != htons(ETH_P_8021Q) &&
+           veth->h_vlan_proto != htons(ETH_P_8021AD))
                return -EINVAL;
-       }
 
        *vlan_tci = ntohs(veth->h_vlan_TCI);
        return 0;
index 785913b8983da6f35e27d2f3bdea1a913a2f4c73..cbaa027ef5a772e583593b6b3ab538dd3feff62a 100644 (file)
@@ -25,6 +25,9 @@ enum {
        NETIF_F_HW_VLAN_CTAG_TX_BIT,    /* Transmit VLAN CTAG HW acceleration */
        NETIF_F_HW_VLAN_CTAG_RX_BIT,    /* Receive VLAN CTAG HW acceleration */
        NETIF_F_HW_VLAN_CTAG_FILTER_BIT,/* Receive filtering on VLAN CTAGs */
+       NETIF_F_HW_VLAN_STAG_TX_BIT,    /* Transmit VLAN STAG HW acceleration */
+       NETIF_F_HW_VLAN_STAG_RX_BIT,    /* Receive VLAN STAG HW acceleration */
+       NETIF_F_HW_VLAN_STAG_FILTER_BIT,/* Receive filtering on VLAN STAGs */
        NETIF_F_VLAN_CHALLENGED_BIT,    /* Device cannot handle VLAN packets */
        NETIF_F_GSO_BIT,                /* Enable software GSO. */
        NETIF_F_LLTX_BIT,               /* LockLess TX - deprecated. Please */
@@ -83,6 +86,9 @@ enum {
 #define NETIF_F_HW_VLAN_CTAG_FILTER __NETIF_F(HW_VLAN_CTAG_FILTER)
 #define NETIF_F_HW_VLAN_CTAG_RX        __NETIF_F(HW_VLAN_CTAG_RX)
 #define NETIF_F_HW_VLAN_CTAG_TX        __NETIF_F(HW_VLAN_CTAG_TX)
+#define NETIF_F_HW_VLAN_STAG_FILTER __NETIF_F(HW_VLAN_STAG_FILTER)
+#define NETIF_F_HW_VLAN_STAG_RX        __NETIF_F(HW_VLAN_STAG_RX)
+#define NETIF_F_HW_VLAN_STAG_TX        __NETIF_F(HW_VLAN_STAG_TX)
 #define NETIF_F_IP_CSUM                __NETIF_F(IP_CSUM)
 #define NETIF_F_IPV6_CSUM      __NETIF_F(IPV6_CSUM)
 #define NETIF_F_LLTX           __NETIF_F(LLTX)
index 9922704f08af5ff251f76902855cf5940f9187f3..e3163544f3391be88ce4a98b1673a763d4e304c9 100644 (file)
@@ -250,6 +250,7 @@ enum {
        IFLA_VLAN_FLAGS,
        IFLA_VLAN_EGRESS_QOS,
        IFLA_VLAN_INGRESS_QOS,
+       IFLA_VLAN_PROTOCOL,
        __IFLA_VLAN_MAX,
 };
 
index 8f7517df41a5a1d5d101d33b1e81517006019d8d..b85a91fa61f1eb0d24b20387e607596833e54595 100644 (file)
@@ -3,7 +3,7 @@
 #
 
 config VLAN_8021Q
-       tristate "802.1Q VLAN Support"
+       tristate "802.1Q/802.1ad VLAN Support"
        ---help---
          Select this and you will be able to create 802.1Q VLAN interfaces
          on your ethernet interfaces.  802.1Q VLAN supports almost
index 245de9653db0b8752da5ed7c6861e74a2769e45a..abc9cb631c47ec44accd90c04137dffe944d284e 100644 (file)
@@ -91,6 +91,7 @@ static inline struct vlan_dev_priv *vlan_dev_priv(const struct net_device *dev)
 
 enum vlan_protos {
        VLAN_PROTO_8021Q        = 0,
+       VLAN_PROTO_8021AD,
        VLAN_PROTO_NUM,
 };
 
@@ -116,6 +117,8 @@ static inline unsigned int vlan_proto_idx(__be16 proto)
        switch (proto) {
        case __constant_htons(ETH_P_8021Q):
                return VLAN_PROTO_8021Q;
+       case __constant_htons(ETH_P_8021AD):
+               return VLAN_PROTO_8021AD;
        default:
                BUG();
        }
index bdb0b9d2e9cfbdc0c914d0233fbaf44f2b67821e..ebfa2fceb88b4f51f9a58bc2a80e6d7cb1241794 100644 (file)
@@ -194,6 +194,18 @@ struct vlan_vid_info {
        int refcount;
 };
 
+static bool vlan_hw_filter_capable(const struct net_device *dev,
+                                    const struct vlan_vid_info *vid_info)
+{
+       if (vid_info->proto == htons(ETH_P_8021Q) &&
+           dev->features & NETIF_F_HW_VLAN_CTAG_FILTER)
+               return true;
+       if (vid_info->proto == htons(ETH_P_8021AD) &&
+           dev->features & NETIF_F_HW_VLAN_STAG_FILTER)
+               return true;
+       return false;
+}
+
 static struct vlan_vid_info *vlan_vid_info_get(struct vlan_info *vlan_info,
                                               __be16 proto, u16 vid)
 {
@@ -231,8 +243,7 @@ static int __vlan_vid_add(struct vlan_info *vlan_info, __be16 proto, u16 vid,
        if (!vid_info)
                return -ENOMEM;
 
-       if (proto == htons(ETH_P_8021Q) &&
-           dev->features & NETIF_F_HW_VLAN_CTAG_FILTER) {
+       if (vlan_hw_filter_capable(dev, vid_info)) {
                err =  ops->ndo_vlan_rx_add_vid(dev, proto, vid);
                if (err) {
                        kfree(vid_info);
@@ -290,8 +301,7 @@ static void __vlan_vid_del(struct vlan_info *vlan_info,
        u16 vid = vid_info->vid;
        int err;
 
-       if (proto == htons(ETH_P_8021Q) &&
-           dev->features & NETIF_F_HW_VLAN_CTAG_FILTER) {
+       if (vlan_hw_filter_capable(dev, vid_info)) {
                err = ops->ndo_vlan_rx_kill_vid(dev, proto, vid);
                if (err) {
                        pr_warn("failed to kill vid %04x/%d for device %s\n",
index a1a956ab39a593a0db539bbd25f96f6d126b4232..309129732285fd610159446bade5ede27c835d1c 100644 (file)
@@ -23,6 +23,7 @@ static const struct nla_policy vlan_policy[IFLA_VLAN_MAX + 1] = {
        [IFLA_VLAN_FLAGS]       = { .len = sizeof(struct ifla_vlan_flags) },
        [IFLA_VLAN_EGRESS_QOS]  = { .type = NLA_NESTED },
        [IFLA_VLAN_INGRESS_QOS] = { .type = NLA_NESTED },
+       [IFLA_VLAN_PROTOCOL]    = { .type = NLA_U16 },
 };
 
 static const struct nla_policy vlan_map_policy[IFLA_VLAN_QOS_MAX + 1] = {
@@ -53,6 +54,16 @@ static int vlan_validate(struct nlattr *tb[], struct nlattr *data[])
        if (!data)
                return -EINVAL;
 
+       if (data[IFLA_VLAN_PROTOCOL]) {
+               switch (nla_get_be16(data[IFLA_VLAN_PROTOCOL])) {
+               case __constant_htons(ETH_P_8021Q):
+               case __constant_htons(ETH_P_8021AD):
+                       break;
+               default:
+                       return -EPROTONOSUPPORT;
+               }
+       }
+
        if (data[IFLA_VLAN_ID]) {
                id = nla_get_u16(data[IFLA_VLAN_ID]);
                if (id >= VLAN_VID_MASK)
@@ -107,6 +118,7 @@ static int vlan_newlink(struct net *src_net, struct net_device *dev,
 {
        struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
        struct net_device *real_dev;
+       __be16 proto;
        int err;
 
        if (!data[IFLA_VLAN_ID])
@@ -118,7 +130,12 @@ static int vlan_newlink(struct net *src_net, struct net_device *dev,
        if (!real_dev)
                return -ENODEV;
 
-       vlan->vlan_proto = htons(ETH_P_8021Q);
+       if (data[IFLA_VLAN_PROTOCOL])
+               proto = nla_get_be16(data[IFLA_VLAN_PROTOCOL]);
+       else
+               proto = htons(ETH_P_8021Q);
+
+       vlan->vlan_proto = proto;
        vlan->vlan_id    = nla_get_u16(data[IFLA_VLAN_ID]);
        vlan->real_dev   = real_dev;
        vlan->flags      = VLAN_FLAG_REORDER_HDR;
@@ -152,7 +169,8 @@ static size_t vlan_get_size(const struct net_device *dev)
 {
        struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
 
-       return nla_total_size(2) +      /* IFLA_VLAN_ID */
+       return nla_total_size(2) +      /* IFLA_VLAN_PROTOCOL */
+              nla_total_size(2) +      /* IFLA_VLAN_ID */
               sizeof(struct ifla_vlan_flags) + /* IFLA_VLAN_FLAGS */
               vlan_qos_map_size(vlan->nr_ingress_mappings) +
               vlan_qos_map_size(vlan->nr_egress_mappings);
@@ -167,7 +185,8 @@ static int vlan_fill_info(struct sk_buff *skb, const struct net_device *dev)
        struct nlattr *nest;
        unsigned int i;
 
-       if (nla_put_u16(skb, IFLA_VLAN_ID, vlan_dev_priv(dev)->vlan_id))
+       if (nla_put_be16(skb, IFLA_VLAN_PROTOCOL, vlan->vlan_proto) ||
+           nla_put_u16(skb, IFLA_VLAN_ID, vlan->vlan_id))
                goto nla_put_failure;
        if (vlan->flags) {
                f.flags = vlan->flags;
index 3a12ee132b596869336c8eadc24dd3e155377ab1..fad4c385f7a1d7cd9820ef0cbdddcd1dc322d521 100644 (file)
@@ -2212,7 +2212,7 @@ __be16 skb_network_protocol(struct sk_buff *skb)
        __be16 type = skb->protocol;
        int vlan_depth = ETH_HLEN;
 
-       while (type == htons(ETH_P_8021Q)) {
+       while (type == htons(ETH_P_8021Q) || type == htons(ETH_P_8021AD)) {
                struct vlan_hdr *vh;
 
                if (unlikely(!pskb_may_pull(skb, vlan_depth + VLAN_HLEN)))
@@ -2428,20 +2428,22 @@ netdev_features_t netif_skb_features(struct sk_buff *skb)
        if (skb_shinfo(skb)->gso_segs > skb->dev->gso_max_segs)
                features &= ~NETIF_F_GSO_MASK;
 
-       if (protocol == htons(ETH_P_8021Q)) {
+       if (protocol == htons(ETH_P_8021Q) || protocol == htons(ETH_P_8021AD)) {
                struct vlan_ethhdr *veh = (struct vlan_ethhdr *)skb->data;
                protocol = veh->h_vlan_encapsulated_proto;
        } else if (!vlan_tx_tag_present(skb)) {
                return harmonize_features(skb, protocol, features);
        }
 
-       features &= (skb->dev->vlan_features | NETIF_F_HW_VLAN_CTAG_TX);
+       features &= (skb->dev->vlan_features | NETIF_F_HW_VLAN_CTAG_TX |
+                                              NETIF_F_HW_VLAN_STAG_TX);
 
-       if (protocol != htons(ETH_P_8021Q)) {
+       if (protocol != htons(ETH_P_8021Q) && protocol != htons(ETH_P_8021AD)) {
                return harmonize_features(skb, protocol, features);
        } else {
                features &= NETIF_F_SG | NETIF_F_HIGHDMA | NETIF_F_FRAGLIST |
-                               NETIF_F_GEN_CSUM | NETIF_F_HW_VLAN_CTAG_TX;
+                               NETIF_F_GEN_CSUM | NETIF_F_HW_VLAN_CTAG_TX |
+                               NETIF_F_HW_VLAN_STAG_TX;
                return harmonize_features(skb, protocol, features);
        }
 }
@@ -3360,6 +3362,7 @@ static bool skb_pfmemalloc_protocol(struct sk_buff *skb)
        case __constant_htons(ETH_P_IP):
        case __constant_htons(ETH_P_IPV6):
        case __constant_htons(ETH_P_8021Q):
+       case __constant_htons(ETH_P_8021AD):
                return true;
        default:
                return false;
@@ -3400,7 +3403,8 @@ another_round:
 
        __this_cpu_inc(softnet_data.processed);
 
-       if (skb->protocol == cpu_to_be16(ETH_P_8021Q)) {
+       if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||
+           skb->protocol == cpu_to_be16(ETH_P_8021AD)) {
                skb = vlan_untag(skb);
                if (unlikely(!skb))
                        goto unlock;