From: David Gibson <david@gibson.dropbear.id.au>
To: Laurent Vivier <lvivier@redhat.com>
Cc: passt-dev@passt.top
Subject: Re: [PATCH v2 7/8] checksum: introduce functions to compute the header part checksum for TCP/UDP
Date: Thu, 15 Feb 2024 14:12:51 +1100 [thread overview]
Message-ID: <Zc2BMyb3HAA1jXB5@zatzit> (raw)
In-Reply-To: <20240214085628.210783-8-lvivier@redhat.com>
[-- Attachment #1: Type: text/plain, Size: 12656 bytes --]
On Wed, Feb 14, 2024 at 09:56:27AM +0100, Laurent Vivier wrote:
> The TCP and UDP checksums are computed using the data in the TCP/UDP
> payload but also some informations in the IP header (protocol,
> length, source and destination addresses).
>
> We add two functions, proto_ipv4_header_psum() and
> proto_ipv6_header_psum(), to compute the checksum of the IP
> header part.
>
> Signed-off-by: Laurent Vivier <lvivier@redhat.com>
> ---
>
> Notes:
> v2:
> - move new function to checksum.c
> - use _psum rather than _checksum in the name
> - replace csum_udp4() and csum_udp6() by the new function
>
> checksum.c | 70 ++++++++++++++++++++----------------------------------
> checksum.h | 11 ++++-----
> tap.c | 19 +++++++++++++--
> tcp.c | 42 +++++++++++++-------------------
> udp.c | 11 +++++----
> 5 files changed, 72 insertions(+), 81 deletions(-)
>
> diff --git a/checksum.c b/checksum.c
> index 5613187a1c82..90dad96ee2c1 100644
> --- a/checksum.c
> +++ b/checksum.c
> @@ -59,12 +59,6 @@
> #include "util.h"
> #include "ip.h"
>
> -/* Checksums are optional for UDP over IPv4, so we usually just set
> - * them to 0. Change this to 1 to calculate real UDP over IPv4
> - * checksums
> - */
> -#define UDP4_REAL_CHECKSUMS 0
> -
> /**
> * sum_16b() - Calculate sum of 16-bit words
> * @buf: Input buffer
> @@ -133,31 +127,23 @@ uint16_t csum_ip4_header(const struct iphdr *ip4h)
> }
>
> /**
> - * csum_udp4() - Calculate and set checksum for a UDP over IPv4 packet
> - * @udp4hr: UDP header, initialised apart from checksum
> - * @saddr: IPv4 source address
> - * @daddr: IPv4 destination address
> - * @payload: ICMPv4 packet payload
> - * @len: Length of @payload (not including UDP)
> + * proto_ipv4_header_psum() - Calculates the partial checksum of an
> + * IPv4 header for UDP or TCP
> + * @param: ip4h Pointer to the IPv4 header structure
> + * @proto: proto Protocol number
> + * Returns: Partial checksum of the IPv4 header
> */
> -void csum_udp4(struct udphdr *udp4hr,
> - struct in_addr saddr, struct in_addr daddr,
> - const void *payload, size_t len)
> +uint32_t proto_ipv4_header_psum(struct iphdr *ip4h, uint8_t proto)
As per comments on the previous patch, I think there are some
advantages to passing the specific header fields as parameters, rather
than assuming they're already writen to the header structure.
Especially since that's closer to the interface of the pre-existing
functions.
> {
> - /* UDP checksums are optional, so don't bother */
> - udp4hr->check = 0;
> -
> - if (UDP4_REAL_CHECKSUMS) {
> - /* UNTESTED: if we did want real UDPv4 checksums, this
> - * is roughly what we'd need */
> - uint32_t psum = csum_fold(saddr.s_addr)
> - + csum_fold(daddr.s_addr)
> - + htons(len + sizeof(*udp4hr))
> - + htons(IPPROTO_UDP);
> - /* Add in partial checksum for the UDP header alone */
> - psum += sum_16b(udp4hr, sizeof(*udp4hr));
> - udp4hr->check = csum(payload, len, psum);
> - }
> + uint32_t sum = htons(proto);
> +
> + sum += (ip4h->saddr >> 16) & 0xffff;
> + sum += ip4h->saddr & 0xffff;
> + sum += (ip4h->daddr >> 16) & 0xffff;
> + sum += ip4h->daddr & 0xffff;
> + sum += htons(ntohs(ip4h->tot_len) - 20);
> +
> + return sum;
> }
>
> /**
> @@ -179,24 +165,20 @@ void csum_icmp4(struct icmphdr *icmp4hr, const void *payload, size_t len)
> }
>
> /**
> - * csum_udp6() - Calculate and set checksum for a UDP over IPv6 packet
> - * @udp6hr: UDP header, initialised apart from checksum
> - * @payload: UDP packet payload
> - * @len: Length of @payload (not including UDP header)
> + * proto_ipv6_header_psum() - Calculates the partial checksum of an
> + * IPv6 header for UDP or TCP
> + * @param: ip6h Pointer to the IPv4 header structure
> + * @proto: proto Protocol number
> + * Returns: Partial checksum of the IPv6 header
> */
> -void csum_udp6(struct udphdr *udp6hr,
> - const struct in6_addr *saddr, const struct in6_addr *daddr,
> - const void *payload, size_t len)
> +uint32_t proto_ipv6_header_psum(struct ipv6hdr *ip6h, uint8_t proto)
> {
> - /* Partial checksum for the pseudo-IPv6 header */
> - uint32_t psum = sum_16b(saddr, sizeof(*saddr)) +
> - sum_16b(daddr, sizeof(*daddr)) +
> - htons(len + sizeof(*udp6hr)) + htons(IPPROTO_UDP);
> + uint32_t sum = htons(proto) + ip6h->payload_len;
> +
> + sum += sum_16b(&ip6h->saddr, sizeof(ip6h->saddr));
> + sum += sum_16b(&ip6h->daddr, sizeof(ip6h->daddr));
>
> - udp6hr->check = 0;
> - /* Add in partial checksum for the UDP header alone */
> - psum += sum_16b(udp6hr, sizeof(*udp6hr));
> - udp6hr->check = csum(payload, len, psum);
> + return sum;
> }
>
> /**
> diff --git a/checksum.h b/checksum.h
> index b87ecd720df5..10533f708853 100644
> --- a/checksum.h
> +++ b/checksum.h
> @@ -6,24 +6,23 @@
> #ifndef CHECKSUM_H
> #define CHECKSUM_H
>
> +struct iphdr;
> struct udphdr;
> struct icmphdr;
> +struct ipv6hdr;
> struct icmp6hdr;
>
> uint32_t sum_16b(const void *buf, size_t len);
> uint16_t csum_fold(uint32_t sum);
> uint16_t csum_unaligned(const void *buf, size_t len, uint32_t init);
> uint16_t csum_ip4_header(const struct iphdr *ip4h);
> -void csum_udp4(struct udphdr *udp4hr,
> - struct in_addr saddr, struct in_addr daddr,
> - const void *payload, size_t len);
> +uint32_t proto_ipv4_header_psum(struct iphdr *ip4h, uint8_t proto);
> void csum_icmp4(struct icmphdr *ih, const void *payload, size_t len);
> -void csum_udp6(struct udphdr *udp6hr,
> - const struct in6_addr *saddr, const struct in6_addr *daddr,
> - const void *payload, size_t len);
> +uint32_t proto_ipv6_header_psum(struct ipv6hdr *ip6h, uint8_t proto);
> void csum_icmp6(struct icmp6hdr *icmp6hr,
> const struct in6_addr *saddr, const struct in6_addr *daddr,
> const void *payload, size_t len);
> +uint32_t csum_unfolded(const void *buf, size_t len, uint32_t init);
> uint16_t csum(const void *buf, size_t len, uint32_t init);
> uint16_t csum_iov(struct iovec *iov, unsigned int n, uint32_t init);
>
> diff --git a/tap.c b/tap.c
> index 70f36a55314f..02b51100d089 100644
> --- a/tap.c
> +++ b/tap.c
> @@ -58,6 +58,12 @@
> #include "tap.h"
> #include "log.h"
>
> +/* Checksums are optional for UDP over IPv4, so we usually just set
> + * them to 0. Change this to 1 to calculate real UDP over IPv4
> + * checksums
> + */
> +#define UDP4_REAL_CHECKSUMS 0
> +
> /* IPv4 (plus ARP) and IPv6 message batches from tap/guest to IP handlers */
> static PACKET_POOL_NOINIT(pool_tap4, TAP_MSGS, pkt_buf);
> static PACKET_POOL_NOINIT(pool_tap6, TAP_MSGS, pkt_buf);
> @@ -188,7 +194,12 @@ void tap_udp4_send(const struct ctx *c, struct in_addr src, in_port_t sport,
> uh->source = htons(sport);
> uh->dest = htons(dport);
> uh->len = htons(udplen);
> - csum_udp4(uh, src, dst, in, len);
> + uh->check = 0;
> + if (UDP4_REAL_CHECKSUMS) {
> + uint32_t sum = proto_ipv4_header_psum(ip4h, IPPROTO_UDP);
> + sum = csum_unfolded(uh, sizeof(struct udphdr), sum);
> + uh->check = csum(in, len, sum);
> + }
> memcpy(data, in, len);
>
> if (tap_send(c, buf, len + (data - buf)) < 0)
> @@ -271,11 +282,15 @@ void tap_udp6_send(const struct ctx *c,
> void *uhp = tap_push_ip6h(ip6h, src, dst, udplen, IPPROTO_UDP, flow);
> struct udphdr *uh = (struct udphdr *)uhp;
> char *data = (char *)(uh + 1);
> + uint32_t sum;
>
> uh->source = htons(sport);
> uh->dest = htons(dport);
> uh->len = htons(udplen);
> - csum_udp6(uh, src, dst, in, len);
> + uh->check = 0;
> + sum = proto_ipv6_header_psum(ip6h, IPPROTO_UDP);
> + sum = csum_unfolded(uh, sizeof(struct udphdr), sum);
> + uh->check = csum(in, len, sum);
I think it would still be good to have a single-call helper for the
UDP checksums since we need them in two places: here for the "slow
path" used by DHCP etc. and then in udp.c for the "fast path".
> memcpy(data, in, len);
>
> if (tap_send(c, buf, len + (data - buf)) < 1)
> diff --git a/tcp.c b/tcp.c
> index 35e240f4ffc3..6a0020f708c0 100644
> --- a/tcp.c
> +++ b/tcp.c
> @@ -938,39 +938,25 @@ static void tcp_sock_set_bufsize(const struct ctx *c, int s)
> * tcp_update_check_tcp4() - Update TCP checksum from stored one
> * @buf: L2 packet buffer with final IPv4 header
> */
> -static void tcp_update_check_tcp4(struct tcp4_l2_buf_t *buf)
> +static uint16_t tcp_update_check_tcp4(struct iphdr *iph)
> {
> - uint16_t tlen = ntohs(buf->iph.tot_len) - 20;
> - uint32_t sum = htons(IPPROTO_TCP);
> + struct tcphdr *th = (struct tcphdr *)(iph + 1);
> + uint16_t tlen = ntohs(iph->tot_len) - 20;
> + uint32_t sum = proto_ipv4_header_psum(iph, IPPROTO_TCP);
>
> - sum += (buf->iph.saddr >> 16) & 0xffff;
> - sum += buf->iph.saddr & 0xffff;
> - sum += (buf->iph.daddr >> 16) & 0xffff;
> - sum += buf->iph.daddr & 0xffff;
> - sum += htons(ntohs(buf->iph.tot_len) - 20);
> -
> - buf->th.check = 0;
> - buf->th.check = csum(&buf->th, tlen, sum);
> + return csum(th, tlen, sum);
> }
>
> /**
> * tcp_update_check_tcp6() - Calculate TCP checksum for IPv6
> * @buf: L2 packet buffer with final IPv6 header
> */
> -static void tcp_update_check_tcp6(struct tcp6_l2_buf_t *buf)
> +static uint16_t tcp_update_check_tcp6(struct ipv6hdr *ip6h)
> {
> - int len = ntohs(buf->ip6h.payload_len) + sizeof(struct ipv6hdr);
> -
> - buf->ip6h.hop_limit = IPPROTO_TCP;
> - buf->ip6h.version = 0;
> - buf->ip6h.nexthdr = 0;
> + struct tcphdr *th = (struct tcphdr *)(ip6h + 1);
> + uint32_t sum = proto_ipv6_header_psum(ip6h, IPPROTO_TCP);
>
> - buf->th.check = 0;
> - buf->th.check = csum(&buf->ip6h, len, 0);
> -
> - buf->ip6h.hop_limit = 255;
> - buf->ip6h.version = 6;
> - buf->ip6h.nexthdr = IPPROTO_TCP;
> + return csum(th, ntohs(ip6h->payload_len), sum);
> }
>
> /**
> @@ -1380,7 +1366,8 @@ do { \
>
> SET_TCP_HEADER_COMMON_V4_V6(b, conn, seq);
>
> - tcp_update_check_tcp4(b);
> + b->th.check = 0;
I think this initialisation should be folded into
tcp_update_check_tcp4(). Otherwise th.check == 0 is a pretty
non-obvious pre-condition for that function.
> + b->th.check = tcp_update_check_tcp4(&b->iph);
>
> tlen = tap_iov_len(c, &b->taph, ip_len);
> } else {
> @@ -1399,7 +1386,12 @@ do { \
>
> SET_TCP_HEADER_COMMON_V4_V6(b, conn, seq);
>
> - tcp_update_check_tcp6(b);
> + b->th.check = 0;
Same for v6, of course.
> + b->th.check = tcp_update_check_tcp6(&b->ip6h);
> +
> + b->ip6h.hop_limit = 255;
> + b->ip6h.version = 6;
> + b->ip6h.nexthdr = IPPROTO_TCP;
>
> b->ip6h.flow_lbl[0] = (conn->sock >> 16) & 0xf;
> b->ip6h.flow_lbl[1] = (conn->sock >> 8) & 0xff;
> diff --git a/udp.c b/udp.c
> index e645c800a823..bf24288d5751 100644
> --- a/udp.c
> +++ b/udp.c
> @@ -618,6 +618,9 @@ static size_t udp_update_hdr4(const struct ctx *c, int n, in_port_t dstport,
Hmm.. pre-existing bug(?) but udp_update_hdr4() should probably
respect the UDP4_REAL_CHECKSUMS option as well. Using a common helper
for there and tap_udp4_send() which checks it would be nice.
> *
> * Return: size of tap frame with headers
> */
> +#pragma GCC diagnostic push
> +/* ignore unaligned pointer value warning for &b->ip6h */
> +#pragma GCC diagnostic ignored "-Waddress-of-packed-member"
> static size_t udp_update_hdr6(const struct ctx *c, int n, in_port_t dstport,
> const struct timespec *now)
> {
> @@ -673,16 +676,16 @@ static size_t udp_update_hdr6(const struct ctx *c, int n, in_port_t dstport,
> b->uh.source = b->s_in6.sin6_port;
> b->uh.dest = htons(dstport);
> b->uh.len = b->ip6h.payload_len;
> -
> - b->ip6h.hop_limit = IPPROTO_UDP;
> - b->ip6h.version = b->ip6h.nexthdr = b->uh.check = 0;
> - b->uh.check = csum(&b->ip6h, ip_len, 0);
> + b->uh.check = 0;
> + b->uh.check = csum(&b->uh, ntohs(b->ip6h.payload_len),
> + proto_ipv6_header_psum(&b->ip6h, IPPROTO_UDP));
> b->ip6h.version = 6;
> b->ip6h.nexthdr = IPPROTO_UDP;
> b->ip6h.hop_limit = 255;
>
> return tap_iov_len(c, &b->taph, ip_len);
> }
> +#pragma GCC diagnostic pop
>
> /**
> * udp_tap_send() - Prepare UDP datagrams and send to tap interface
--
David Gibson | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_
| _way_ _around_!
http://www.ozlabs.org/~dgibson
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
next prev parent reply other threads:[~2024-02-15 3:12 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-02-14 8:56 [PATCH v2 0/8] Add vhost-user support to passt (part 1) Laurent Vivier
2024-02-14 8:56 ` [PATCH v2 1/8] iov: add some functions to manage iovec Laurent Vivier
2024-02-15 0:24 ` David Gibson
2024-02-15 0:32 ` David Gibson
2024-02-16 5:29 ` Stefano Brivio
2024-02-14 8:56 ` [PATCH v2 2/8] pcap: add pcap_iov() Laurent Vivier
2024-02-15 0:35 ` David Gibson
2024-02-16 5:30 ` Stefano Brivio
2024-02-14 8:56 ` [PATCH v2 3/8] checksum: align buffers Laurent Vivier
2024-02-15 0:40 ` David Gibson
2024-02-14 8:56 ` [PATCH v2 4/8] checksum: add csum_iov() Laurent Vivier
2024-02-15 0:44 ` David Gibson
2024-02-14 8:56 ` [PATCH v2 5/8] util: move IP stuff from util.[ch] to ip.[ch] Laurent Vivier
2024-02-15 2:29 ` David Gibson
2024-02-14 8:56 ` [PATCH v2 6/8] checksum: use csum_ip4_header() in udp.c and tcp.c Laurent Vivier
2024-02-15 2:51 ` David Gibson
2024-02-16 9:08 ` Stefano Brivio
2024-02-16 14:17 ` Laurent Vivier
2024-02-16 14:54 ` Stefano Brivio
2024-02-16 18:05 ` Laurent Vivier
2024-02-16 18:24 ` Stefano Brivio
2024-02-17 14:22 ` Laurent Vivier
2024-02-17 14:37 ` Stefano Brivio
2024-02-19 2:06 ` David Gibson
2024-02-14 8:56 ` [PATCH v2 7/8] checksum: introduce functions to compute the header part checksum for TCP/UDP Laurent Vivier
2024-02-15 3:12 ` David Gibson [this message]
2024-02-14 8:56 ` [PATCH v2 8/8] tap: make tap_update_mac() generic Laurent Vivier
2024-02-15 3:13 ` David Gibson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=Zc2BMyb3HAA1jXB5@zatzit \
--to=david@gibson.dropbear.id.au \
--cc=lvivier@redhat.com \
--cc=passt-dev@passt.top \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://passt.top/passt
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).