Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a430a43d authored by Herbert Xu's avatar Herbert Xu Committed by David S. Miller
Browse files

[NET] gso: Fix up GSO packets with broken checksums



Certain subsystems in the stack (e.g., netfilter) can break the partial
checksum on GSO packets.  Until they're fixed, this patch allows this to
work by recomputing the partial checksums through the GSO mechanism.

Once they've all been converted to update the partial checksum instead of
clearing it, this workaround can be removed.

Signed-off-by: default avatarHerbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 89114afd
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -549,6 +549,7 @@ struct packet_type {
					 struct net_device *);
	struct sk_buff		*(*gso_segment)(struct sk_buff *skb,
						int features);
	int			(*gso_send_check)(struct sk_buff *skb);
	void			*af_packet_priv;
	struct list_head	list;
};
@@ -1001,13 +1002,14 @@ static inline int net_gso_ok(int features, int gso_type)

static inline int skb_gso_ok(struct sk_buff *skb, int features)
{
	return net_gso_ok(features, skb_is_gso(skb) ?
				    skb_shinfo(skb)->gso_type : 0);
	return net_gso_ok(features, skb_shinfo(skb)->gso_type);
}

static inline int netif_needs_gso(struct net_device *dev, struct sk_buff *skb)
{
	return !skb_gso_ok(skb, dev->features);
	return skb_is_gso(skb) &&
	       (!skb_gso_ok(skb, dev->features) ||
		unlikely(skb->ip_summed != CHECKSUM_HW));
}

#endif /* __KERNEL__ */
+2 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@
struct net_protocol {
	int			(*handler)(struct sk_buff *skb);
	void			(*err_handler)(struct sk_buff *skb, u32 info);
	int			(*gso_send_check)(struct sk_buff *skb);
	struct sk_buff	       *(*gso_segment)(struct sk_buff *skb,
					       int features);
	int			no_policy;
@@ -51,6 +52,7 @@ struct inet6_protocol
			       int type, int code, int offset,
			       __u32 info);

	int	(*gso_send_check)(struct sk_buff *skb);
	struct sk_buff *(*gso_segment)(struct sk_buff *skb,
				       int features);

+1 −0
Original line number Diff line number Diff line
@@ -1086,6 +1086,7 @@ extern struct request_sock_ops tcp_request_sock_ops;

extern int tcp_v4_destroy_sock(struct sock *sk);

extern int tcp_v4_gso_send_check(struct sk_buff *skb);
extern struct sk_buff *tcp_tso_segment(struct sk_buff *skb, int features);

#ifdef CONFIG_PROC_FS
+32 −4
Original line number Diff line number Diff line
@@ -1162,9 +1162,17 @@ int skb_checksum_help(struct sk_buff *skb, int inward)
	unsigned int csum;
	int ret = 0, offset = skb->h.raw - skb->data;

	if (inward) {
		skb->ip_summed = CHECKSUM_NONE;
		goto out;
	if (inward)
		goto out_set_summed;

	if (unlikely(skb_shinfo(skb)->gso_size)) {
		static int warned;

		WARN_ON(!warned);
		warned = 1;

		/* Let GSO fix up the checksum. */
		goto out_set_summed;
	}

	if (skb_cloned(skb)) {
@@ -1181,6 +1189,8 @@ int skb_checksum_help(struct sk_buff *skb, int inward)
	BUG_ON(skb->csum + 2 > offset);

	*(u16*)(skb->h.raw + skb->csum) = csum_fold(csum);

out_set_summed:
	skb->ip_summed = CHECKSUM_NONE;
out:	
	return ret;
@@ -1201,17 +1211,35 @@ struct sk_buff *skb_gso_segment(struct sk_buff *skb, int features)
	struct sk_buff *segs = ERR_PTR(-EPROTONOSUPPORT);
	struct packet_type *ptype;
	int type = skb->protocol;
	int err;

	BUG_ON(skb_shinfo(skb)->frag_list);
	BUG_ON(skb->ip_summed != CHECKSUM_HW);

	skb->mac.raw = skb->data;
	skb->mac_len = skb->nh.raw - skb->data;
	__skb_pull(skb, skb->mac_len);

	if (unlikely(skb->ip_summed != CHECKSUM_HW)) {
		static int warned;

		WARN_ON(!warned);
		warned = 1;

		if (skb_header_cloned(skb) &&
		    (err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC)))
			return ERR_PTR(err);
	}

	rcu_read_lock();
	list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type) & 15], list) {
		if (ptype->type == type && !ptype->dev && ptype->gso_segment) {
			if (unlikely(skb->ip_summed != CHECKSUM_HW)) {
				err = ptype->gso_send_check(skb);
				segs = ERR_PTR(err);
				if (err || skb_gso_ok(skb, features))
					break;
				__skb_push(skb, skb->data - skb->nh.raw);
			}
			segs = ptype->gso_segment(skb, features);
			break;
		}
+36 −0
Original line number Diff line number Diff line
@@ -1097,6 +1097,40 @@ int inet_sk_rebuild_header(struct sock *sk)

EXPORT_SYMBOL(inet_sk_rebuild_header);

static int inet_gso_send_check(struct sk_buff *skb)
{
	struct iphdr *iph;
	struct net_protocol *ops;
	int proto;
	int ihl;
	int err = -EINVAL;

	if (unlikely(!pskb_may_pull(skb, sizeof(*iph))))
		goto out;

	iph = skb->nh.iph;
	ihl = iph->ihl * 4;
	if (ihl < sizeof(*iph))
		goto out;

	if (unlikely(!pskb_may_pull(skb, ihl)))
		goto out;

	skb->h.raw = __skb_pull(skb, ihl);
	iph = skb->nh.iph;
	proto = iph->protocol & (MAX_INET_PROTOS - 1);
	err = -EPROTONOSUPPORT;

	rcu_read_lock();
	ops = rcu_dereference(inet_protos[proto]);
	if (likely(ops && ops->gso_send_check))
		err = ops->gso_send_check(skb);
	rcu_read_unlock();

out:
	return err;
}

static struct sk_buff *inet_gso_segment(struct sk_buff *skb, int features)
{
	struct sk_buff *segs = ERR_PTR(-EINVAL);
@@ -1162,6 +1196,7 @@ static struct net_protocol igmp_protocol = {
static struct net_protocol tcp_protocol = {
	.handler =	tcp_v4_rcv,
	.err_handler =	tcp_v4_err,
	.gso_send_check = tcp_v4_gso_send_check,
	.gso_segment =	tcp_tso_segment,
	.no_policy =	1,
};
@@ -1208,6 +1243,7 @@ static int ipv4_proc_init(void);
static struct packet_type ip_packet_type = {
	.type = __constant_htons(ETH_P_IP),
	.func = ip_rcv,
	.gso_send_check = inet_gso_send_check,
	.gso_segment = inet_gso_segment,
};

Loading