Skip to content

Commit 10a153e

Browse files
committed
AK+Userland+Kernel+Tests: Deduplicate IPv4 header checksumming
Previously, there were two slightly different implementations of this in the Kernel and in LibCrypto. This patch removes those, and replaces them with an implementation that seeks to combine the best aspects of both while not being a drop-in replacement for either. Two new tests are also included.
1 parent b9e81af commit 10a153e

File tree

16 files changed

+117
-131
lines changed

16 files changed

+117
-131
lines changed

AK/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ set(AK_SOURCES
1515
FuzzyMatch.cpp
1616
GenericLexer.cpp
1717
Hex.cpp
18+
InternetChecksum.cpp
1819
JsonObject.cpp
1920
JsonParser.cpp
2021
JsonPath.cpp

AK/InternetChecksum.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (c) 2025, the SerenityOS developers.
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <AK/ByteReader.h>
8+
#include <AK/InternetChecksum.h>
9+
10+
namespace AK {
11+
12+
InternetChecksum::InternetChecksum(ReadonlyBytes bytes)
13+
{
14+
update(bytes);
15+
}
16+
17+
void InternetChecksum::update(ReadonlyBytes bytes)
18+
{
19+
VERIFY(!m_uneven_payload);
20+
for (size_t i = 0; i < bytes.size() / sizeof(u16); ++i)
21+
m_state += ByteReader::load16(bytes.offset_pointer(i * sizeof(u16)));
22+
if (bytes.size() % 2 == 1) {
23+
m_state += bytes.last();
24+
m_uneven_payload = true;
25+
}
26+
}
27+
28+
NetworkOrdered<u16> InternetChecksum::digest()
29+
{
30+
u32 output = m_state;
31+
// While there are any bits above the bottom 16...
32+
while (output >> 16)
33+
// Drop the top bits, and add the carries to the sum.
34+
output = (output & 0xFFFF) + (output >> 16);
35+
36+
// Return the one's complement (this is already network-ordered, so we
37+
// bypass the constructor of that).
38+
return bit_cast<NetworkOrdered<u16>>(static_cast<u16>(~output));
39+
}
40+
41+
}

AK/InternetChecksum.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2025, the SerenityOS developers.
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#pragma once
8+
9+
#include <AK/Endian.h>
10+
#include <AK/Forward.h>
11+
#include <AK/Types.h>
12+
13+
namespace AK {
14+
15+
class InternetChecksum {
16+
public:
17+
InternetChecksum() = default;
18+
InternetChecksum(ReadonlyBytes);
19+
20+
void update(ReadonlyBytes);
21+
NetworkOrdered<u16> digest();
22+
23+
private:
24+
u32 m_state { 0 };
25+
bool m_uneven_payload { false };
26+
};
27+
28+
}
29+
30+
#ifdef USING_AK_GLOBALLY
31+
using AK::InternetChecksum;
32+
#endif

Kernel/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,7 @@ set(AK_SOURCES
611611
../AK/DOSPackedTime.cpp
612612
../AK/GenericLexer.cpp
613613
../AK/Hex.cpp
614+
../AK/InternetChecksum.cpp
614615
../AK/MemoryStream.cpp
615616
../AK/SipHash.cpp
616617
../AK/Stream.cpp

Kernel/Net/IP/IP.h

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,32 +22,4 @@ enum class TransportProtocol : u8 {
2222
ICMPv6 = 58,
2323
};
2424

25-
class InternetChecksum {
26-
public:
27-
void add(ReadonlyBytes bytes)
28-
{
29-
VERIFY(!m_uneven_payload);
30-
Span<u16 const> words { bit_cast<u16 const*>(bytes.data()), bytes.size() / 2 };
31-
for (u16 w : words) {
32-
m_checksum += AK::convert_between_host_and_network_endian(w);
33-
if (m_checksum & 0x80000000)
34-
m_checksum = (m_checksum & 0xffff) | (m_checksum >> 16);
35-
}
36-
if (bytes.size() % 2 == 1) {
37-
m_checksum += (bytes.last()) << 8;
38-
m_uneven_payload = true;
39-
}
40-
}
41-
NetworkOrdered<u16> finish()
42-
{
43-
while (m_checksum >> 16)
44-
m_checksum = (m_checksum & 0xffff) + (m_checksum >> 16);
45-
return ~m_checksum & 0xffff;
46-
}
47-
48-
private:
49-
u32 m_checksum { 0 };
50-
bool m_uneven_payload { false };
51-
};
52-
5325
}

Kernel/Net/IP/IPv4.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <AK/Assertions.h>
1010
#include <AK/Endian.h>
1111
#include <AK/IPv4Address.h>
12+
#include <AK/InternetChecksum.h>
1213
#include <AK/Types.h>
1314
#include <Kernel/Net/IP/IP.h>
1415

@@ -81,9 +82,7 @@ class [[gnu::packed]] IPv4Packet {
8182
NetworkOrdered<u16> compute_checksum() const
8283
{
8384
VERIFY(!m_checksum);
84-
InternetChecksum checksum;
85-
checksum.add({ this, sizeof(IPv4Packet) });
86-
return checksum.finish();
85+
return InternetChecksum({ this, sizeof(IPv4Packet) }).digest();
8786
}
8887

8988
private:

Kernel/Net/NetworkTask.cpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -331,10 +331,10 @@ void handle_icmpv6(EthernetFrameHeader const& eth, IPv6PacketHeader const& ipv6_
331331
header.next_header = TransportProtocol::ICMPv6;
332332

333333
InternetChecksum checksum;
334-
checksum.add({ &header, sizeof(IPv6PseudoHeader) });
335-
checksum.add({ &response, sizeof(advertisement_with_option) });
334+
checksum.update({ &header, sizeof(IPv6PseudoHeader) });
335+
checksum.update({ &response, sizeof(advertisement_with_option) });
336336

337-
response.base.header.set_checksum(checksum.finish());
337+
response.base.header.set_checksum(checksum.digest());
338338
} else if (icmpv6_header.type() == ICMPv6Type::EchoRequest) {
339339
dbgln_if(ICMPV6_DEBUG, "handle_icmp6: got echo request");
340340
if (icmp_packet_size < sizeof(ICMPv6Echo)) {
@@ -370,10 +370,10 @@ void handle_icmpv6(EthernetFrameHeader const& eth, IPv6PacketHeader const& ipv6_
370370
memcpy(response.payload(), request.payload(), icmp_packet_size - sizeof(ICMPv6Echo));
371371

372372
InternetChecksum checksum;
373-
checksum.add({ &header, sizeof(IPv6PseudoHeader) });
374-
checksum.add({ &response, icmp_packet_size });
373+
checksum.update({ &header, sizeof(IPv6PseudoHeader) });
374+
checksum.update({ &response, icmp_packet_size });
375375

376-
response.header.set_checksum(checksum.finish());
376+
response.header.set_checksum(checksum.digest());
377377
} else {
378378
dbgln_if(ICMPV6_DEBUG, "handle_icmp6: got unknown ICMPv6 type {:#02x}", icmpv6_header.type());
379379
return;
@@ -431,9 +431,7 @@ void handle_icmp(EthernetFrameHeader const& eth, IPv4Packet const& ipv4_packet,
431431
if (icmp_packet_size > icmp_header_size)
432432
memcpy(response.payload, request.payload, icmp_packet_size - icmp_header_size);
433433

434-
InternetChecksum checksum;
435-
checksum.add({ &response, icmp_packet_size });
436-
response.header.checksum = checksum.finish();
434+
response.header.checksum = InternetChecksum({ &response, icmp_packet_size }).digest();
437435

438436
adapter->send_packet(packet->bytes());
439437
adapter->release_packet_buffer(*packet);

Tests/AK/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ set(AK_TEST_SOURCES
5151
TestIndexSequence.cpp
5252
TestInsertionSort.cpp
5353
TestIntegerMath.cpp
54+
TestInternetChecksum.cpp
5455
TestIntrusiveList.cpp
5556
TestIntrusiveRedBlackTree.cpp
5657
TestJSON.cpp

Tests/AK/TestInternetChecksum.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) 2025, the SerenityOS developers.
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <AK/Array.h>
8+
#include <AK/InternetChecksum.h>
9+
#include <LibTest/TestCase.h>
10+
#include <netinet/in.h>
11+
12+
TEST_CASE(test_internetchecksum)
13+
{
14+
auto do_test = [](ReadonlyBytes input, u16 expected_result) {
15+
auto digest = InternetChecksum(input).digest();
16+
EXPECT_EQ(digest, expected_result);
17+
};
18+
19+
do_test(to_readonly_bytes(Array<u16, 3> { htons(0b0110'0110'0110'0000), htons(0b0101'0101'0101'0101), htons(0b1000'1111'0000'1100) }.span()), 0b1011'0101'0011'1101);
20+
21+
// Test case from RFC1071.
22+
// The specified result doesn't include the final conversion from one's complement,
23+
// hence the bitwise negation.
24+
do_test(to_readonly_bytes(Array<u16, 4> { 0x0100, 0x03f2, 0xf5f4, 0xf7f6 }.span()), static_cast<u16>(~0xddf2));
25+
26+
// Variation of the above (with an uneven payload).
27+
do_test(to_readonly_bytes(Array<u8, 3> { 0x01, 0x00, 0x03 }.span()), htons(static_cast<u16>(~0x4)));
28+
}

Tests/LibCrypto/TestChecksum.cpp

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
#include <LibCrypto/Checksum/Adler32.h>
88
#include <LibCrypto/Checksum/CRC32.h>
9-
#include <LibCrypto/Checksum/IPv4Header.h>
109
#include <LibCrypto/Checksum/cksum.h>
1110
#include <LibTest/TestCase.h>
1211
#include <arpa/inet.h>
@@ -65,13 +64,3 @@ TEST_CASE(test_crc32)
6564
do_test("The quick brown fox jumps over the lazy dog"sv.bytes(), 0x414FA339);
6665
do_test("various CRC algorithms input data"sv.bytes(), 0x9BD366AE);
6766
}
68-
69-
TEST_CASE(test_ipv4header)
70-
{
71-
auto do_test = [](ReadonlyBytes input, u16 expected_result) {
72-
auto digest = Crypto::Checksum::IPv4Header(input).digest();
73-
EXPECT_EQ(digest, expected_result);
74-
};
75-
Array<u16, 3> bytes = { htons(0b0110'0110'0110'0000), htons(0b0101'0101'0101'0101), htons(0b1000'1111'0000'1100) };
76-
do_test(to_readonly_bytes(bytes.span()), htons(0b1011'0101'0011'1101));
77-
}

0 commit comments

Comments
 (0)