Commit e0ce4af9 authored by Ian Campbell's avatar Ian Campbell Committed by David S. Miller

xen: netfront: handle incoming GSO SKBs which are not CHECKSUM_PARTIAL

The Linux network stack expects all GSO SKBs to have ip_summed ==
CHECKSUM_PARTIAL (which implies that the frame contains a partial
checksum) and the Xen network ring protocol similarly expects an SKB
which has GSO set to also have NETRX_csum_blank (which also implies a
partial checksum).

However there have been cases of buggy guests which mark a frame as
GSO but do not set csum_blank. If we detect that we a receiving such a
frame (which manifests as ip_summed != PARTIAL && skb_is_gso) then
force the SKB to partial and recalculate the checksum, since we cannot
rely on the peer having done so if they have not set csum_blank.

Add an ethtool stat to track occurances of this event.
Signed-off-by: default avatarIan Campbell <ian.campbell@citrix.com>
Cc: Jeremy Fitzhardinge <jeremy@goop.org>
Cc: David Miller <davem@davemloft.net>
Cc: xen-devel@lists.xensource.com
Cc: netdev@vger.kernel.org
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 389f2a18
...@@ -120,6 +120,9 @@ struct netfront_info { ...@@ -120,6 +120,9 @@ struct netfront_info {
unsigned long rx_pfn_array[NET_RX_RING_SIZE]; unsigned long rx_pfn_array[NET_RX_RING_SIZE];
struct multicall_entry rx_mcl[NET_RX_RING_SIZE+1]; struct multicall_entry rx_mcl[NET_RX_RING_SIZE+1];
struct mmu_update rx_mmu[NET_RX_RING_SIZE]; struct mmu_update rx_mmu[NET_RX_RING_SIZE];
/* Statistics */
int rx_gso_checksum_fixup;
}; };
struct netfront_rx_info { struct netfront_rx_info {
...@@ -770,11 +773,29 @@ static RING_IDX xennet_fill_frags(struct netfront_info *np, ...@@ -770,11 +773,29 @@ static RING_IDX xennet_fill_frags(struct netfront_info *np,
return cons; return cons;
} }
static int skb_checksum_setup(struct sk_buff *skb) static int checksum_setup(struct net_device *dev, struct sk_buff *skb)
{ {
struct iphdr *iph; struct iphdr *iph;
unsigned char *th; unsigned char *th;
int err = -EPROTO; int err = -EPROTO;
int recalculate_partial_csum = 0;
/*
* A GSO SKB must be CHECKSUM_PARTIAL. However some buggy
* peers can fail to set NETRXF_csum_blank when sending a GSO
* frame. In this case force the SKB to CHECKSUM_PARTIAL and
* recalculate the partial checksum.
*/
if (skb->ip_summed != CHECKSUM_PARTIAL && skb_is_gso(skb)) {
struct netfront_info *np = netdev_priv(dev);
np->rx_gso_checksum_fixup++;
skb->ip_summed = CHECKSUM_PARTIAL;
recalculate_partial_csum = 1;
}
/* A non-CHECKSUM_PARTIAL SKB does not require setup. */
if (skb->ip_summed != CHECKSUM_PARTIAL)
return 0;
if (skb->protocol != htons(ETH_P_IP)) if (skb->protocol != htons(ETH_P_IP))
goto out; goto out;
...@@ -788,9 +809,23 @@ static int skb_checksum_setup(struct sk_buff *skb) ...@@ -788,9 +809,23 @@ static int skb_checksum_setup(struct sk_buff *skb)
switch (iph->protocol) { switch (iph->protocol) {
case IPPROTO_TCP: case IPPROTO_TCP:
skb->csum_offset = offsetof(struct tcphdr, check); skb->csum_offset = offsetof(struct tcphdr, check);
if (recalculate_partial_csum) {
struct tcphdr *tcph = (struct tcphdr *)th;
tcph->check = ~csum_tcpudp_magic(iph->saddr, iph->daddr,
skb->len - iph->ihl*4,
IPPROTO_TCP, 0);
}
break; break;
case IPPROTO_UDP: case IPPROTO_UDP:
skb->csum_offset = offsetof(struct udphdr, check); skb->csum_offset = offsetof(struct udphdr, check);
if (recalculate_partial_csum) {
struct udphdr *udph = (struct udphdr *)th;
udph->check = ~csum_tcpudp_magic(iph->saddr, iph->daddr,
skb->len - iph->ihl*4,
IPPROTO_UDP, 0);
}
break; break;
default: default:
if (net_ratelimit()) if (net_ratelimit())
...@@ -829,13 +864,11 @@ static int handle_incoming_queue(struct net_device *dev, ...@@ -829,13 +864,11 @@ static int handle_incoming_queue(struct net_device *dev,
/* Ethernet work: Delayed to here as it peeks the header. */ /* Ethernet work: Delayed to here as it peeks the header. */
skb->protocol = eth_type_trans(skb, dev); skb->protocol = eth_type_trans(skb, dev);
if (skb->ip_summed == CHECKSUM_PARTIAL) { if (checksum_setup(dev, skb)) {
if (skb_checksum_setup(skb)) { kfree_skb(skb);
kfree_skb(skb); packets_dropped++;
packets_dropped++; dev->stats.rx_errors++;
dev->stats.rx_errors++; continue;
continue;
}
} }
dev->stats.rx_packets++; dev->stats.rx_packets++;
...@@ -1632,12 +1665,59 @@ static void netback_changed(struct xenbus_device *dev, ...@@ -1632,12 +1665,59 @@ static void netback_changed(struct xenbus_device *dev,
} }
} }
static const struct xennet_stat {
char name[ETH_GSTRING_LEN];
u16 offset;
} xennet_stats[] = {
{
"rx_gso_checksum_fixup",
offsetof(struct netfront_info, rx_gso_checksum_fixup)
},
};
static int xennet_get_sset_count(struct net_device *dev, int string_set)
{
switch (string_set) {
case ETH_SS_STATS:
return ARRAY_SIZE(xennet_stats);
default:
return -EINVAL;
}
}
static void xennet_get_ethtool_stats(struct net_device *dev,
struct ethtool_stats *stats, u64 * data)
{
void *np = netdev_priv(dev);
int i;
for (i = 0; i < ARRAY_SIZE(xennet_stats); i++)
data[i] = *(int *)(np + xennet_stats[i].offset);
}
static void xennet_get_strings(struct net_device *dev, u32 stringset, u8 * data)
{
int i;
switch (stringset) {
case ETH_SS_STATS:
for (i = 0; i < ARRAY_SIZE(xennet_stats); i++)
memcpy(data + i * ETH_GSTRING_LEN,
xennet_stats[i].name, ETH_GSTRING_LEN);
break;
}
}
static const struct ethtool_ops xennet_ethtool_ops = static const struct ethtool_ops xennet_ethtool_ops =
{ {
.set_tx_csum = ethtool_op_set_tx_csum, .set_tx_csum = ethtool_op_set_tx_csum,
.set_sg = xennet_set_sg, .set_sg = xennet_set_sg,
.set_tso = xennet_set_tso, .set_tso = xennet_set_tso,
.get_link = ethtool_op_get_link, .get_link = ethtool_op_get_link,
.get_sset_count = xennet_get_sset_count,
.get_ethtool_stats = xennet_get_ethtool_stats,
.get_strings = xennet_get_strings,
}; };
#ifdef CONFIG_SYSFS #ifdef CONFIG_SYSFS
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment