]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ip-util: introduce ip_checksum()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 15 Mar 2026 04:30:38 +0000 (13:30 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 24 Apr 2026 20:23:37 +0000 (05:23 +0900)
It is equivalent to dhcp_packet_checksum(). It is generic
and not specific to DHCP. Hence, renamed to ip_checksum().

src/libsystemd-network/dhcp-packet.c
src/libsystemd-network/dhcp-packet.h
src/libsystemd-network/ip-util.c [new file with mode: 0644]
src/libsystemd-network/ip-util.h [new file with mode: 0644]
src/libsystemd-network/meson.build
src/libsystemd-network/test-dhcp-client.c
src/libsystemd-network/test-ip-util.c [new file with mode: 0644]

index 90eae88379ad558837713e5f6d3223de26e6797c..3b17ad8ac427aa77239c10c2b6e646e4275a1525 100644 (file)
@@ -4,10 +4,10 @@
 ***/
 
 #include <net/if_arp.h>
-#include <string.h>
 
 #include "dhcp-option.h"
 #include "dhcp-packet.h"
+#include "ip-util.h"
 #include "log.h"
 #include "memory-util.h"
 
@@ -79,41 +79,6 @@ int dhcp_message_init(
         return 0;
 }
 
-uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len) {
-        uint64_t *buf_64 = (uint64_t*)buf;
-        uint64_t *end_64 = buf_64 + (len / sizeof(uint64_t));
-        uint64_t sum = 0;
-
-        /* See RFC1071 */
-
-        while (buf_64 < end_64) {
-                sum += *buf_64;
-                if (sum < *buf_64)
-                        /* wrap around in one's complement */
-                        sum++;
-
-                buf_64++;
-        }
-
-        if (len % sizeof(uint64_t)) {
-                /* If the buffer is not aligned to 64-bit, we need
-                   to zero-pad the last few bytes and add them in */
-                uint64_t buf_tail = 0;
-
-                memcpy(&buf_tail, buf_64, len % sizeof(uint64_t));
-
-                sum += buf_tail;
-                if (sum < buf_tail)
-                        /* wrap around */
-                        sum++;
-        }
-
-        while (sum >> 16)
-                sum = (sum & 0xffff) + (sum >> 16);
-
-        return ~sum;
-}
-
 void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr,
                                    uint16_t source_port, be32_t destination_addr,
                                    uint16_t destination_port, uint16_t len, int ip_service_type) {
@@ -136,11 +101,11 @@ void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr,
         packet->udp.len = htobe16(len - DHCP_IP_SIZE);
 
         packet->ip.check = packet->udp.len;
-        packet->udp.check = dhcp_packet_checksum(&packet->ip.ttl, len - 8);
+        packet->udp.check = ip_checksum(&packet->ip.ttl, len - 8);
 
         packet->ip.ttl = IPDEFTTL;
         packet->ip.check = 0;
-        packet->ip.check = dhcp_packet_checksum((uint8_t*)&packet->ip, DHCP_IP_SIZE);
+        packet->ip.check = ip_checksum(&packet->ip, DHCP_IP_SIZE);
 }
 
 int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) {
@@ -193,7 +158,7 @@ int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, ui
            if all the other checks have passed
          */
 
-        if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen))
+        if (ip_checksum(&packet->ip, hdrlen))
                 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
                                        "ignoring packet: invalid IP checksum");
 
@@ -201,8 +166,7 @@ int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, ui
                 packet->ip.check = packet->udp.len;
                 packet->ip.ttl = 0;
 
-                if (dhcp_packet_checksum(&packet->ip.ttl,
-                                  be16toh(packet->udp.len) + 12))
+                if (ip_checksum(&packet->ip.ttl, be16toh(packet->udp.len) + 12))
                         return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
                                                "ignoring packet: invalid UDP checksum");
         }
index 967bd5d89df715a1319a48ba6e94450f63f1e3e2..8a56383adda6112bbac09800f787bba465a98242 100644 (file)
@@ -23,8 +23,6 @@ int dhcp_message_init(
                 size_t optlen,
                 size_t *ret_optoffset);
 
-uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len);
-
 void dhcp_packet_append_ip_headers(
                 DHCPPacket *packet,
                 be32_t source_addr,
diff --git a/src/libsystemd-network/ip-util.c b/src/libsystemd-network/ip-util.c
new file mode 100644 (file)
index 0000000..dd7e872
--- /dev/null
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <string.h>
+
+#include "iovec-util.h"
+#include "ip-util.h"
+
+static uint64_t complement_sum(uint64_t a, uint64_t b) {
+        /* This performs one's complement addition (end-around carry). See RFC1071. */
+        if (a <= UINT64_MAX - b)
+                return a + b;
+
+        return a - (UINT64_MAX - b);
+}
+
+static uint64_t checksum_iov(uint64_t sum, const struct iovec *iov) {
+        assert(iov);
+
+        for (struct iovec i = *iov; iovec_is_set(&i); iovec_inc(&i, sizeof(uint64_t))) {
+                uint64_t t = 0;
+                memcpy(&t, i.iov_base, MIN(i.iov_len, sizeof(uint64_t)));
+                sum = complement_sum(sum, t);
+        }
+
+        return sum;
+}
+
+static uint16_t checksum_finalize(uint64_t sum) {
+        while ((sum >> 16) != 0)
+                sum = (sum & 0xffffu) + (sum >> 16);
+
+        return ~sum;
+}
+
+uint16_t ip_checksum(const void *buf, size_t len) {
+        /* See RFC1071 */
+        return checksum_finalize(checksum_iov(0, &IOVEC_MAKE(buf, len)));
+}
diff --git a/src/libsystemd-network/ip-util.h b/src/libsystemd-network/ip-util.h
new file mode 100644 (file)
index 0000000..dd11afb
--- /dev/null
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-forward.h"
+
+uint16_t ip_checksum(const void *buf, size_t len);
index d1e13d99b536fc87902cbdb8e371df6ec0abafaa..b0443c3695206fd728e622d6c8b322c4d570af7f 100644 (file)
@@ -12,6 +12,7 @@ libsystemd_network_sources = files(
         'dhcp6-protocol.c',
         'icmp6-packet.c',
         'icmp6-util.c',
+        'ip-util.c',
         'lldp-neighbor.c',
         'lldp-network.c',
         'ndisc-option.c',
@@ -84,6 +85,9 @@ executables += [
         network_test_template + {
                 'sources' : files('test-dhcp6-client.c'),
         },
+        network_test_template + {
+                'sources' : files('test-ip-util.c'),
+        },
         network_test_template + {
                 'sources' : files('test-ipv4ll-manual.c'),
                 'type' : 'manual',
index 97802e2c164e42ed88a8b3930020d79d88b88c73..b2cadc07e5b1e63f67d3db838a82f362b866bd63 100644 (file)
@@ -17,9 +17,9 @@
 #include "dhcp-duid-internal.h"
 #include "dhcp-network.h"
 #include "dhcp-option.h"
-#include "dhcp-packet.h"
 #include "ether-addr-util.h"
 #include "fd-util.h"
+#include "ip-util.h"
 #include "log.h"
 #include "tests.h"
 
@@ -107,16 +107,6 @@ TEST(dhcp_client_anonymize) {
         ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, 101));
 }
 
-TEST(dhcp_packet_checksum) {
-        uint8_t buf[20] = {
-                0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00,
-                0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                0xff, 0xff, 0xff, 0xff
-        };
-
-        ASSERT_EQ(dhcp_packet_checksum(buf, 20), be16toh(0x78ae));
-}
-
 TEST(dhcp_identifier_set_iaid) {
         uint32_t iaid_legacy;
         be32_t iaid;
@@ -181,13 +171,13 @@ int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const
         discover->ip.ttl = 0;
         discover->ip.check = discover->udp.len;
 
-        udp_check = ~dhcp_packet_checksum(&discover->ip.ttl, len - 8);
+        udp_check = ~ip_checksum(&discover->ip.ttl, len - 8);
         ASSERT_EQ(udp_check, 0xffff);
 
         discover->ip.ttl = IPDEFTTL;
         discover->ip.check = ip_check;
 
-        ip_check = ~dhcp_packet_checksum((uint8_t*) &discover->ip, sizeof(discover->ip));
+        ip_check = ~ip_checksum((uint8_t*) &discover->ip, sizeof(discover->ip));
         ASSERT_EQ(ip_check, 0xffff);
 
         ASSERT_NE(discover->dhcp.xid, 0u);
diff --git a/src/libsystemd-network/test-ip-util.c b/src/libsystemd-network/test-ip-util.c
new file mode 100644 (file)
index 0000000..58adb8f
--- /dev/null
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "ip-util.h"
+#include "tests.h"
+
+TEST(ip_checksum) {
+        uint8_t buf[20] = {
+                0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00,
+                0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0xff, 0xff, 0xff, 0xff
+        };
+
+        ASSERT_EQ(ip_checksum(buf, 20), be16toh(0x78ae));
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);