Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[sfrench/cifs-2.6.git] / net / core / dev.c
index 1ba2cfe3f8e88f59a7bcd65136c7af744e1f8d95..5367bfba094781bc74f7e67853e96d4105948534 100644 (file)
@@ -2289,8 +2289,8 @@ EXPORT_SYMBOL(skb_checksum_help);
 
 __be16 skb_network_protocol(struct sk_buff *skb, int *depth)
 {
+       unsigned int vlan_depth = skb->mac_len;
        __be16 type = skb->protocol;
-       int vlan_depth = skb->mac_len;
 
        /* Tunnel gso handlers can set protocol to ethernet. */
        if (type == htons(ETH_P_TEB)) {
@@ -2303,15 +2303,30 @@ __be16 skb_network_protocol(struct sk_buff *skb, int *depth)
                type = eth->h_proto;
        }
 
-       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)))
-                       return 0;
-
-               vh = (struct vlan_hdr *)(skb->data + vlan_depth);
-               type = vh->h_vlan_encapsulated_proto;
-               vlan_depth += VLAN_HLEN;
+       /* if skb->protocol is 802.1Q/AD then the header should already be
+        * present at mac_len - VLAN_HLEN (if mac_len > 0), or at
+        * ETH_HLEN otherwise
+        */
+       if (type == htons(ETH_P_8021Q) || type == htons(ETH_P_8021AD)) {
+               if (vlan_depth) {
+                       if (unlikely(WARN_ON(vlan_depth < VLAN_HLEN)))
+                               return 0;
+                       vlan_depth -= VLAN_HLEN;
+               } else {
+                       vlan_depth = ETH_HLEN;
+               }
+               do {
+                       struct vlan_hdr *vh;
+
+                       if (unlikely(!pskb_may_pull(skb,
+                                                   vlan_depth + VLAN_HLEN)))
+                               return 0;
+
+                       vh = (struct vlan_hdr *)(skb->data + vlan_depth);
+                       type = vh->h_vlan_encapsulated_proto;
+                       vlan_depth += VLAN_HLEN;
+               } while (type == htons(ETH_P_8021Q) ||
+                        type == htons(ETH_P_8021AD));
        }
 
        *depth = vlan_depth;