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(a)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) { - /* 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); 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; + 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; + 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, * * 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 -- 2.42.0