diff --git a/Android.mk b/Android.mk index 240260b4e..c764b100a 100644 --- a/Android.mk +++ b/Android.mk @@ -228,6 +228,7 @@ LOCAL_SRC_FILES := \ src/core/meshcop/meshcop_tlvs.cpp \ src/core/meshcop/panid_query_client.cpp \ src/core/meshcop/timestamp.cpp \ + src/core/net/checksum.cpp \ src/core/net/dhcp6_client.cpp \ src/core/net/dhcp6_server.cpp \ src/core/net/dns_client.cpp \ diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index dcd0f837e..3e745cdef 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -440,6 +440,8 @@ openthread_core_files = [ "meshcop/panid_query_client.hpp", "meshcop/timestamp.cpp", "meshcop/timestamp.hpp", + "net/checksum.cpp", + "net/checksum.hpp", "net/dhcp6.hpp", "net/dhcp6_client.cpp", "net/dhcp6_client.hpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d83fc9cc2..fa63f38f2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -156,6 +156,7 @@ set(COMMON_SOURCES meshcop/meshcop_tlvs.cpp meshcop/panid_query_client.cpp meshcop/timestamp.cpp + net/checksum.cpp net/dhcp6_client.cpp net/dhcp6_server.cpp net/dns_client.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 0aa23e0f5..04b3c0c7d 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -198,6 +198,7 @@ SOURCES_COMMON = \ meshcop/meshcop_tlvs.cpp \ meshcop/panid_query_client.cpp \ meshcop/timestamp.cpp \ + net/checksum.cpp \ net/dhcp6_client.cpp \ net/dhcp6_server.cpp \ net/dns_client.cpp \ @@ -415,6 +416,7 @@ HEADERS_COMMON = \ meshcop/meshcop_tlvs.hpp \ meshcop/panid_query_client.hpp \ meshcop/timestamp.hpp \ + net/checksum.hpp \ net/dhcp6.hpp \ net/dhcp6_client.hpp \ net/dhcp6_server.hpp \ diff --git a/src/core/common/message.cpp b/src/core/common/message.cpp index 6dbf8066d..f8f1b2630 100644 --- a/src/core/common/message.cpp +++ b/src/core/common/message.cpp @@ -38,6 +38,7 @@ #include "common/instance.hpp" #include "common/locator-getters.hpp" #include "common/logging.hpp" +#include "net/checksum.hpp" #include "net/ip6.hpp" namespace ot { @@ -631,41 +632,6 @@ void Message::SetLinkInfo(const ThreadLinkInfo &aLinkInfo) #endif } -uint16_t Message::UpdateChecksum(uint16_t aChecksum, uint16_t aValue) -{ - uint16_t result = aChecksum + aValue; - return result + (result < aChecksum); -} - -uint16_t Message::UpdateChecksum(uint16_t aChecksum, const void *aBuf, uint16_t aLength) -{ - const uint8_t *bytes = reinterpret_cast(aBuf); - - for (int i = 0; i < aLength; i++) - { - aChecksum = UpdateChecksum(aChecksum, (i & 1) ? bytes[i] : static_cast(bytes[i] << 8)); - } - - return aChecksum; -} - -uint16_t Message::UpdateChecksum(uint16_t aChecksum, uint16_t aOffset, uint16_t aLength) const -{ - Chunk chunk; - - OT_ASSERT(aOffset + aLength <= GetLength()); - - GetFirstChunk(aOffset, aLength, chunk); - - while (chunk.GetLength() > 0) - { - aChecksum = Message::UpdateChecksum(aChecksum, chunk.GetData(), chunk.GetLength()); - GetNextChunk(aLength, chunk); - } - - return aChecksum; -} - bool Message::IsTimeSync(void) const { #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE diff --git a/src/core/common/message.hpp b/src/core/common/message.hpp index df0a7fe87..f21165c1a 100644 --- a/src/core/common/message.hpp +++ b/src/core/common/message.hpp @@ -228,6 +228,7 @@ protected: */ class Message : public Buffer { + friend class Checksum; friend class MessagePool; friend class MessageQueue; friend class PriorityQueue; @@ -836,41 +837,6 @@ public: */ void SetLinkInfo(const ThreadLinkInfo &aLinkInfo); - /** - * This static method updates a checksum. - * - * @param[in] aChecksum The checksum value to update. - * @param[in] aValue The 16-bit value to update @p aChecksum with. - * - * @returns The updated checksum. - * - */ - static uint16_t UpdateChecksum(uint16_t aChecksum, uint16_t aValue); - - /** - * This static method updates a checksum. - * - * @param[in] aChecksum The checksum value to update. - * @param[in] aBuf A pointer to a buffer. - * @param[in] aLength The number of bytes in @p aBuf. - * - * @returns The updated checksum. - * - */ - static uint16_t UpdateChecksum(uint16_t aChecksum, const void *aBuf, uint16_t aLength); - - /** - * This method is used to update a checksum value. - * - * @param[in] aChecksum Initial checksum value. - * @param[in] aOffset Byte offset within the message to begin checksum computation. - * @param[in] aLength Number of bytes to compute the checksum over. - * - * @retval The updated checksum value. - * - */ - uint16_t UpdateChecksum(uint16_t aChecksum, uint16_t aOffset, uint16_t aLength) const; - /** * This method returns a pointer to the message queue (if any) where this message is queued. * diff --git a/src/core/net/checksum.cpp b/src/core/net/checksum.cpp new file mode 100644 index 000000000..674130b31 --- /dev/null +++ b/src/core/net/checksum.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements checksum calculation. + */ + +#include "checksum.hpp" + +#include "common/code_utils.hpp" +#include "common/message.hpp" +#include "net/icmp6.hpp" +#include "net/udp6.hpp" + +namespace ot { + +void Checksum::AddUint8(uint8_t aUint8) +{ + uint16_t newValue = mValue; + + // BigEndian encoding: Even index is MSB and odd index is LSB. + + newValue += mAtOddIndex ? aUint8 : (static_cast(aUint8) << 8); + + // Calculate one's complement sum. + + if (newValue < mValue) + { + newValue++; + } + + mValue = newValue; + mAtOddIndex = !mAtOddIndex; +} + +void Checksum::AddUint16(uint16_t aUint16) +{ + // BigEndian encoding + AddUint8(static_cast(aUint16 >> 8)); + AddUint8(static_cast(aUint16 & 0xff)); +} + +void Checksum::AddData(const uint8_t *aBuffer, uint16_t aLength) +{ + for (uint16_t i = 0; i < aLength; i++) + { + AddUint8(aBuffer[i]); + } +} + +void Checksum::WriteToMessage(uint16_t aOffset, Message &aMessage) const +{ + uint16_t checksum = GetValue(); + + if (checksum != 0xffff) + { + checksum = ~checksum; + } + + checksum = Encoding::BigEndian::HostSwap16(checksum); + + aMessage.Write(aOffset, sizeof(checksum), &checksum); +} + +void Checksum::Calculate(const Ip6::Address &aSource, + const Ip6::Address &aDestination, + uint8_t aIpProto, + const Message & aMessage) +{ + Message::Chunk chunk; + uint16_t length = aMessage.GetLength() - aMessage.GetOffset(); + + // Pseudo-header for checksum calculation (RFC-2460). + + AddData(aSource.GetBytes(), sizeof(Ip6::Address)); + AddData(aDestination.GetBytes(), sizeof(Ip6::Address)); + AddUint16(length); + AddUint16(static_cast(aIpProto)); + + // Add message content (from offset to the end) to checksum. + + aMessage.GetFirstChunk(aMessage.GetOffset(), length, chunk); + + while (chunk.GetLength() > 0) + { + AddData(chunk.GetData(), chunk.GetLength()); + aMessage.GetNextChunk(length, chunk); + } +} + +otError Checksum::VerifyMessageChecksum(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint8_t aIpProto) +{ + Checksum checksum; + + checksum.Calculate(aMessageInfo.GetPeerAddr(), aMessageInfo.GetSockAddr(), aIpProto, aMessage); + + return (checksum.GetValue() == kValidRxChecksum) ? OT_ERROR_NONE : OT_ERROR_DROP; +} + +void Checksum::UpdateMessageChecksum(Message & aMessage, + const Ip6::Address &aSource, + const Ip6::Address &aDestination, + uint8_t aIpProto) +{ + uint16_t headerOffset; + Checksum checksum; + + switch (aIpProto) + { + case Ip6::kProtoUdp: + headerOffset = Ip6::Udp::Header::kChecksumFieldOffset; + break; + + case Ip6::kProtoIcmp6: + headerOffset = Ip6::Icmp::Header::kChecksumFieldOffset; + break; + + default: + ExitNow(); + } + + checksum.Calculate(aSource, aDestination, aIpProto, aMessage); + checksum.WriteToMessage(aMessage.GetOffset() + headerOffset, aMessage); + +exit: + return; +} + +} // namespace ot diff --git a/src/core/net/checksum.hpp b/src/core/net/checksum.hpp new file mode 100644 index 000000000..f61cbafb8 --- /dev/null +++ b/src/core/net/checksum.hpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for checksum calculation. + */ + +#ifndef CHECKSUM_HPP_ +#define CHECKSUM_HPP_ + +#include "openthread-core-config.h" + +#include + +#include "common/message.hpp" +#include "net/ip6_address.hpp" +#include "net/ip6_headers.hpp" +#include "net/socket.hpp" + +namespace ot { + +/** + * This class implements IP checksum calculation and verification. + * + */ +class Checksum +{ + friend class ChecksumTester; + +public: + /** + * This static method verifies the checksum in a given message (if UDP/ICMP6). + * + * @param[in] aMessage The message to verify its checksum. The `aMessage.GetOffset()` should point to start + * UDP/ICMP6 header. + * @param[in] aMessageInfo The message info associated with @p aMessage. + * @param[in] aIpProto The Internet Protocol value. + * + * @retval OT_ERROR_NONE The checksum is valid if UDP/ICMP6 protocol, or not a UDP/ICMP6 protocol. + * @retval OT_ERROR_DROP The check is not valid and message should be dropped. + * + */ + static otError VerifyMessageChecksum(const Message & aMessage, + const Ip6::MessageInfo &aMessageInfo, + uint8_t aIpProto); + + /** + * This static method calculates and then updates the checksum in a given message (if UDP/ICMP6). + * + * @param[inout] aMessage The message to update the checksum in. The `aMessage.GetOffset()` should point to start + * of the UDP/ICMP6 header. On exit the checksum field in UDP/ICMP6 header in the message + * is updated. + * @param[in] aSource The source address. + * @param[in] aDestination The destination address. + * @param[in] aIpProto The Internet Protocol value. + * + */ + static void UpdateMessageChecksum(Message & aMessage, + const Ip6::Address &aSource, + const Ip6::Address &aDestination, + uint8_t aIpProto); + +private: + Checksum(void) + : mValue(0) + , mAtOddIndex(false) + { + } + + uint16_t GetValue(void) const { return mValue; } + void AddUint8(uint8_t aUint8); + void AddUint16(uint16_t aUint16); + void AddData(const uint8_t *aBuffer, uint16_t aLength); + void WriteToMessage(uint16_t aOffset, Message &aMessage) const; + void Calculate(const Ip6::Address &aSource, + const Ip6::Address &aDestination, + uint8_t aIpProto, + const Message & aMessage); + + enum : uint16_t + { + kValidRxChecksum = 0xffff, + }; + + uint16_t mValue; + bool mAtOddIndex; +}; + +} // namespace ot + +#endif // CHECKSUM_HPP_ diff --git a/src/core/net/icmp6.cpp b/src/core/net/icmp6.cpp index 33419924d..2d5659f57 100644 --- a/src/core/net/icmp6.cpp +++ b/src/core/net/icmp6.cpp @@ -39,10 +39,9 @@ #include "common/locator-getters.hpp" #include "common/logging.hpp" #include "common/message.hpp" +#include "net/checksum.hpp" #include "net/ip6.hpp" -using ot::Encoding::BigEndian::HostSwap16; - namespace ot { namespace Ip6 { @@ -139,20 +138,13 @@ exit: otError Icmp::HandleMessage(Message &aMessage, MessageInfo &aMessageInfo) { - otError error = OT_ERROR_NONE; - uint16_t payloadLength; - Header icmp6Header; - uint16_t checksum; + otError error = OT_ERROR_NONE; + Header icmp6Header; VerifyOrExit(aMessage.Read(aMessage.GetOffset(), sizeof(icmp6Header), &icmp6Header) == sizeof(icmp6Header), error = OT_ERROR_PARSE); - payloadLength = aMessage.GetLength() - aMessage.GetOffset(); - // verify checksum - checksum = Ip6::ComputePseudoheaderChecksum(aMessageInfo.GetPeerAddr(), aMessageInfo.GetSockAddr(), payloadLength, - kProtoIcmp6); - checksum = aMessage.UpdateChecksum(checksum, aMessage.GetOffset(), payloadLength); - VerifyOrExit(checksum == 0xffff, error = OT_ERROR_PARSE); + SuccessOrExit(error = Checksum::VerifyMessageChecksum(aMessage, aMessageInfo, kProtoIcmp6)); if (icmp6Header.GetType() == Header::kTypeEchoRequest) { @@ -244,18 +236,5 @@ exit: return error; } -void Icmp::UpdateChecksum(Message &aMessage, uint16_t aChecksum) -{ - aChecksum = aMessage.UpdateChecksum(aChecksum, aMessage.GetOffset(), aMessage.GetLength() - aMessage.GetOffset()); - - if (aChecksum != 0xffff) - { - aChecksum = ~aChecksum; - } - - aChecksum = HostSwap16(aChecksum); - aMessage.Write(aMessage.GetOffset() + Header::kChecksumFieldOffset, sizeof(aChecksum), &aChecksum); -} - } // namespace Ip6 } // namespace ot diff --git a/src/core/net/icmp6.hpp b/src/core/net/icmp6.hpp index 7992406cb..6ef952686 100644 --- a/src/core/net/icmp6.hpp +++ b/src/core/net/icmp6.hpp @@ -297,15 +297,6 @@ public: */ otError HandleMessage(Message &aMessage, MessageInfo &aMessageInfo); - /** - * This method updates the ICMPv6 checksum. - * - * @param[in] aMessage A reference to the ICMPv6 message. - * @param[in] aChecksum The pseudo-header checksum value. - * - */ - void UpdateChecksum(Message &aMessage, uint16_t aChecksum); - /** * This method indicates whether or not ICMPv6 Echo processing is enabled. * diff --git a/src/core/net/ip6.cpp b/src/core/net/ip6.cpp index b4d1b4065..123f735dd 100644 --- a/src/core/net/ip6.cpp +++ b/src/core/net/ip6.cpp @@ -40,6 +40,7 @@ #include "common/logging.hpp" #include "common/message.hpp" #include "common/random.hpp" +#include "net/checksum.hpp" #include "net/icmp6.hpp" #include "net/ip6_address.hpp" #include "net/ip6_filter.hpp" @@ -170,26 +171,6 @@ exit: return error; } -uint16_t Ip6::UpdateChecksum(uint16_t aChecksum, const Address &aAddress) -{ - return Message::UpdateChecksum(aChecksum, aAddress.mFields.m8, sizeof(aAddress)); -} - -uint16_t Ip6::ComputePseudoheaderChecksum(const Address &aSource, - const Address &aDestination, - uint16_t aLength, - uint8_t aProto) -{ - uint16_t checksum; - - checksum = Message::UpdateChecksum(0, aLength); - checksum = Message::UpdateChecksum(checksum, static_cast(aProto)); - checksum = UpdateChecksum(checksum, aSource); - checksum = UpdateChecksum(checksum, aDestination); - - return checksum; -} - void Ip6::SetReceiveDatagramCallback(otIp6ReceiveCallback aCallback, void *aCallbackContext) { mReceiveIp6DatagramCallback = aCallback; @@ -458,7 +439,6 @@ otError Ip6::SendDatagram(Message &aMessage, MessageInfo &aMessageInfo, uint8_t otError error = OT_ERROR_NONE; Header header; uint16_t payloadLength = aMessage.GetLength(); - uint16_t checksum; header.Init(); header.SetDscp(PriorityToDscp(aMessage.GetPriority())); @@ -495,21 +475,7 @@ otError Ip6::SendDatagram(Message &aMessage, MessageInfo &aMessageInfo, uint8_t SuccessOrExit(error = aMessage.Prepend(&header, sizeof(header))); - checksum = ComputePseudoheaderChecksum(header.GetSource(), header.GetDestination(), payloadLength, aIpProto); - - switch (aIpProto) - { - case kProtoUdp: - mUdp.UpdateChecksum(aMessage, checksum); - break; - - case kProtoIcmp6: - mIcmp.UpdateChecksum(aMessage, checksum); - break; - - default: - break; - } + Checksum::UpdateMessageChecksum(aMessage, header.GetSource(), header.GetDestination(), aIpProto); if (aMessageInfo.GetPeerAddr().IsMulticastLargerThanRealmLocal()) { diff --git a/src/core/net/ip6.hpp b/src/core/net/ip6.hpp index 4a34f69db..e73d63baa 100644 --- a/src/core/net/ip6.hpp +++ b/src/core/net/ip6.hpp @@ -225,22 +225,6 @@ public: */ otError HandleDatagram(Message &aMessage, Netif *aNetif, const void *aLinkMessageInfo, bool aFromNcpHost); - /** - * This static method computes the pseudoheader checksum. - * - * @param[in] aSource A reference to the IPv6 source address. - * @param[in] aDestination A reference to the IPv6 destination address. - * @param[in] aLength The IPv6 Payload Length value. - * @param[in] aProto The IPv6 Next Header value. - * - * @returns The pseudoheader checksum. - * - */ - static uint16_t ComputePseudoheaderChecksum(const Address &aSource, - const Address &aDestination, - uint16_t aLength, - uint8_t aProto); - /** * This method registers a callback to provide received raw IPv6 datagrams. * @@ -343,9 +327,8 @@ private: static void HandleSendQueue(Tasklet &aTasklet); void HandleSendQueue(void); - static uint8_t PriorityToDscp(Message::Priority aPriority); - static otError GetDatagramPriority(const uint8_t *aData, uint16_t aDataLen, Message::Priority &aPriority); - static uint16_t UpdateChecksum(uint16_t aChecksum, const Address &aAddress); + static uint8_t PriorityToDscp(Message::Priority aPriority); + static otError GetDatagramPriority(const uint8_t *aData, uint16_t aDataLen, Message::Priority &aPriority); void EnqueueDatagram(Message &aMessage); otError ProcessReceiveCallback(Message & aMessage, diff --git a/src/core/net/ip6_address.hpp b/src/core/net/ip6_address.hpp index 286c75835..831bb7c58 100644 --- a/src/core/net/ip6_address.hpp +++ b/src/core/net/ip6_address.hpp @@ -525,6 +525,14 @@ public: */ typedef String InfoString; + /** + * This method gets the IPv6 address as a pointer to a byte array. + * + * @returns A pointer to a byte array containing the IPv6 address. + * + */ + const uint8_t *GetBytes(void) const { return mFields.m8; } + /** * This method indicates whether or not the IPv6 address is the Unspecified Address. * diff --git a/src/core/net/ip6_headers.hpp b/src/core/net/ip6_headers.hpp index ed70339da..88d60122f 100644 --- a/src/core/net/ip6_headers.hpp +++ b/src/core/net/ip6_headers.hpp @@ -254,6 +254,14 @@ public: */ Address &GetSource(void) { return mSource; } + /** + * This method returns the IPv6 Source address. + * + * @returns A reference to the IPv6 Source address. + * + */ + const Address &GetSource(void) const { return mSource; } + /** * This method sets the IPv6 Source address. * @@ -270,6 +278,14 @@ public: */ Address &GetDestination(void) { return mDestination; } + /** + * This method returns the IPv6 Destination address. + * + * @returns A reference to the IPv6 Destination address. + * + */ + const Address &GetDestination(void) const { return mDestination; } + /** * This method sets the IPv6 Destination address. * diff --git a/src/core/net/udp6.cpp b/src/core/net/udp6.cpp index ba56ae478..f5d985b98 100644 --- a/src/core/net/udp6.cpp +++ b/src/core/net/udp6.cpp @@ -41,10 +41,9 @@ #include "common/encoding.hpp" #include "common/instance.hpp" #include "common/locator-getters.hpp" +#include "net/checksum.hpp" #include "net/ip6.hpp" -using ot::Encoding::BigEndian::HostSwap16; - namespace ot { namespace Ip6 { @@ -364,27 +363,16 @@ exit: otError Udp::HandleMessage(Message &aMessage, MessageInfo &aMessageInfo) { - otError error = OT_ERROR_NONE; - Header udpHeader; - uint16_t payloadLength; - uint16_t checksum; - - payloadLength = aMessage.GetLength() - aMessage.GetOffset(); - - // check length - VerifyOrExit(payloadLength >= sizeof(Header), error = OT_ERROR_PARSE); - - // verify checksum - checksum = Ip6::ComputePseudoheaderChecksum(aMessageInfo.GetPeerAddr(), aMessageInfo.GetSockAddr(), payloadLength, - kProtoUdp); - checksum = aMessage.UpdateChecksum(checksum, aMessage.GetOffset(), payloadLength); - -#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION - VerifyOrExit(checksum == 0xffff, error = OT_ERROR_DROP); -#endif + otError error = OT_ERROR_NONE; + Header udpHeader; VerifyOrExit(aMessage.Read(aMessage.GetOffset(), sizeof(udpHeader), &udpHeader) == sizeof(udpHeader), error = OT_ERROR_PARSE); + +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + SuccessOrExit(error = Checksum::VerifyMessageChecksum(aMessage, aMessageInfo, kProtoUdp)); +#endif + aMessage.MoveOffset(sizeof(udpHeader)); aMessageInfo.mPeerPort = udpHeader.GetSourcePort(); aMessageInfo.mSockPort = udpHeader.GetDestinationPort(); @@ -420,19 +408,6 @@ exit: return; } -void Udp::UpdateChecksum(Message &aMessage, uint16_t aChecksum) -{ - aChecksum = aMessage.UpdateChecksum(aChecksum, aMessage.GetOffset(), aMessage.GetLength() - aMessage.GetOffset()); - - if (aChecksum != 0xffff) - { - aChecksum = ~aChecksum; - } - - aChecksum = HostSwap16(aChecksum); - aMessage.Write(aMessage.GetOffset() + Header::kChecksumFieldOffset, sizeof(aChecksum), &aChecksum); -} - bool Udp::IsMlePort(uint16_t aPort) const { bool isMlePort = (aPort == Mle::kUdpPort); diff --git a/src/core/net/udp6.hpp b/src/core/net/udp6.hpp index 1d5df513c..2acff21bf 100644 --- a/src/core/net/udp6.hpp +++ b/src/core/net/udp6.hpp @@ -528,15 +528,6 @@ public: */ void HandlePayload(Message &aMessage, MessageInfo &aMessageInfo); - /** - * This method updates the UDP checksum. - * - * @param[in] aMessage A reference to the UDP message. - * @param[in] aChecksum The pseudo-header checksum value. - * - */ - void UpdateChecksum(Message &aMessage, uint16_t aChecksum); - /** * This method returns the head of UDP Sockets list. * diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 49ced260d..bc754f900 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -77,6 +77,29 @@ add_executable(test-child test_child.cpp ) +add_executable(test-checksum + ${COMMON_SOURCES} + test_checksum.cpp +) + +target_include_directories(test-checksum + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(test-checksum + PRIVATE + ${COMMON_COMPILE_OPTIONS} +) + +target_link_libraries(test-checksum + PRIVATE + ${COMMON_LIBS} +) + +add_test(NAME test-checksum COMMAND test-checksum) + + target_include_directories(test-child PRIVATE ${COMMON_INCLUDES} @@ -536,7 +559,7 @@ target_link_libraries(test-timer add_test(NAME test-timer COMMAND test-timer) set_target_properties( - test-aes test-child test-child-table test-flash test-heap test-hmac-sha256 test-ip6-address test-link-quality test-linked-list test-lowpan test-mac-frame test-message test-message-queue test-multicast-listeners-table test-netif test-network-data test-pool test-priority-queue test-pskc test-steering-data test-string test-timer + test-aes test-checksum test-child test-child-table test-flash test-heap test-hmac-sha256 test-ip6-address test-link-quality test-linked-list test-lowpan test-mac-frame test-message test-message-queue test-multicast-listeners-table test-netif test-network-data test-pool test-priority-queue test-pskc test-steering-data test-string test-timer PROPERTIES C_STANDARD 99 CXX_STANDARD 11 diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 5d3d29197..3ae4f4918 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -107,6 +107,7 @@ check_PROGRAMS = \ if OPENTHREAD_ENABLE_FTD check_PROGRAMS += \ test-aes \ + test-checksum \ test-child \ test-child-table \ test-flash \ @@ -171,6 +172,9 @@ COMMON_SOURCES = test_platform.cpp test_util.cpp test_aes_LDADD = $(COMMON_LDADD) test_aes_SOURCES = $(COMMON_SOURCES) test_aes.cpp +test_checksum_LDADD = $(COMMON_LDADD) +test_checksum_SOURCES = $(COMMON_SOURCES) test_checksum.cpp + test_child_LDADD = $(COMMON_LDADD) test_child_SOURCES = $(COMMON_SOURCES) test_child.cpp diff --git a/tests/unit/test_checksum.cpp b/tests/unit/test_checksum.cpp new file mode 100644 index 000000000..c4a0be203 --- /dev/null +++ b/tests/unit/test_checksum.cpp @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "common/encoding.hpp" +#include "common/instance.hpp" +#include "common/message.hpp" +#include "common/random.hpp" +#include "net/checksum.hpp" +#include "net/icmp6.hpp" +#include "net/udp6.hpp" + +#include "test_platform.h" +#include "test_util.hpp" + +namespace ot { + +uint16_t CalculateChecksum(const void *aBuffer, uint16_t aLength) +{ + // Calculates checksum over a given buffer data. This implementation + // is inspired by the algorithm from RFC-1071. + + uint32_t sum = 0; + const uint8_t *bytes = reinterpret_cast(aBuffer); + + while (aLength >= sizeof(uint16_t)) + { + sum += Encoding::BigEndian::ReadUint16(bytes); + bytes += sizeof(uint16_t); + aLength -= sizeof(uint16_t); + } + + if (aLength > 0) + { + sum += (static_cast(bytes[0]) << 8); + } + + // Fold 32-bit sum to 16 bits. + + while (sum >> 16) + { + sum = (sum & 0xffff) + (sum >> 16); + } + + return static_cast(sum & 0xffff); +} + +uint16_t CalculateChecksum(const Ip6::Address &aSource, + const Ip6::Address &aDestination, + uint8_t aIpProto, + const Message & aMessage) +{ + // This method calculates the checksum over an IPv6 message. + + enum : uint16_t + { + kMaxPayload = 1024, + }; + + OT_TOOL_PACKED_BEGIN + struct PseudoHeader + { + Ip6::Address mSource; + Ip6::Address mDestination; + uint32_t mPayloadLength; + uint32_t mProtocol; + } OT_TOOL_PACKED_END; + + OT_TOOL_PACKED_BEGIN + struct ChecksumData + { + PseudoHeader mPseudoHeader; + uint8_t mPayload[kMaxPayload]; + } OT_TOOL_PACKED_END; + + ChecksumData data; + uint16_t payloadLength; + + payloadLength = aMessage.GetLength() - aMessage.GetOffset(); + + data.mPseudoHeader.mSource = aSource; + data.mPseudoHeader.mDestination = aDestination; + data.mPseudoHeader.mProtocol = Encoding::BigEndian::HostSwap32(aIpProto); + data.mPseudoHeader.mPayloadLength = Encoding::BigEndian::HostSwap32(payloadLength); + + VerifyOrQuit(aMessage.Read(aMessage.GetOffset(), payloadLength, data.mPayload) == payloadLength, + "Message::Read() failed"); + + return CalculateChecksum(&data, sizeof(PseudoHeader) + payloadLength); +} + +void CorruptMessage(Message &aMessage) +{ + // Change a random bit in the message. + + uint16_t byteOffset; + uint8_t bitOffset; + uint8_t byte; + + byteOffset = Random::NonCrypto::GetUint16InRange(0, aMessage.GetLength()); + + VerifyOrQuit(aMessage.Read(byteOffset, sizeof(uint8_t), &byte) == sizeof(uint8_t), "Read failed"); + + bitOffset = Random::NonCrypto::GetUint8InRange(0, CHAR_BIT); + + byte ^= (1 << bitOffset); + + aMessage.Write(byteOffset, sizeof(uint8_t), &byte); +} + +void TestUdpMessageChecksum(void) +{ + enum : uint16_t + { + kMinSize = sizeof(Ip6::Udp::Header), + kMaxSize = kBufferSize * 3 + 24, + }; + + const char *kSourceAddress = "fd00:1122:3344:5566:7788:99aa:bbcc:ddee"; + const char *kDestAddress = "fd01:2345:6789:abcd:ef01:2345:6789:abcd"; + + Instance *instance = static_cast(testInitInstance()); + + VerifyOrQuit(instance != nullptr, "Null OpenThread instance\n"); + + for (uint16_t size = kMinSize; size <= kMaxSize; size++) + { + Message * message = instance->Get().NewMessage(sizeof(Ip6::Udp::Header)); + Ip6::Udp::Header udpHeader; + Ip6::MessageInfo messageInfo; + + VerifyOrQuit(message != nullptr, "Ip6::NewMesssage() failed"); + SuccessOrQuit(message->SetLength(size), "Message::SetLength() failed"); + + // Write UDP header with a random payload. + + Random::NonCrypto::FillBuffer(reinterpret_cast(&udpHeader), sizeof(udpHeader)); + udpHeader.SetChecksum(0); + message->Write(0, sizeof(udpHeader), &udpHeader); + + if (size > sizeof(udpHeader)) + { + uint8_t buffer[kMaxSize]; + uint16_t payloadSize = size - sizeof(udpHeader); + + Random::NonCrypto::FillBuffer(buffer, payloadSize); + message->Write(sizeof(udpHeader), payloadSize, &buffer[0]); + } + + SuccessOrQuit(messageInfo.GetSockAddr().FromString(kSourceAddress), "FromString() failed"); + SuccessOrQuit(messageInfo.GetPeerAddr().FromString(kDestAddress), "FromString() failed"); + + // Verify that the `Checksum::UpdateMessageChecksum` correctly + // updates the checksum field in the UDP header on the message. + + Checksum::UpdateMessageChecksum(*message, messageInfo.GetSockAddr(), messageInfo.GetPeerAddr(), Ip6::kProtoUdp); + + VerifyOrQuit(message->Read(message->GetOffset(), sizeof(udpHeader), &udpHeader) == sizeof(udpHeader), + "Message::Read() failed"); + VerifyOrQuit(udpHeader.GetChecksum() != 0, "Failed to update checksum"); + + // Verify that the calculated UDP checksum is valid. + + VerifyOrQuit( + CalculateChecksum(messageInfo.GetSockAddr(), messageInfo.GetPeerAddr(), Ip6::kProtoUdp, *message) == 0xffff, + "UDP checksum does not match expected value"); + + // Verify that `Checksum::VerifyMessageChecksum()` accepts the + // message and its calculated checksum. + + SuccessOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoUdp), + "Checksum::VerifyMessageChecksum() failed"); + + // Corrupt the message and verify that checksum is no longer accepted. + + CorruptMessage(*message); + + VerifyOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoUdp) != OT_ERROR_NONE, + "Checksum passed on corrupted message"); + + message->Free(); + } +} + +void TestIcmp6MessageChecksum(void) +{ + enum : uint16_t + { + kMinSize = sizeof(Ip6::Icmp::Header), + kMaxSize = kBufferSize * 3 + 24, + }; + + const char *kSourceAddress = "fd00:feef:dccd:baab:9889:7667:5444:3223"; + const char *kDestAddress = "fd01:abab:beef:cafe:1234:5678:9abc:0"; + + Instance *instance = static_cast(testInitInstance()); + + VerifyOrQuit(instance != nullptr, "Null OpenThread instance\n"); + + for (uint16_t size = kMinSize; size <= kMaxSize; size++) + { + Message * message = instance->Get().NewMessage(sizeof(Ip6::Icmp::Header)); + Ip6::Icmp::Header icmp6Header; + Ip6::MessageInfo messageInfo; + + VerifyOrQuit(message != nullptr, "Ip6::NewMesssage() failed"); + SuccessOrQuit(message->SetLength(size), "Message::SetLength() failed"); + + // Write ICMP6 header with a random payload. + + Random::NonCrypto::FillBuffer(reinterpret_cast(&icmp6Header), sizeof(icmp6Header)); + icmp6Header.SetChecksum(0); + message->Write(0, sizeof(icmp6Header), &icmp6Header); + + if (size > sizeof(icmp6Header)) + { + uint8_t buffer[kMaxSize]; + uint16_t payloadSize = size - sizeof(icmp6Header); + + Random::NonCrypto::FillBuffer(buffer, payloadSize); + message->Write(sizeof(icmp6Header), payloadSize, &buffer[0]); + } + + SuccessOrQuit(messageInfo.GetSockAddr().FromString(kSourceAddress), "FromString() failed"); + SuccessOrQuit(messageInfo.GetPeerAddr().FromString(kDestAddress), "FromString() failed"); + + // Verify that the `Checksum::UpdateMessageChecksum` correctly + // updates the checksum field in the ICMP6 header on the message. + + Checksum::UpdateMessageChecksum(*message, messageInfo.GetSockAddr(), messageInfo.GetPeerAddr(), + Ip6::kProtoIcmp6); + + VerifyOrQuit(message->Read(message->GetOffset(), sizeof(icmp6Header), &icmp6Header) == sizeof(icmp6Header), + "Message::Read() failed"); + VerifyOrQuit(icmp6Header.GetChecksum() != 0, "Failed to update checksum"); + + // Verify that the calculated ICMP6 checksum is valid. + + VerifyOrQuit(CalculateChecksum(messageInfo.GetSockAddr(), messageInfo.GetPeerAddr(), Ip6::kProtoIcmp6, + *message) == 0xffff, + "UDP checksum does not match expected value"); + + // Verify that `Checksum::VerifyMessageChecksum()` accepts the + // message and its calculated checksum. + + SuccessOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoIcmp6), + "Checksum::VerifyMessageChecksum() failed"); + + // Corrupt the message and verify that checksum is no longer accepted. + + CorruptMessage(*message); + + VerifyOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoIcmp6) != OT_ERROR_NONE, + "Checksum passed on corrupted message"); + + message->Free(); + } +} + +class ChecksumTester +{ +public: + static void TestExampleVector(void) + { + // Example from RFC 1071 + const uint8_t kTestVector[] = {0x00, 0x01, 0xf2, 0x03, 0xf4, 0xf5, 0xf6, 0xf7}; + const uint16_t kTestVectorChecksum = 0xddf2; + + Checksum checksum; + + VerifyOrQuit(checksum.GetValue() == 0, "Incorrect initial checksum value"); + + checksum.AddData(kTestVector, sizeof(kTestVector)); + VerifyOrQuit(checksum.GetValue() == kTestVectorChecksum, "Checksum::AddData() failed"); + VerifyOrQuit(checksum.GetValue() == CalculateChecksum(kTestVector, sizeof(kTestVector)), + "Checksum::AddData() failed"); + } +}; + +} // namespace ot + +int main(void) +{ + ot::ChecksumTester::TestExampleVector(); + ot::TestUdpMessageChecksum(); + ot::TestIcmp6MessageChecksum(); + printf("All tests passed\n"); + return 0; +}