Procházet zdrojové kódy

Merge "rmnet_core: Manually checksum csum_valid = 0 packets"

qctecmdr před 4 roky
rodič
revize
841892972c
1 změnil soubory, kde provedl 146 přidání a 1 odebrání
  1. 146 1
      core/rmnet_descriptor.c

+ 146 - 1
core/rmnet_descriptor.c

@@ -1441,6 +1441,140 @@ rmnet_frag_data_check_coal_header(struct rmnet_frag_descriptor *frag_desc,
 	return 0;
 }
 
+static int rmnet_frag_checksum_pkt(struct rmnet_frag_descriptor *frag_desc)
+{
+	struct rmnet_priv *priv = netdev_priv(frag_desc->dev);
+	struct rmnet_fragment *frag;
+	int offset = sizeof(struct rmnet_map_header) +
+		     sizeof(struct rmnet_map_v5_csum_header);
+	u8 *version, __version;
+	__wsum csum;
+	u16 csum_len;
+
+	version = rmnet_frag_header_ptr(frag_desc, offset, sizeof(*version),
+					&__version);
+	if (!version)
+		return -EINVAL;
+
+	if ((*version & 0xF0) == 0x40) {
+		struct iphdr *iph;
+		u8 __iph[60]; /* Max IP header size (0xF * 4) */
+
+		/* We need to access the entire IP header including options
+		 * to validate its checksum. Fortunately, the version byte
+		 * also will tell us the length, so we only need to pull
+		 * once ;)
+		 */
+		frag_desc->ip_len = (*version & 0xF) * 4;
+		iph = rmnet_frag_header_ptr(frag_desc, offset,
+					    frag_desc->ip_len,
+					    __iph);
+		if (!iph || ip_is_fragment(iph))
+			return -EINVAL;
+
+		/* Length needs to be sensible */
+		csum_len = ntohs(iph->tot_len);
+		if (csum_len > frag_desc->len - offset)
+			return -EINVAL;
+
+		csum_len -= frag_desc->ip_len;
+		/* IPv4 checksum must be valid */
+		if (ip_fast_csum((u8 *)iph, frag_desc->ip_len)) {
+			priv->stats.csum_sw++;
+			return 0;
+		}
+
+		frag_desc->ip_proto = 4;
+		frag_desc->trans_proto = iph->protocol;
+		csum = ~csum_tcpudp_magic(iph->saddr, iph->daddr,
+					  csum_len,
+					  frag_desc->trans_proto, 0);
+	} else if ((*version & 0xF0) == 0x60) {
+		struct ipv6hdr *ip6h, __ip6h;
+		int ip_len;
+		__be16 frag_off;
+		u8 protocol;
+
+		ip6h = rmnet_frag_header_ptr(frag_desc, offset, sizeof(*ip6h),
+					     &__ip6h);
+		if (!ip6h)
+			return -EINVAL;
+
+		frag_desc->ip_proto = 6;
+		protocol = ip6h->nexthdr;
+		ip_len = rmnet_frag_ipv6_skip_exthdr(frag_desc,
+						     offset + sizeof(*ip6h),
+						     &protocol, &frag_off);
+		if (ip_len < 0 || frag_off)
+			return -EINVAL;
+
+		/* Length needs to be sensible */
+		frag_desc->ip_len = (u16)ip_len;
+		csum_len = ntohs(ip6h->payload_len);
+		if (csum_len + frag_desc->ip_len > frag_desc->len - offset)
+			return -EINVAL;
+
+		frag_desc->trans_proto = protocol;
+		csum = ~csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
+					csum_len,
+					frag_desc->trans_proto, 0);
+	} else {
+		/* Not checksumable */
+		return -EINVAL;
+	}
+
+	/* Protocol check */
+	if (frag_desc->trans_proto != IPPROTO_TCP &&
+	    frag_desc->trans_proto != IPPROTO_UDP)
+		return -EINVAL;
+
+	offset += frag_desc->ip_len;
+	/* Check for UDP zero csum packets */
+	if (frag_desc->trans_proto == IPPROTO_UDP) {
+		struct udphdr *uh, __uh;
+
+		uh = rmnet_frag_header_ptr(frag_desc, offset, sizeof(*uh),
+					   &__uh);
+		if (!uh)
+			return -EINVAL;
+
+		if (!uh->check) {
+			if (frag_desc->ip_proto == 4) {
+				/* Zero checksum is valid */
+				priv->stats.csum_sw++;
+				return 1;
+			}
+
+			/* Not valid in IPv6 */
+			priv->stats.csum_sw++;
+			return 0;
+		}
+	}
+
+	/* Walk the frags and checksum each chunk */
+	list_for_each_entry(frag, &frag_desc->frags, list) {
+		u32 frag_size = skb_frag_size(&frag->frag);
+
+		if (!csum_len)
+			break;
+
+		if (offset < frag_size) {
+			void *addr = skb_frag_address(&frag->frag) + offset;
+			u32 len = min_t(u32, csum_len, frag_size - offset);
+
+			/* Checksum 'len' bytes and add them in */
+			csum = csum_partial(addr, len, csum);
+			csum_len -= len;
+			offset = 0;
+		} else {
+			offset -= frag_size;
+		}
+	}
+
+	priv->stats.csum_sw++;
+	return !csum_fold(csum);
+}
+
 /* Process a QMAPv5 packet header */
 int rmnet_frag_process_next_hdr_packet(struct rmnet_frag_descriptor *frag_desc,
 				       struct rmnet_port *port,
@@ -1485,7 +1619,18 @@ int rmnet_frag_process_next_hdr_packet(struct rmnet_frag_descriptor *frag_desc,
 			priv->stats.csum_ok++;
 			frag_desc->csum_valid = true;
 		} else {
-			priv->stats.csum_valid_unset++;
+			int valid = rmnet_frag_checksum_pkt(frag_desc);
+
+			if (valid < 0) {
+				priv->stats.csum_validation_failed++;
+			} else if (valid) {
+				/* All's good */
+				priv->stats.csum_ok++;
+				frag_desc->csum_valid = true;
+			} else {
+				/* Checksum is actually bad */
+				priv->stats.csum_valid_unset++;
+			}
 		}
 
 		if (!rmnet_frag_pull(frag_desc, port,