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

Commit 673195ca authored by Sean Tranchetti's avatar Sean Tranchetti Committed by Gerrit - the friendly Code Review server
Browse files

net: qualcomm: rmnet: Set partial checksum on single segments



When segmenting a single packet out of an RSB/RSC frame, the L4 checksum
of the original transport header copied into the packet will not be
correct, and since this is a single packet, CHECKSUM_UNNECESSARY will be
stamped instead of calculating all the GSO and partial checksum
information.

This can cause issues when sending these single packets back out to the
network during forwarding scenarios as the L4 checksum will not be updated
by the network stack before being resent to the driver.

Change-Id: Ibbbd9d0422d14e6647f26e5a75b138e9f5b3495e
Signed-off-by: default avatarSean Tranchetti <stranche@codeaurora.org>
parent 508ddee6
Loading
Loading
Loading
Loading
+20 −7
Original line number Diff line number Diff line
@@ -375,12 +375,28 @@ static void rmnet_frag_gso_stamp(struct sk_buff *skb,
				 struct rmnet_frag_descriptor *frag_desc)
{
	struct skb_shared_info *shinfo = skb_shinfo(skb);

	if (frag_desc->trans_proto == IPPROTO_TCP)
		shinfo->gso_type = (frag_desc->ip_proto == 4) ?
				   SKB_GSO_TCPV4 : SKB_GSO_TCPV6;
	else
		shinfo->gso_type = SKB_GSO_UDP_L4;

	shinfo->gso_size = frag_desc->gso_size;
	shinfo->gso_segs = frag_desc->gso_segs;
}

/* Set the partial checksum information. Sets the transport checksum tot he
 * pseudoheader checksum and sets the offload metadata.
 */
static void rmnet_frag_partial_csum(struct sk_buff *skb,
				    struct rmnet_frag_descriptor *frag_desc)
{
	struct iphdr *iph = (struct iphdr *)skb->data;
	__sum16 pseudo;
	u16 pkt_len = skb->len - frag_desc->ip_len;
	bool ipv4 = frag_desc->ip_proto == 4;

	if (ipv4) {
	if (frag_desc->ip_proto == 4) {
		iph->tot_len = htons(skb->len);
		iph->check = 0;
		iph->check = ip_fast_csum(iph, iph->ihl);
@@ -401,7 +417,6 @@ static void rmnet_frag_gso_stamp(struct sk_buff *skb,
				    ((u8 *)iph + frag_desc->ip_len);

		tp->check = pseudo;
		shinfo->gso_type = (ipv4) ? SKB_GSO_TCPV4 : SKB_GSO_TCPV6;
		skb->csum_offset = offsetof(struct tcphdr, check);
	} else {
		struct udphdr *up = (struct udphdr *)
@@ -409,14 +424,11 @@ static void rmnet_frag_gso_stamp(struct sk_buff *skb,

		up->len = htons(pkt_len);
		up->check = pseudo;
		shinfo->gso_type = SKB_GSO_UDP_L4;
		skb->csum_offset = offsetof(struct udphdr, check);
	}

	skb->ip_summed = CHECKSUM_PARTIAL;
	skb->csum_start = (u8 *)iph + frag_desc->ip_len - skb->head;
	shinfo->gso_size = frag_desc->gso_size;
	shinfo->gso_segs = frag_desc->gso_segs;
}

/* Allocate and populate an skb to contain the packet represented by the
@@ -542,7 +554,8 @@ static struct sk_buff *rmnet_alloc_skb(struct rmnet_frag_descriptor *frag_desc,

	/* Handle csum offloading */
	if (frag_desc->csum_valid) {
		head_skb->ip_summed = CHECKSUM_UNNECESSARY;
		/* Set the partial checksum information */
		rmnet_frag_partial_csum(head_skb, frag_desc);
	} else if (frag_desc->hdrs_valid &&
		   (frag_desc->trans_proto == IPPROTO_TCP ||
		    frag_desc->trans_proto == IPPROTO_UDP)) {
+23 −8
Original line number Diff line number Diff line
@@ -656,12 +656,28 @@ static void rmnet_map_gso_stamp(struct sk_buff *skb,
				struct rmnet_map_coal_metadata *coal_meta)
{
	struct skb_shared_info *shinfo = skb_shinfo(skb);

	if (coal_meta->trans_proto == IPPROTO_TCP)
		shinfo->gso_type = (coal_meta->ip_proto == 4) ?
				   SKB_GSO_TCPV4 : SKB_GSO_TCPV6;
	else
		shinfo->gso_type = SKB_GSO_UDP_L4;

	shinfo->gso_size = coal_meta->data_len;
	shinfo->gso_segs = coal_meta->pkt_count;
}

/* Handles setting up the partial checksum in the skb. Sets the transport
 * checksum to the pseudoheader checksum and sets the csum offload metadata
 */
static void rmnet_map_partial_csum(struct sk_buff *skb,
				   struct rmnet_map_coal_metadata *coal_meta)
{
	unsigned char *data = skb->data;
	__sum16 pseudo;
	u16 pkt_len = skb->len - coal_meta->ip_len;
	bool ipv4 = coal_meta->ip_proto == 4;

	if (ipv4) {
	if (coal_meta->ip_proto == 4) {
		struct iphdr *iph = (struct iphdr *)data;

		pseudo = ~csum_tcpudp_magic(iph->saddr, iph->daddr,
@@ -678,20 +694,16 @@ static void rmnet_map_gso_stamp(struct sk_buff *skb,
		struct tcphdr *tp = (struct tcphdr *)(data + coal_meta->ip_len);

		tp->check = pseudo;
		shinfo->gso_type = (ipv4) ? SKB_GSO_TCPV4 : SKB_GSO_TCPV6;
		skb->csum_offset = offsetof(struct tcphdr, check);
	} else {
		struct udphdr *up = (struct udphdr *)(data + coal_meta->ip_len);

		up->check = pseudo;
		shinfo->gso_type = SKB_GSO_UDP_L4;
		skb->csum_offset = offsetof(struct udphdr, check);
	}

	skb->ip_summed = CHECKSUM_PARTIAL;
	skb->csum_start = skb->data + coal_meta->ip_len - skb->head;
	shinfo->gso_size = coal_meta->data_len;
	shinfo->gso_segs = coal_meta->pkt_count;
}

static void
@@ -756,7 +768,8 @@ __rmnet_map_segment_coal_skb(struct sk_buff *coal_skb,

	/* Handle checksum status */
	if (likely(csum_valid)) {
		skbn->ip_summed = CHECKSUM_UNNECESSARY;
		/* Set the partial checksum information */
		rmnet_map_partial_csum(skbn, coal_meta);
	} else if (check) {
		/* Unfortunately, we have to fake a bad checksum here, since
		 * the original bad value is lost by the hardware. The only
@@ -938,8 +951,10 @@ static void rmnet_map_segment_coal_skb(struct sk_buff *coal_skb,
		coal_meta.data_len = ntohs(coal_hdr->nl_pairs[0].pkt_len);
		coal_meta.data_len -= coal_meta.ip_len + coal_meta.trans_len;
		coal_meta.pkt_count = coal_hdr->nl_pairs[0].num_packets;
		if (coal_meta.pkt_count > 1)
		if (coal_meta.pkt_count > 1) {
			rmnet_map_partial_csum(coal_skb, &coal_meta);
			rmnet_map_gso_stamp(coal_skb, &coal_meta);
		}

		__skb_queue_tail(list, coal_skb);
		return;