Loading core/rmnet_descriptor.c +168 −8 Original line number Diff line number Diff line /* Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and Loading Loading @@ -322,12 +323,13 @@ int rmnet_frag_descriptor_add_frags_from(struct rmnet_frag_descriptor *to, EXPORT_SYMBOL(rmnet_frag_descriptor_add_frags_from); int rmnet_frag_ipv6_skip_exthdr(struct rmnet_frag_descriptor *frag_desc, int start, u8 *nexthdrp, __be16 *fragp) int start, u8 *nexthdrp, __be16 *frag_offp, bool *frag_hdrp) { u8 nexthdr = *nexthdrp; *fragp = 0; *frag_offp = 0; *frag_hdrp = false; while (ipv6_ext_hdr(nexthdr)) { struct ipv6_opt_hdr *hp, __hp; int hdrlen; Loading @@ -349,8 +351,9 @@ int rmnet_frag_ipv6_skip_exthdr(struct rmnet_frag_descriptor *frag_desc, if (!fp) return -EINVAL; *fragp = *fp; if (ntohs(*fragp) & ~0x7) *frag_offp = *fp; *frag_hdrp = true; if (ntohs(*frag_offp) & ~0x7) break; hdrlen = 8; } else if (nexthdr == NEXTHDR_AUTH) { Loading Loading @@ -1143,6 +1146,7 @@ rmnet_frag_segment_coal_data(struct rmnet_frag_descriptor *coal_desc, struct ipv6hdr *ip6h, __ip6h; int ip_len; __be16 frag_off; bool frag_hdr; u8 protocol; ip6h = rmnet_frag_header_ptr(coal_desc, 0, sizeof(*ip6h), Loading @@ -1155,10 +1159,11 @@ rmnet_frag_segment_coal_data(struct rmnet_frag_descriptor *coal_desc, ip_len = rmnet_frag_ipv6_skip_exthdr(coal_desc, sizeof(*ip6h), &protocol, &frag_off); &frag_off, &frag_hdr); coal_desc->trans_proto = protocol; /* If we run into a problem, or this has a fragment header /* If we run into a problem, or this is fragmented packet * (which should technically not be possible, if the HW * works as intended...), bail. */ Loading @@ -1167,6 +1172,14 @@ rmnet_frag_segment_coal_data(struct rmnet_frag_descriptor *coal_desc, return; } if (frag_hdr) { /* There is a fragment header, but this is not a * fragmented packet. We can handle this, but it * cannot be coalesced because of kernel limitations. */ gro = false; } coal_desc->ip_len = (u16)ip_len; if (coal_desc->ip_len > sizeof(*ip6h)) { /* Don't allow coalescing of any packets with IPv6 Loading Loading @@ -1396,6 +1409,142 @@ 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; bool frag_hdr; 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, &frag_hdr); 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, Loading Loading @@ -1440,8 +1589,19 @@ int rmnet_frag_process_next_hdr_packet(struct rmnet_frag_descriptor *frag_desc, priv->stats.csum_ok++; frag_desc->csum_valid = true; } else { 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, offset + sizeof(*csum_hdr))) { Loading core/rmnet_descriptor.h +3 −1 Original line number Diff line number Diff line /* Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and Loading Loading @@ -75,7 +76,8 @@ int rmnet_frag_descriptor_add_frags_from(struct rmnet_frag_descriptor *to, struct rmnet_frag_descriptor *from, u32 off, u32 len); int rmnet_frag_ipv6_skip_exthdr(struct rmnet_frag_descriptor *frag_desc, int start, u8 *nexthdrp, __be16 *fragp); int start, u8 *nexthdrp, __be16 *frag_offp, bool *frag_hdrp); /* QMAP command packets */ void rmnet_frag_command(struct rmnet_frag_descriptor *frag_desc, Loading Loading
core/rmnet_descriptor.c +168 −8 Original line number Diff line number Diff line /* Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and Loading Loading @@ -322,12 +323,13 @@ int rmnet_frag_descriptor_add_frags_from(struct rmnet_frag_descriptor *to, EXPORT_SYMBOL(rmnet_frag_descriptor_add_frags_from); int rmnet_frag_ipv6_skip_exthdr(struct rmnet_frag_descriptor *frag_desc, int start, u8 *nexthdrp, __be16 *fragp) int start, u8 *nexthdrp, __be16 *frag_offp, bool *frag_hdrp) { u8 nexthdr = *nexthdrp; *fragp = 0; *frag_offp = 0; *frag_hdrp = false; while (ipv6_ext_hdr(nexthdr)) { struct ipv6_opt_hdr *hp, __hp; int hdrlen; Loading @@ -349,8 +351,9 @@ int rmnet_frag_ipv6_skip_exthdr(struct rmnet_frag_descriptor *frag_desc, if (!fp) return -EINVAL; *fragp = *fp; if (ntohs(*fragp) & ~0x7) *frag_offp = *fp; *frag_hdrp = true; if (ntohs(*frag_offp) & ~0x7) break; hdrlen = 8; } else if (nexthdr == NEXTHDR_AUTH) { Loading Loading @@ -1143,6 +1146,7 @@ rmnet_frag_segment_coal_data(struct rmnet_frag_descriptor *coal_desc, struct ipv6hdr *ip6h, __ip6h; int ip_len; __be16 frag_off; bool frag_hdr; u8 protocol; ip6h = rmnet_frag_header_ptr(coal_desc, 0, sizeof(*ip6h), Loading @@ -1155,10 +1159,11 @@ rmnet_frag_segment_coal_data(struct rmnet_frag_descriptor *coal_desc, ip_len = rmnet_frag_ipv6_skip_exthdr(coal_desc, sizeof(*ip6h), &protocol, &frag_off); &frag_off, &frag_hdr); coal_desc->trans_proto = protocol; /* If we run into a problem, or this has a fragment header /* If we run into a problem, or this is fragmented packet * (which should technically not be possible, if the HW * works as intended...), bail. */ Loading @@ -1167,6 +1172,14 @@ rmnet_frag_segment_coal_data(struct rmnet_frag_descriptor *coal_desc, return; } if (frag_hdr) { /* There is a fragment header, but this is not a * fragmented packet. We can handle this, but it * cannot be coalesced because of kernel limitations. */ gro = false; } coal_desc->ip_len = (u16)ip_len; if (coal_desc->ip_len > sizeof(*ip6h)) { /* Don't allow coalescing of any packets with IPv6 Loading Loading @@ -1396,6 +1409,142 @@ 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; bool frag_hdr; 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, &frag_hdr); 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, Loading Loading @@ -1440,8 +1589,19 @@ int rmnet_frag_process_next_hdr_packet(struct rmnet_frag_descriptor *frag_desc, priv->stats.csum_ok++; frag_desc->csum_valid = true; } else { 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, offset + sizeof(*csum_hdr))) { Loading
core/rmnet_descriptor.h +3 −1 Original line number Diff line number Diff line /* Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and Loading Loading @@ -75,7 +76,8 @@ int rmnet_frag_descriptor_add_frags_from(struct rmnet_frag_descriptor *to, struct rmnet_frag_descriptor *from, u32 off, u32 len); int rmnet_frag_ipv6_skip_exthdr(struct rmnet_frag_descriptor *frag_desc, int start, u8 *nexthdrp, __be16 *fragp); int start, u8 *nexthdrp, __be16 *frag_offp, bool *frag_hdrp); /* QMAP command packets */ void rmnet_frag_command(struct rmnet_frag_descriptor *frag_desc, Loading