aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVlad Yasevich <vyasevic@redhat.com>2014-08-08 14:42:13 -0400
committerDavid S. Miller <davem@davemloft.net>2014-08-11 15:16:51 -0400
commit0d5501c1c828fb97d02af50aa9d2b1a5498b94e4 (patch)
tree2ac5399cd619d7d802605a3de67a4f80886bc665
parent217e606bf69c0c9f2ef25def38e9f2a8969578f5 (diff)
net: Always untag vlan-tagged traffic on input.
Currently the functionality to untag traffic on input resides as part of the vlan module and is build only when VLAN support is enabled in the kernel. When VLAN is disabled, the function vlan_untag() turns into a stub and doesn't really untag the packets. This seems to create an interesting interaction between VMs supporting checksum offloading and some network drivers. There are some drivers that do not allow the user to change tx-vlan-offload feature of the driver. These drivers also seem to assume that any VLAN-tagged traffic they transmit will have the vlan information in the vlan_tci and not in the vlan header already in the skb. When transmitting skbs that already have tagged data with partial checksum set, the checksum doesn't appear to be updated correctly by the card thus resulting in a failure to establish TCP connections. The following is a packet trace taken on the receiver where a sender is a VM with a VLAN configued. The host VM is running on doest not have VLAN support and the outging interface on the host is tg3: 10:12:43.503055 52:54:00:ae:42:3f > 28:d2:44:7d:c2:de, ethertype 802.1Q (0x8100), length 78: vlan 100, p 0, ethertype IPv4, (tos 0x0, ttl 64, id 27243, offset 0, flags [DF], proto TCP (6), length 60) 10.0.100.1.58545 > 10.0.100.10.ircu-2: Flags [S], cksum 0xdc39 (incorrect -> 0x48d9), seq 1069378582, win 29200, options [mss 1460,sackOK,TS val 4294837885 ecr 0,nop,wscale 7], length 0 10:12:44.505556 52:54:00:ae:42:3f > 28:d2:44:7d:c2:de, ethertype 802.1Q (0x8100), length 78: vlan 100, p 0, ethertype IPv4, (tos 0x0, ttl 64, id 27244, offset 0, flags [DF], proto TCP (6), length 60) 10.0.100.1.58545 > 10.0.100.10.ircu-2: Flags [S], cksum 0xdc39 (incorrect -> 0x44ee), seq 1069378582, win 29200, options [mss 1460,sackOK,TS val 4294838888 ecr 0,nop,wscale 7], length 0 This connection finally times out. I've only access to the TG3 hardware in this configuration thus have only tested this with TG3 driver. There are a lot of other drivers that do not permit user changes to vlan acceleration features, and I don't know if they all suffere from a similar issue. The patch attempt to fix this another way. It moves the vlan header stipping code out of the vlan module and always builds it into the kernel network core. This way, even if vlan is not supported on a virtualizatoin host, the virtual machines running on top of such host will still work with VLANs enabled. CC: Patrick McHardy <kaber@trash.net> CC: Nithin Nayak Sujir <nsujir@broadcom.com> CC: Michael Chan <mchan@broadcom.com> CC: Jiri Pirko <jiri@resnulli.us> Signed-off-by: Vladislav Yasevich <vyasevic@redhat.com> Acked-by: Jiri Pirko <jiri@resnulli.us> Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--include/linux/if_vlan.h6
-rw-r--r--include/linux/skbuff.h1
-rw-r--r--net/8021q/vlan_core.c53
-rw-r--r--net/bridge/br_vlan.c2
-rw-r--r--net/core/dev.c2
-rw-r--r--net/core/skbuff.c53
6 files changed, 56 insertions, 61 deletions
diff --git a/include/linux/if_vlan.h b/include/linux/if_vlan.h
index 4967916fe4ac..d69f0577a319 100644
--- a/include/linux/if_vlan.h
+++ b/include/linux/if_vlan.h
@@ -187,7 +187,6 @@ vlan_dev_get_egress_qos_mask(struct net_device *dev, u32 skprio)
187} 187}
188 188
189extern bool vlan_do_receive(struct sk_buff **skb); 189extern bool vlan_do_receive(struct sk_buff **skb);
190extern struct sk_buff *vlan_untag(struct sk_buff *skb);
191 190
192extern int vlan_vid_add(struct net_device *dev, __be16 proto, u16 vid); 191extern int vlan_vid_add(struct net_device *dev, __be16 proto, u16 vid);
193extern void vlan_vid_del(struct net_device *dev, __be16 proto, u16 vid); 192extern void vlan_vid_del(struct net_device *dev, __be16 proto, u16 vid);
@@ -241,11 +240,6 @@ static inline bool vlan_do_receive(struct sk_buff **skb)
241 return false; 240 return false;
242} 241}
243 242
244static inline struct sk_buff *vlan_untag(struct sk_buff *skb)
245{
246 return skb;
247}
248
249static inline int vlan_vid_add(struct net_device *dev, __be16 proto, u16 vid) 243static inline int vlan_vid_add(struct net_device *dev, __be16 proto, u16 vid)
250{ 244{
251 return 0; 245 return 0;
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 11c270551d25..abde271c18ae 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -2555,6 +2555,7 @@ int skb_shift(struct sk_buff *tgt, struct sk_buff *skb, int shiftlen);
2555void skb_scrub_packet(struct sk_buff *skb, bool xnet); 2555void skb_scrub_packet(struct sk_buff *skb, bool xnet);
2556unsigned int skb_gso_transport_seglen(const struct sk_buff *skb); 2556unsigned int skb_gso_transport_seglen(const struct sk_buff *skb);
2557struct sk_buff *skb_segment(struct sk_buff *skb, netdev_features_t features); 2557struct sk_buff *skb_segment(struct sk_buff *skb, netdev_features_t features);
2558struct sk_buff *skb_vlan_untag(struct sk_buff *skb);
2558 2559
2559struct skb_checksum_ops { 2560struct skb_checksum_ops {
2560 __wsum (*update)(const void *mem, int len, __wsum wsum); 2561 __wsum (*update)(const void *mem, int len, __wsum wsum);
diff --git a/net/8021q/vlan_core.c b/net/8021q/vlan_core.c
index 75d427763992..90cc2bdd4064 100644
--- a/net/8021q/vlan_core.c
+++ b/net/8021q/vlan_core.c
@@ -112,59 +112,6 @@ __be16 vlan_dev_vlan_proto(const struct net_device *dev)
112} 112}
113EXPORT_SYMBOL(vlan_dev_vlan_proto); 113EXPORT_SYMBOL(vlan_dev_vlan_proto);
114 114
115static struct sk_buff *vlan_reorder_header(struct sk_buff *skb)
116{
117 if (skb_cow(skb, skb_headroom(skb)) < 0) {
118 kfree_skb(skb);
119 return NULL;
120 }
121
122 memmove(skb->data - ETH_HLEN, skb->data - VLAN_ETH_HLEN, 2 * ETH_ALEN);
123 skb->mac_header += VLAN_HLEN;
124 return skb;
125}
126
127struct sk_buff *vlan_untag(struct sk_buff *skb)
128{
129 struct vlan_hdr *vhdr;
130 u16 vlan_tci;
131
132 if (unlikely(vlan_tx_tag_present(skb))) {
133 /* vlan_tci is already set-up so leave this for another time */
134 return skb;
135 }
136
137 skb = skb_share_check(skb, GFP_ATOMIC);
138 if (unlikely(!skb))
139 goto err_free;
140
141 if (unlikely(!pskb_may_pull(skb, VLAN_HLEN)))
142 goto err_free;
143
144 vhdr = (struct vlan_hdr *) skb->data;
145 vlan_tci = ntohs(vhdr->h_vlan_TCI);
146 __vlan_hwaccel_put_tag(skb, skb->protocol, vlan_tci);
147
148 skb_pull_rcsum(skb, VLAN_HLEN);
149 vlan_set_encap_proto(skb, vhdr);
150
151 skb = vlan_reorder_header(skb);
152 if (unlikely(!skb))
153 goto err_free;
154
155 skb_reset_network_header(skb);
156 skb_reset_transport_header(skb);
157 skb_reset_mac_len(skb);
158
159 return skb;
160
161err_free:
162 kfree_skb(skb);
163 return NULL;
164}
165EXPORT_SYMBOL(vlan_untag);
166
167
168/* 115/*
169 * vlan info and vid list 116 * vlan info and vid list
170 */ 117 */
diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
index febb0f87fa37..e1bcd653899b 100644
--- a/net/bridge/br_vlan.c
+++ b/net/bridge/br_vlan.c
@@ -181,7 +181,7 @@ bool br_allowed_ingress(struct net_bridge *br, struct net_port_vlans *v,
181 */ 181 */
182 if (unlikely(!vlan_tx_tag_present(skb) && 182 if (unlikely(!vlan_tx_tag_present(skb) &&
183 skb->protocol == proto)) { 183 skb->protocol == proto)) {
184 skb = vlan_untag(skb); 184 skb = skb_vlan_untag(skb);
185 if (unlikely(!skb)) 185 if (unlikely(!skb))
186 return false; 186 return false;
187 } 187 }
diff --git a/net/core/dev.c b/net/core/dev.c
index 1c15b189c52b..b65a5051361f 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -3602,7 +3602,7 @@ another_round:
3602 3602
3603 if (skb->protocol == cpu_to_be16(ETH_P_8021Q) || 3603 if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||
3604 skb->protocol == cpu_to_be16(ETH_P_8021AD)) { 3604 skb->protocol == cpu_to_be16(ETH_P_8021AD)) {
3605 skb = vlan_untag(skb); 3605 skb = skb_vlan_untag(skb);
3606 if (unlikely(!skb)) 3606 if (unlikely(!skb))
3607 goto unlock; 3607 goto unlock;
3608 } 3608 }
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 224506a6fa80..163b673f9e62 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -62,6 +62,7 @@
62#include <linux/scatterlist.h> 62#include <linux/scatterlist.h>
63#include <linux/errqueue.h> 63#include <linux/errqueue.h>
64#include <linux/prefetch.h> 64#include <linux/prefetch.h>
65#include <linux/if_vlan.h>
65 66
66#include <net/protocol.h> 67#include <net/protocol.h>
67#include <net/dst.h> 68#include <net/dst.h>
@@ -3973,3 +3974,55 @@ unsigned int skb_gso_transport_seglen(const struct sk_buff *skb)
3973 return shinfo->gso_size; 3974 return shinfo->gso_size;
3974} 3975}
3975EXPORT_SYMBOL_GPL(skb_gso_transport_seglen); 3976EXPORT_SYMBOL_GPL(skb_gso_transport_seglen);
3977
3978static struct sk_buff *skb_reorder_vlan_header(struct sk_buff *skb)
3979{
3980 if (skb_cow(skb, skb_headroom(skb)) < 0) {
3981 kfree_skb(skb);
3982 return NULL;
3983 }
3984
3985 memmove(skb->data - ETH_HLEN, skb->data - VLAN_ETH_HLEN, 2 * ETH_ALEN);
3986 skb->mac_header += VLAN_HLEN;
3987 return skb;
3988}
3989
3990struct sk_buff *skb_vlan_untag(struct sk_buff *skb)
3991{
3992 struct vlan_hdr *vhdr;
3993 u16 vlan_tci;
3994
3995 if (unlikely(vlan_tx_tag_present(skb))) {
3996 /* vlan_tci is already set-up so leave this for another time */
3997 return skb;
3998 }
3999
4000 skb = skb_share_check(skb, GFP_ATOMIC);
4001 if (unlikely(!skb))
4002 goto err_free;
4003
4004 if (unlikely(!pskb_may_pull(skb, VLAN_HLEN)))
4005 goto err_free;
4006
4007 vhdr = (struct vlan_hdr *)skb->data;
4008 vlan_tci = ntohs(vhdr->h_vlan_TCI);
4009 __vlan_hwaccel_put_tag(skb, skb->protocol, vlan_tci);
4010
4011 skb_pull_rcsum(skb, VLAN_HLEN);
4012 vlan_set_encap_proto(skb, vhdr);
4013
4014 skb = skb_reorder_vlan_header(skb);
4015 if (unlikely(!skb))
4016 goto err_free;
4017
4018 skb_reset_network_header(skb);
4019 skb_reset_transport_header(skb);
4020 skb_reset_mac_len(skb);
4021
4022 return skb;
4023
4024err_free:
4025 kfree_skb(skb);
4026 return NULL;
4027}
4028EXPORT_SYMBOL(skb_vlan_untag);