Skip to content

Commit

Permalink
vlan: Fix header ops passthru when doing TX VLAN offload.
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
David S. Miller committed Dec 31, 2013
1 parent 71ce176 commit 2205369
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 2 deletions.
9 changes: 9 additions & 0 deletions include/linux/netdevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -1912,6 +1912,15 @@ static inline int dev_parse_header(const struct sk_buff *skb,
return dev->header_ops->parse(skb, haddr);
}

static inline int dev_rebuild_header(struct sk_buff *skb)
{
const struct net_device *dev = skb->dev;

if (!dev->header_ops || !dev->header_ops->rebuild)
return 0;
return dev->header_ops->rebuild(skb);
}

typedef int gifconf_func_t(struct net_device * dev, char __user * bufptr, int len);
int register_gifconf(unsigned int family, gifconf_func_t *gifconf);
static inline int unregister_gifconf(unsigned int family)
Expand Down
19 changes: 18 additions & 1 deletion net/8021q/vlan_dev.c
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,23 @@ static const struct header_ops vlan_header_ops = {
.parse = eth_header_parse,
};

static int vlan_passthru_hard_header(struct sk_buff *skb, struct net_device *dev,
unsigned short type,
const void *daddr, const void *saddr,
unsigned int len)
{
struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
struct net_device *real_dev = vlan->real_dev;

return dev_hard_header(skb, real_dev, type, daddr, saddr, len);
}

static const struct header_ops vlan_passthru_header_ops = {
.create = vlan_passthru_hard_header,
.rebuild = dev_rebuild_header,
.parse = eth_header_parse,
};

static struct device_type vlan_type = {
.name = "vlan",
};
Expand Down Expand Up @@ -573,7 +590,7 @@ static int vlan_dev_init(struct net_device *dev)

dev->needed_headroom = real_dev->needed_headroom;
if (real_dev->features & NETIF_F_HW_VLAN_CTAG_TX) {
dev->header_ops = real_dev->header_ops;
dev->header_ops = &vlan_passthru_header_ops;
dev->hard_header_len = real_dev->hard_header_len;
} else {
dev->header_ops = &vlan_header_ops;
Expand Down
2 changes: 1 addition & 1 deletion net/core/neighbour.c
Original file line number Diff line number Diff line change
Expand Up @@ -1275,7 +1275,7 @@ int neigh_compat_output(struct neighbour *neigh, struct sk_buff *skb)

if (dev_hard_header(skb, dev, ntohs(skb->protocol), NULL, NULL,
skb->len) < 0 &&
dev->header_ops->rebuild(skb))
dev_rebuild_header(skb))
return 0;

return dev_queue_xmit(skb);
Expand Down

0 comments on commit 2205369

Please sign in to comment.