diff options
author | David S. Miller <davem@davemloft.net> | 2013-12-31 16:23:35 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-01-15 18:28:49 -0500 |
commit | c726095ec74daabd48a9a4ed48d46304601017b4 (patch) | |
tree | 5455a5a161d1230c07e8e1c26b643a0497ab55a8 | |
parent | 05df3bfbc832046a774fce22654cc55260cd1583 (diff) |
vlan: Fix header ops passthru when doing TX VLAN offload.
[ Upstream commit 2205369a314e12fcec4781cc73ac9c08fc2b47de ]
When the vlan code detects that the real device can do TX VLAN offloads
in hardware, it tries to arrange for the real device's header_ops to
be invoked directly.
But it does so illegally, by simply hooking the real device's
header_ops up to the VLAN device.
This doesn't work because we will end up invoking a set of header_ops
routines which expect a device type which matches the real device, but
will see a VLAN device instead.
Fix this by providing a pass-thru set of header_ops which will arrange
to pass the proper real device instead.
To facilitate this add a dev_rebuild_header(). There are
implementations which provide a ->cache and ->create but not a
->rebuild (f.e. PLIP). So we need a helper function just like
dev_hard_header() to avoid crashes.
Use this helper in the one existing place where the
header_ops->rebuild was being invoked, the neighbour code.
With lots of help from Florian Westphal.
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | include/linux/netdevice.h | 9 | ||||
-rw-r--r-- | net/8021q/vlan_dev.c | 19 | ||||
-rw-r--r-- | net/core/neighbour.c | 2 |
3 files changed, 28 insertions, 2 deletions
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 96e4c21e15e0..abf7756eaf9e 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h | |||
@@ -1772,6 +1772,15 @@ static inline int dev_parse_header(const struct sk_buff *skb, | |||
1772 | return dev->header_ops->parse(skb, haddr); | 1772 | return dev->header_ops->parse(skb, haddr); |
1773 | } | 1773 | } |
1774 | 1774 | ||
1775 | static inline int dev_rebuild_header(struct sk_buff *skb) | ||
1776 | { | ||
1777 | const struct net_device *dev = skb->dev; | ||
1778 | |||
1779 | if (!dev->header_ops || !dev->header_ops->rebuild) | ||
1780 | return 0; | ||
1781 | return dev->header_ops->rebuild(skb); | ||
1782 | } | ||
1783 | |||
1775 | typedef int gifconf_func_t(struct net_device * dev, char __user * bufptr, int len); | 1784 | typedef int gifconf_func_t(struct net_device * dev, char __user * bufptr, int len); |
1776 | extern int register_gifconf(unsigned int family, gifconf_func_t * gifconf); | 1785 | extern int register_gifconf(unsigned int family, gifconf_func_t * gifconf); |
1777 | static inline int unregister_gifconf(unsigned int family) | 1786 | static inline int unregister_gifconf(unsigned int family) |
diff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c index 1cd3d2a406f5..4af64afc7022 100644 --- a/net/8021q/vlan_dev.c +++ b/net/8021q/vlan_dev.c | |||
@@ -549,6 +549,23 @@ static const struct header_ops vlan_header_ops = { | |||
549 | .parse = eth_header_parse, | 549 | .parse = eth_header_parse, |
550 | }; | 550 | }; |
551 | 551 | ||
552 | static int vlan_passthru_hard_header(struct sk_buff *skb, struct net_device *dev, | ||
553 | unsigned short type, | ||
554 | const void *daddr, const void *saddr, | ||
555 | unsigned int len) | ||
556 | { | ||
557 | struct vlan_dev_priv *vlan = vlan_dev_priv(dev); | ||
558 | struct net_device *real_dev = vlan->real_dev; | ||
559 | |||
560 | return dev_hard_header(skb, real_dev, type, daddr, saddr, len); | ||
561 | } | ||
562 | |||
563 | static const struct header_ops vlan_passthru_header_ops = { | ||
564 | .create = vlan_passthru_hard_header, | ||
565 | .rebuild = dev_rebuild_header, | ||
566 | .parse = eth_header_parse, | ||
567 | }; | ||
568 | |||
552 | static struct device_type vlan_type = { | 569 | static struct device_type vlan_type = { |
553 | .name = "vlan", | 570 | .name = "vlan", |
554 | }; | 571 | }; |
@@ -592,7 +609,7 @@ static int vlan_dev_init(struct net_device *dev) | |||
592 | 609 | ||
593 | dev->needed_headroom = real_dev->needed_headroom; | 610 | dev->needed_headroom = real_dev->needed_headroom; |
594 | if (real_dev->features & NETIF_F_HW_VLAN_CTAG_TX) { | 611 | if (real_dev->features & NETIF_F_HW_VLAN_CTAG_TX) { |
595 | dev->header_ops = real_dev->header_ops; | 612 | dev->header_ops = &vlan_passthru_header_ops; |
596 | dev->hard_header_len = real_dev->hard_header_len; | 613 | dev->hard_header_len = real_dev->hard_header_len; |
597 | } else { | 614 | } else { |
598 | dev->header_ops = &vlan_header_ops; | 615 | dev->header_ops = &vlan_header_ops; |
diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 0034b611fa5e..49aeab86f317 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c | |||
@@ -1274,7 +1274,7 @@ int neigh_compat_output(struct neighbour *neigh, struct sk_buff *skb) | |||
1274 | 1274 | ||
1275 | if (dev_hard_header(skb, dev, ntohs(skb->protocol), NULL, NULL, | 1275 | if (dev_hard_header(skb, dev, ntohs(skb->protocol), NULL, NULL, |
1276 | skb->len) < 0 && | 1276 | skb->len) < 0 && |
1277 | dev->header_ops->rebuild(skb)) | 1277 | dev_rebuild_header(skb)) |
1278 | return 0; | 1278 | return 0; |
1279 | 1279 | ||
1280 | return dev_queue_xmit(skb); | 1280 | return dev_queue_xmit(skb); |