1 /*
2 * Copyright (c) 2020, The OpenThread Authors.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. Neither the name of the copyright holder nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "common/encoding.hpp"
30 #include "common/message.hpp"
31 #include "common/numeric_limits.hpp"
32 #include "common/random.hpp"
33 #include "common/string.hpp"
34 #include "instance/instance.hpp"
35 #include "net/checksum.hpp"
36 #include "net/icmp6.hpp"
37 #include "net/ip4_types.hpp"
38 #include "net/udp6.hpp"
39 #include "utils/verhoeff_checksum.hpp"
40
41 #include "test_platform.h"
42 #include "test_util.hpp"
43
44 namespace ot {
45
CalculateChecksum(const void * aBuffer,uint16_t aLength)46 uint16_t CalculateChecksum(const void *aBuffer, uint16_t aLength)
47 {
48 // Calculates checksum over a given buffer data. This implementation
49 // is inspired by the algorithm from RFC-1071.
50
51 uint32_t sum = 0;
52 const uint8_t *bytes = reinterpret_cast<const uint8_t *>(aBuffer);
53
54 while (aLength >= sizeof(uint16_t))
55 {
56 sum += BigEndian::ReadUint16(bytes);
57 bytes += sizeof(uint16_t);
58 aLength -= sizeof(uint16_t);
59 }
60
61 if (aLength > 0)
62 {
63 sum += (static_cast<uint32_t>(bytes[0]) << 8);
64 }
65
66 // Fold 32-bit sum to 16 bits.
67
68 while (sum >> 16)
69 {
70 sum = (sum & 0xffff) + (sum >> 16);
71 }
72
73 return static_cast<uint16_t>(sum & 0xffff);
74 }
75
CalculateChecksum(const Ip6::Address & aSource,const Ip6::Address & aDestination,uint8_t aIpProto,const Message & aMessage)76 uint16_t CalculateChecksum(const Ip6::Address &aSource,
77 const Ip6::Address &aDestination,
78 uint8_t aIpProto,
79 const Message &aMessage)
80 {
81 // This method calculates the checksum over an IPv6 message.
82 constexpr uint16_t kMaxPayload = 1024;
83
84 OT_TOOL_PACKED_BEGIN
85 struct PseudoHeader
86 {
87 Ip6::Address mSource;
88 Ip6::Address mDestination;
89 uint32_t mPayloadLength;
90 uint32_t mProtocol;
91 } OT_TOOL_PACKED_END;
92
93 OT_TOOL_PACKED_BEGIN
94 struct ChecksumData
95 {
96 PseudoHeader mPseudoHeader;
97 uint8_t mPayload[kMaxPayload];
98 } OT_TOOL_PACKED_END;
99
100 ChecksumData data;
101 uint16_t payloadLength;
102
103 payloadLength = aMessage.GetLength() - aMessage.GetOffset();
104
105 data.mPseudoHeader.mSource = aSource;
106 data.mPseudoHeader.mDestination = aDestination;
107 data.mPseudoHeader.mProtocol = BigEndian::HostSwap32(aIpProto);
108 data.mPseudoHeader.mPayloadLength = BigEndian::HostSwap32(payloadLength);
109
110 SuccessOrQuit(aMessage.Read(aMessage.GetOffset(), data.mPayload, payloadLength));
111
112 return CalculateChecksum(&data, sizeof(PseudoHeader) + payloadLength);
113 }
114
CalculateChecksum(const Ip4::Address & aSource,const Ip4::Address & aDestination,uint8_t aIpProto,const Message & aMessage)115 uint16_t CalculateChecksum(const Ip4::Address &aSource,
116 const Ip4::Address &aDestination,
117 uint8_t aIpProto,
118 const Message &aMessage)
119 {
120 // This method calculates the checksum over an IPv4 message.
121 constexpr uint16_t kMaxPayload = 1024;
122
123 OT_TOOL_PACKED_BEGIN
124 struct PseudoHeader
125 {
126 Ip4::Address mSource;
127 Ip4::Address mDestination;
128 uint16_t mPayloadLength;
129 uint16_t mProtocol;
130 } OT_TOOL_PACKED_END;
131
132 OT_TOOL_PACKED_BEGIN
133 struct ChecksumData
134 {
135 PseudoHeader mPseudoHeader;
136 uint8_t mPayload[kMaxPayload];
137 } OT_TOOL_PACKED_END;
138
139 ChecksumData data;
140 uint16_t payloadLength;
141
142 payloadLength = aMessage.GetLength() - aMessage.GetOffset();
143
144 data.mPseudoHeader.mSource = aSource;
145 data.mPseudoHeader.mDestination = aDestination;
146 data.mPseudoHeader.mProtocol = BigEndian::HostSwap16(aIpProto);
147 data.mPseudoHeader.mPayloadLength = BigEndian::HostSwap16(payloadLength);
148
149 SuccessOrQuit(aMessage.Read(aMessage.GetOffset(), data.mPayload, payloadLength));
150
151 return CalculateChecksum(&data, sizeof(PseudoHeader) + payloadLength);
152 }
153
CorruptMessage(Message & aMessage)154 void CorruptMessage(Message &aMessage)
155 {
156 // Change a random bit in the message.
157
158 uint16_t byteOffset;
159 uint8_t bitOffset;
160 uint8_t byte;
161
162 byteOffset = Random::NonCrypto::GetUint16InRange(0, aMessage.GetLength());
163
164 SuccessOrQuit(aMessage.Read(byteOffset, byte));
165
166 bitOffset = Random::NonCrypto::GetUint8InRange(0, kBitsPerByte);
167
168 byte ^= (1 << bitOffset);
169
170 aMessage.Write(byteOffset, byte);
171 }
172
TestUdpMessageChecksum(void)173 void TestUdpMessageChecksum(void)
174 {
175 constexpr uint16_t kMinSize = sizeof(Ip6::Udp::Header);
176 constexpr uint16_t kMaxSize = kBufferSize * 3 + 24;
177
178 const char *kSourceAddress = "fd00:1122:3344:5566:7788:99aa:bbcc:ddee";
179 const char *kDestAddress = "fd01:2345:6789:abcd:ef01:2345:6789:abcd";
180
181 Instance *instance = static_cast<Instance *>(testInitInstance());
182
183 VerifyOrQuit(instance != nullptr);
184
185 for (uint16_t size = kMinSize; size <= kMaxSize; size++)
186 {
187 Message *message = instance->Get<Ip6::Ip6>().NewMessage(sizeof(Ip6::Udp::Header));
188 Ip6::Udp::Header udpHeader;
189 Ip6::MessageInfo messageInfo;
190
191 VerifyOrQuit(message != nullptr, "Ip6::NewMesssage() failed");
192 SuccessOrQuit(message->SetLength(size));
193
194 // Write UDP header with a random payload.
195
196 Random::NonCrypto::Fill(udpHeader);
197 udpHeader.SetChecksum(0);
198 message->Write(0, udpHeader);
199
200 if (size > sizeof(udpHeader))
201 {
202 uint8_t buffer[kMaxSize];
203 uint16_t payloadSize = size - sizeof(udpHeader);
204
205 Random::NonCrypto::FillBuffer(buffer, payloadSize);
206 message->WriteBytes(sizeof(udpHeader), &buffer[0], payloadSize);
207 }
208
209 SuccessOrQuit(messageInfo.GetSockAddr().FromString(kSourceAddress));
210 SuccessOrQuit(messageInfo.GetPeerAddr().FromString(kDestAddress));
211
212 // Verify that the `Checksum::UpdateMessageChecksum` correctly
213 // updates the checksum field in the UDP header on the message.
214
215 Checksum::UpdateMessageChecksum(*message, messageInfo.GetSockAddr(), messageInfo.GetPeerAddr(), Ip6::kProtoUdp);
216
217 SuccessOrQuit(message->Read(message->GetOffset(), udpHeader));
218 VerifyOrQuit(udpHeader.GetChecksum() != 0);
219
220 // Verify that the calculated UDP checksum is valid.
221
222 VerifyOrQuit(CalculateChecksum(messageInfo.GetSockAddr(), messageInfo.GetPeerAddr(), Ip6::kProtoUdp,
223 *message) == 0xffff);
224
225 // Verify that `Checksum::VerifyMessageChecksum()` accepts the
226 // message and its calculated checksum.
227
228 SuccessOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoUdp));
229
230 // Corrupt the message and verify that checksum is no longer accepted.
231
232 CorruptMessage(*message);
233
234 VerifyOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoUdp) != kErrorNone,
235 "Checksum passed on corrupted message");
236
237 message->Free();
238 }
239 }
240
TestIcmp6MessageChecksum(void)241 void TestIcmp6MessageChecksum(void)
242 {
243 constexpr uint16_t kMinSize = sizeof(Ip6::Icmp::Header);
244 constexpr uint16_t kMaxSize = kBufferSize * 3 + 24;
245
246 const char *kSourceAddress = "fd00:feef:dccd:baab:9889:7667:5444:3223";
247 const char *kDestAddress = "fd01:abab:beef:cafe:1234:5678:9abc:0";
248
249 Instance *instance = static_cast<Instance *>(testInitInstance());
250
251 VerifyOrQuit(instance != nullptr, "Null OpenThread instance\n");
252
253 for (uint16_t size = kMinSize; size <= kMaxSize; size++)
254 {
255 Message *message = instance->Get<Ip6::Ip6>().NewMessage(sizeof(Ip6::Icmp::Header));
256 Ip6::Icmp::Header icmp6Header;
257 Ip6::MessageInfo messageInfo;
258
259 VerifyOrQuit(message != nullptr, "Ip6::NewMesssage() failed");
260 SuccessOrQuit(message->SetLength(size));
261
262 // Write ICMP6 header with a random payload.
263
264 Random::NonCrypto::Fill(icmp6Header);
265 icmp6Header.SetChecksum(0);
266 message->Write(0, icmp6Header);
267
268 if (size > sizeof(icmp6Header))
269 {
270 uint8_t buffer[kMaxSize];
271 uint16_t payloadSize = size - sizeof(icmp6Header);
272
273 Random::NonCrypto::FillBuffer(buffer, payloadSize);
274 message->WriteBytes(sizeof(icmp6Header), &buffer[0], payloadSize);
275 }
276
277 SuccessOrQuit(messageInfo.GetSockAddr().FromString(kSourceAddress));
278 SuccessOrQuit(messageInfo.GetPeerAddr().FromString(kDestAddress));
279
280 // Verify that the `Checksum::UpdateMessageChecksum` correctly
281 // updates the checksum field in the ICMP6 header on the message.
282
283 Checksum::UpdateMessageChecksum(*message, messageInfo.GetSockAddr(), messageInfo.GetPeerAddr(),
284 Ip6::kProtoIcmp6);
285
286 SuccessOrQuit(message->Read(message->GetOffset(), icmp6Header));
287 VerifyOrQuit(icmp6Header.GetChecksum() != 0, "Failed to update checksum");
288
289 // Verify that the calculated ICMP6 checksum is valid.
290
291 VerifyOrQuit(CalculateChecksum(messageInfo.GetSockAddr(), messageInfo.GetPeerAddr(), Ip6::kProtoIcmp6,
292 *message) == 0xffff);
293
294 // Verify that `Checksum::VerifyMessageChecksum()` accepts the
295 // message and its calculated checksum.
296
297 SuccessOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoIcmp6));
298
299 // Corrupt the message and verify that checksum is no longer accepted.
300
301 CorruptMessage(*message);
302
303 VerifyOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoIcmp6) != kErrorNone,
304 "Checksum passed on corrupted message");
305
306 message->Free();
307 }
308 }
309
TestTcp4MessageChecksum(void)310 void TestTcp4MessageChecksum(void)
311 {
312 constexpr size_t kMinSize = sizeof(Ip4::Tcp::Header);
313 constexpr size_t kMaxSize = kBufferSize * 3 + 24;
314
315 const char *kSourceAddress = "12.34.56.78";
316 const char *kDestAddress = "87.65.43.21";
317
318 Ip4::Address sourceAddress;
319 Ip4::Address destAddress;
320
321 Instance *instance = static_cast<Instance *>(testInitInstance());
322
323 VerifyOrQuit(instance != nullptr);
324
325 SuccessOrQuit(sourceAddress.FromString(kSourceAddress));
326 SuccessOrQuit(destAddress.FromString(kDestAddress));
327
328 for (uint16_t size = kMinSize; size <= kMaxSize; size++)
329 {
330 Message *message = instance->Get<Ip6::Ip6>().NewMessage(sizeof(Ip4::Tcp::Header));
331 Ip4::Tcp::Header tcpHeader;
332
333 VerifyOrQuit(message != nullptr, "Ip6::NewMesssage() failed");
334 SuccessOrQuit(message->SetLength(size));
335
336 // Write TCP header with a random payload.
337
338 Random::NonCrypto::Fill(tcpHeader);
339 message->Write(0, tcpHeader);
340
341 if (size > sizeof(tcpHeader))
342 {
343 uint8_t buffer[kMaxSize];
344 uint16_t payloadSize = size - sizeof(tcpHeader);
345
346 Random::NonCrypto::FillBuffer(buffer, payloadSize);
347 message->WriteBytes(sizeof(tcpHeader), &buffer[0], payloadSize);
348 }
349
350 // Verify that the `Checksum::UpdateMessageChecksum` correctly
351 // updates the checksum field in the UDP header on the message.
352
353 Checksum::UpdateMessageChecksum(*message, sourceAddress, destAddress, Ip4::kProtoTcp);
354
355 SuccessOrQuit(message->Read(message->GetOffset(), tcpHeader));
356 VerifyOrQuit(tcpHeader.GetChecksum() != 0);
357
358 // Verify that the calculated UDP checksum is valid.
359
360 VerifyOrQuit(CalculateChecksum(sourceAddress, destAddress, Ip4::kProtoTcp, *message) == 0xffff);
361 message->Free();
362 }
363 }
364
TestUdp4MessageChecksum(void)365 void TestUdp4MessageChecksum(void)
366 {
367 constexpr uint16_t kMinSize = sizeof(Ip4::Udp::Header);
368 constexpr uint16_t kMaxSize = kBufferSize * 3 + 24;
369
370 const char *kSourceAddress = "12.34.56.78";
371 const char *kDestAddress = "87.65.43.21";
372
373 Ip4::Address sourceAddress;
374 Ip4::Address destAddress;
375
376 Instance *instance = static_cast<Instance *>(testInitInstance());
377
378 SuccessOrQuit(sourceAddress.FromString(kSourceAddress));
379 SuccessOrQuit(destAddress.FromString(kDestAddress));
380
381 VerifyOrQuit(instance != nullptr);
382
383 for (uint16_t size = kMinSize; size <= kMaxSize; size++)
384 {
385 Message *message = instance->Get<Ip6::Ip6>().NewMessage(sizeof(Ip4::Udp::Header));
386 Ip4::Udp::Header udpHeader;
387
388 VerifyOrQuit(message != nullptr, "Ip6::NewMesssage() failed");
389 SuccessOrQuit(message->SetLength(size));
390
391 // Write UDP header with a random payload.
392
393 Random::NonCrypto::Fill(udpHeader);
394 udpHeader.SetChecksum(0);
395 message->Write(0, udpHeader);
396
397 if (size > sizeof(udpHeader))
398 {
399 uint8_t buffer[kMaxSize];
400 uint16_t payloadSize = size - sizeof(udpHeader);
401
402 Random::NonCrypto::FillBuffer(buffer, payloadSize);
403 message->WriteBytes(sizeof(udpHeader), &buffer[0], payloadSize);
404 }
405
406 // Verify that the `Checksum::UpdateMessageChecksum` correctly
407 // updates the checksum field in the UDP header on the message.
408
409 Checksum::UpdateMessageChecksum(*message, sourceAddress, destAddress, Ip4::kProtoUdp);
410
411 SuccessOrQuit(message->Read(message->GetOffset(), udpHeader));
412 VerifyOrQuit(udpHeader.GetChecksum() != 0);
413
414 // Verify that the calculated UDP checksum is valid.
415
416 VerifyOrQuit(CalculateChecksum(sourceAddress, destAddress, Ip4::kProtoUdp, *message) == 0xffff);
417 message->Free();
418 }
419 }
420
TestIcmp4MessageChecksum(void)421 void TestIcmp4MessageChecksum(void)
422 {
423 // A captured ICMP echo request (ping) message. Checksum field is set to zero.
424 const uint8_t kExampleIcmpMessage[] = "\x08\x00\x00\x00\x67\x2e\x00\x00\x62\xaf\xf1\x61\x00\x04\xfc\x24"
425 "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17"
426 "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27"
427 "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37";
428 uint16_t kChecksumForExampleMessage = 0x5594;
429 Instance *instance = static_cast<Instance *>(testInitInstance());
430 Message *message = instance->Get<Ip6::Ip6>().NewMessage(sizeof(kExampleIcmpMessage));
431
432 Ip4::Address source;
433 Ip4::Address dest;
434
435 uint8_t mPayload[sizeof(kExampleIcmpMessage)];
436 Ip4::Icmp::Header icmpHeader;
437
438 SuccessOrQuit(message->AppendBytes(kExampleIcmpMessage, sizeof(kExampleIcmpMessage)));
439
440 // Random IPv4 address, ICMP message checksum does not include a presudo header like TCP and UDP.
441 source.mFields.m32 = 0x12345678;
442 dest.mFields.m32 = 0x87654321;
443
444 Checksum::UpdateMessageChecksum(*message, source, dest, Ip4::kProtoIcmp);
445
446 SuccessOrQuit(message->Read(0, icmpHeader));
447 VerifyOrQuit(icmpHeader.GetChecksum() == kChecksumForExampleMessage);
448
449 SuccessOrQuit(message->Read(message->GetOffset(), mPayload, sizeof(mPayload)));
450 VerifyOrQuit(CalculateChecksum(mPayload, sizeof(mPayload)) == 0xffff);
451 }
452
453 class ChecksumTester
454 {
455 public:
TestExampleVector(void)456 static void TestExampleVector(void)
457 {
458 // Example from RFC 1071
459 const uint8_t kTestVector[] = {0x00, 0x01, 0xf2, 0x03, 0xf4, 0xf5, 0xf6, 0xf7};
460 const uint16_t kTestVectorChecksum = 0xddf2;
461
462 Checksum checksum;
463
464 VerifyOrQuit(checksum.GetValue() == 0, "Incorrect initial checksum value");
465
466 checksum.AddData(kTestVector, sizeof(kTestVector));
467 VerifyOrQuit(checksum.GetValue() == kTestVectorChecksum);
468 VerifyOrQuit(checksum.GetValue() == CalculateChecksum(kTestVector, sizeof(kTestVector)), );
469 }
470 };
471
472 #if OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE
473
TestVerhoeffChecksum(void)474 void TestVerhoeffChecksum(void)
475 {
476 static constexpr uint16_t kMaxStringSize = 50;
477
478 const char *kExamples[] = {"307318421", "487300178", "123455672", "0", "15",
479 "999999994", "000000001", "100000000", "2363"};
480
481 const char *kInvalidFormats[] = {
482 "307 318421",
483 "307318421 ",
484 " 307318421",
485 "ABCDE",
486 };
487
488 char string[kMaxStringSize];
489 char checksum;
490 char expectedChecksum;
491
492 printf("\nVerhoeffChecksum\n");
493
494 for (const char *example : kExamples)
495 {
496 uint16_t length = StringLength(example, kMaxStringSize - 1);
497
498 memcpy(string, example, length + 1);
499
500 printf("- \"%s\"\n", string);
501
502 SuccessOrQuit(Utils::VerhoeffChecksum::Validate(string));
503
504 expectedChecksum = string[length - 1];
505
506 string[length - 1] = (expectedChecksum == '0') ? '9' : (expectedChecksum - 1);
507 VerifyOrQuit(Utils::VerhoeffChecksum::Validate(string) == kErrorFailed);
508
509 string[length - 1] = '\0';
510 SuccessOrQuit(Utils::VerhoeffChecksum::Calculate(string, checksum));
511 VerifyOrQuit(checksum == expectedChecksum);
512
513 string[length - 1] = expectedChecksum == '0' ? '9' : (expectedChecksum - 1);
514 }
515
516 printf("\nInvalid format:\n");
517
518 for (const char *example : kInvalidFormats)
519 {
520 printf("- \"%s\"\n", example);
521 VerifyOrQuit(Utils::VerhoeffChecksum::Validate(example) == kErrorInvalidArgs);
522 VerifyOrQuit(Utils::VerhoeffChecksum::Calculate(example, checksum) == kErrorInvalidArgs);
523 }
524 }
525
526 #endif // OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE
527
528 } // namespace ot
529
main(void)530 int main(void)
531 {
532 ot::ChecksumTester::TestExampleVector();
533 ot::TestUdpMessageChecksum();
534 ot::TestIcmp6MessageChecksum();
535 ot::TestTcp4MessageChecksum();
536 ot::TestUdp4MessageChecksum();
537 ot::TestIcmp4MessageChecksum();
538 #if OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE
539 ot::TestVerhoeffChecksum();
540 #endif
541
542 printf("All tests passed\n");
543 return 0;
544 }
545