From 89d5799d7643684675f22167b22f47880d618310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Bida?= <85344032+canisLupus1313@users.noreply.github.com> Date: Fri, 17 Mar 2023 17:44:26 +0100 Subject: [PATCH] [dns] implement DNS query over TCP (#8474) This commit introduces DNS query over TCP protocol. --- .github/workflows/posix.yml | 1 + etc/cmake/options.cmake | 2 +- include/openthread/dns_client.h | 24 +- include/openthread/instance.h | 2 +- script/cmake-build | 5 +- src/cli/README.md | 12 +- src/cli/cli.cpp | 17 + src/core/config/dns_client.h | 21 ++ src/core/net/dns_client.cpp | 333 +++++++++++++++++- src/core/net/dns_client.hpp | 86 ++++- tests/scripts/expect/cli-tcp-tls.exp | 5 +- tests/scripts/expect/cli-tcp.exp | 5 +- .../expect/tun-dns-over-tcp-client.exp | 46 +++ 13 files changed, 529 insertions(+), 30 deletions(-) create mode 100755 tests/scripts/expect/tun-dns-over-tcp-client.exp diff --git a/.github/workflows/posix.yml b/.github/workflows/posix.yml index 78a1de66c..a197b39ae 100644 --- a/.github/workflows/posix.yml +++ b/.github/workflows/posix.yml @@ -95,6 +95,7 @@ jobs: sudo apt-get install --no-install-recommends -y bind9-host ntp socat sudo systemctl restart ntp sudo socat 'UDP6-LISTEN:53,fork,reuseaddr,bind=[::1]' UDP:127.0.0.53:53 & + socat 'TCP6-LISTEN:2000,fork,reuseaddr' TCP:127.0.0.53:53 & host ipv6.google.com 127.0.0.53 host ipv6.google.com ::1 ulimit -c unlimited diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 4df97cd41..7ed2d73bd 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -101,6 +101,7 @@ ot_option(OT_DHCP6_CLIENT OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE "DHCP6 client") ot_option(OT_DHCP6_SERVER OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE "DHCP6 server") ot_option(OT_DIAGNOSTIC OPENTHREAD_CONFIG_DIAG_ENABLE "diagnostic") ot_option(OT_DNS_CLIENT OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE "DNS client") +ot_option(OT_DNS_CLIENT_OVER_TCP OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE "Enable dns query over tcp") ot_option(OT_DNS_DSO OPENTHREAD_CONFIG_DNS_DSO_ENABLE "DNS Stateful Operations (DSO)") ot_option(OT_DNS_UPSTREAM_QUERY OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE "Allow sending DNS queries to upstream") ot_option(OT_DNSSD_SERVER OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE "DNS-SD server") @@ -192,4 +193,3 @@ endif() if(OT_POSIX_SETTINGS_PATH) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_POSIX_SETTINGS_PATH=${OT_POSIX_SETTINGS_PATH}") endif() - diff --git a/include/openthread/dns_client.h b/include/openthread/dns_client.h index 95da750b6..c7f25b772 100644 --- a/include/openthread/dns_client.h +++ b/include/openthread/dns_client.h @@ -80,6 +80,19 @@ typedef enum OT_DNS_NAT64_DISALLOW = 2, ///< Do not allow NAT64 address translation during DNS client address resolution. } otDnsNat64Mode; +/** + * This enumeration type represents the DNS transport protocol in an `otDnsQueryConfig`. + * + * This `OT_DNS_TRANSPORT_TCP` is only supported when `OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE` is enabled. + * + */ +typedef enum +{ + OT_DNS_TRANSPORT_UNSPECIFIED = 0, /// DNS transport is unspecified. + OT_DNS_TRANSPORT_UDP = 1, /// DNS query should be sent via UDP. + OT_DNS_TRANSPORT_TCP = 2, /// DNS query should be sent via TCP. +} otDnsTransportProto; + /** * This structure represents a DNS query configuration. * @@ -89,11 +102,12 @@ typedef enum */ typedef struct otDnsQueryConfig { - otSockAddr mServerSockAddr; ///< Server address (IPv6 address/port). All zero or zero port for unspecified. - uint32_t mResponseTimeout; ///< Wait time (in msec) to rx response. Zero indicates unspecified value. - uint8_t mMaxTxAttempts; ///< Maximum tx attempts before reporting failure. Zero for unspecified value. - otDnsRecursionFlag mRecursionFlag; ///< Indicates whether the server can resolve the query recursively or not. - otDnsNat64Mode mNat64Mode; ///< Allow/Disallow NAT64 address translation during address resolution. + otSockAddr mServerSockAddr; ///< Server address (IPv6 address/port). All zero or zero port for unspecified. + uint32_t mResponseTimeout; ///< Wait time (in msec) to rx response. Zero indicates unspecified value. + uint8_t mMaxTxAttempts; ///< Maximum tx attempts before reporting failure. Zero for unspecified value. + otDnsRecursionFlag mRecursionFlag; ///< Indicates whether the server can resolve the query recursively or not. + otDnsNat64Mode mNat64Mode; ///< Allow/Disallow NAT64 address translation during address resolution. + otDnsTransportProto mTransportProto; ///< Select default transport protocol. } otDnsQueryConfig; /** diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 87ce6f735..8f13ff1be 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (300) +#define OPENTHREAD_API_VERSION (301) /** * @addtogroup api-instance diff --git a/script/cmake-build b/script/cmake-build index 8d726831a..364721623 100755 --- a/script/cmake-build +++ b/script/cmake-build @@ -155,13 +155,16 @@ main() case "${platform}" in posix) local_options+=( + "-DOT_TCP=OFF" "-DOT_LOG_OUTPUT=PLATFORM_DEFINED" "-DOT_POSIX_MAX_POWER_TABLE=ON" ) options+=("${OT_POSIX_SIM_COMMON_OPTIONS[@]}" "${local_options[@]}") ;; simulation) - local_options=("-DOT_LINK_RAW=ON") + local_options=("-DOT_LINK_RAW=ON" + "-DOT_DNS_CLIENT_OVER_TCP=ON" + ) options+=("${OT_POSIX_SIM_COMMON_OPTIONS[@]}" "${local_options[@]}") ;; *) diff --git a/src/cli/README.md b/src/cli/README.md index f5cdf2e14..f3483c1af 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -1082,16 +1082,19 @@ Server: [fd00:0:0:0:0:0:0:1]:1234 ResponseTimeout: 5000 ms MaxTxAttempts: 2 RecursionDesired: no +TransportProtocol: udp Done > ``` -### dns config \[DNS server IP\] \[DNS server port\] \[response timeout (ms)\] \[max tx attempts\] \[recursion desired (boolean)\] +### dns config \[DNS server IP\] \[DNS server port\] \[response timeout (ms)\] \[max tx attempts\] \[recursion desired (boolean)\] \[transport protocol\] Set the default query config. +To set protocol effectively to tcp `OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE` is required. + ```bash -> dns config fd00::1 1234 5000 2 0 +> dns config fd00::1 1234 5000 2 0 tcp Done > dns config @@ -1099,6 +1102,7 @@ Server: [fd00:0:0:0:0:0:0:1]:1234 ResponseTimeout: 5000 ms MaxTxAttempts: 2 RecursionDesired: no +TransportProtocol: tcp Done ``` @@ -1116,12 +1120,14 @@ RecursionDesired: yes Done ``` -### dns resolve \ \[DNS server IP\] \[DNS server port\] \[response timeout (ms)\] \[max tx attempts\] \[recursion desired (boolean)\] +### dns resolve \ \[DNS server IP\] \[DNS server port\] \[response timeout (ms)\] \[max tx attempts\] \[recursion desired (boolean)\] \[transport protocol\] Send DNS Query to obtain IPv6 address for given hostname. The parameters after `hostname` are optional. Any unspecified (or zero) value for these optional parameters is replaced by the value from the current default config (`dns config`). +To use tcp, `OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE` is required. + ```bash > dns resolve ipv6.google.com > DNS response for ipv6.google.com - 2a00:1450:401b:801:0:0:0:200e TTL: 300 diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 75b4e2642..a0cb6162e 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -3011,6 +3011,10 @@ template <> otError Interpreter::Process(Arg aArgs[]) OutputLine("MaxTxAttempts: %u", defaultConfig->mMaxTxAttempts); OutputLine("RecursionDesired: %s", (defaultConfig->mRecursionFlag == OT_DNS_FLAG_RECURSION_DESIRED) ? "yes" : "no"); +#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE + OutputLine("TransportProtocol: %s", + (defaultConfig->mTransportProto == OT_DNS_TRANSPORT_UDP) ? "udp" : "tcp"); +#endif } /* clang-format off */ /** @@ -3286,6 +3290,19 @@ otError Interpreter::GetDnsConfig(Arg aArgs[], otDnsQueryConfig *&aConfig) SuccessOrExit(error = aArgs[4].ParseAsBool(recursionDesired)); aConfig->mRecursionFlag = recursionDesired ? OT_DNS_FLAG_RECURSION_DESIRED : OT_DNS_FLAG_NO_RECURSION; + VerifyOrExit(!aArgs[5].IsEmpty()); + if (aArgs[5] == "tcp") + { + aConfig->mTransportProto = OT_DNS_TRANSPORT_TCP; + } + else if (aArgs[5] == "udp") + { + aConfig->mTransportProto = OT_DNS_TRANSPORT_UDP; + } + else + { + error = OT_ERROR_INVALID_ARGS; + } exit: return error; } diff --git a/src/core/config/dns_client.h b/src/core/config/dns_client.h index e0a9ec894..99a6520cf 100644 --- a/src/core/config/dns_client.h +++ b/src/core/config/dns_client.h @@ -35,6 +35,7 @@ #ifndef CONFIG_DNS_CLIENT_H_ #define CONFIG_DNS_CLIENT_H_ +#include "config/ip6.h" #include "config/srp_client.h" /** @@ -150,4 +151,24 @@ #define OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_RECURSION_DESIRED_FLAG 1 #endif +/** + * @def OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE + * + * Enables support for sending DNS Queries over TCP. + * + */ +#ifndef OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE +#define OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE 0 +#endif + +/** + * @def OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_QUERY_MAX_SIZE + * + * Specifies size of receive and transmit buffers of TCP sockets for DNS query over TCP. + * + */ +#ifndef OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_QUERY_MAX_SIZE +#define OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_QUERY_MAX_SIZE 1024 +#endif + #endif // CONFIG_DNS_CLIENT_H_ diff --git a/src/core/net/dns_client.cpp b/src/core/net/dns_client.cpp index ffa9c37e4..e25732ed5 100644 --- a/src/core/net/dns_client.cpp +++ b/src/core/net/dns_client.cpp @@ -49,6 +49,11 @@ namespace ot { namespace Dns { +#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE +using ot::Encoding::BigEndian::ReadUint16; +using ot::Encoding::BigEndian::WriteUint16; +#endif + RegisterLogModule("DnsClient"); //--------------------------------------------------------------------------------------------------------------------- @@ -68,6 +73,7 @@ Client::QueryConfig::QueryConfig(InitMode aMode) #if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE SetNat64Mode(kDefaultNat64Allowed ? kNat64Allow : kNat64Disallow); #endif + SetTransportProto(kDnsTransportUdp); } void Client::QueryConfig::SetFrom(const QueryConfig &aConfig, const QueryConfig &aDefaultConfig) @@ -109,6 +115,10 @@ void Client::QueryConfig::SetFrom(const QueryConfig &aConfig, const QueryConfig SetNat64Mode(aDefaultConfig.GetNat64Mode()); } #endif + if (GetTransportProto() == kDnsTransportUnspecified) + { + SetTransportProto(aDefaultConfig.GetTransportProto()); + } } //--------------------------------------------------------------------------------------------------------------------- @@ -540,6 +550,9 @@ const uint16_t *Client::kQuestionRecordTypes[] = { Client::Client(Instance &aInstance) : InstanceLocator(aInstance) , mSocket(aInstance) +#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE + , mTcpState(kTcpUninitialized) +#endif , mTimer(aInstance) , mDefaultConfig(QueryConfig::kInitFromDefaults) #if OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE @@ -580,8 +593,39 @@ void Client::Stop(void) } IgnoreError(mSocket.Close()); +#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE + if (mTcpState != kTcpUninitialized) + { + IgnoreError(mEndpoint.Deinitialize()); + } +#endif } +#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE +Error Client::InitTcpSocket(void) +{ + Error error; + otTcpEndpointInitializeArgs endpointArgs; + + memset(&endpointArgs, 0x00, sizeof(endpointArgs)); + endpointArgs.mSendDoneCallback = HandleTcpSendDoneCallback; + endpointArgs.mEstablishedCallback = HandleTcpEstablishedCallback; + endpointArgs.mReceiveAvailableCallback = HandleTcpReceiveAvailableCallback; + endpointArgs.mDisconnectedCallback = HandleTcpDisconnectedCallback; + endpointArgs.mContext = this; + endpointArgs.mReceiveBuffer = mReceiveBufferBytes; + endpointArgs.mReceiveBufferSize = sizeof(mReceiveBufferBytes); + + mSendLink.mNext = nullptr; + mSendLink.mData = mSendBufferBytes; + mSendLink.mLength = 0; + + SuccessOrExit(error = mEndpoint.Initialize(Get(), endpointArgs)); +exit: + return error; +} +#endif + void Client::SetDefaultConfig(const QueryConfig &aQueryConfig) { QueryConfig startingDefault(QueryConfig::kInitFromDefaults); @@ -727,8 +771,10 @@ Error Client::StartQuery(QueryInfo &aInfo, SuccessOrExit(error = AllocateQuery(aInfo, aLabel, aName, query)); mQueries.Enqueue(*query); - - SendQuery(*query, aInfo, /* aUpdateTimer */ true); + if ((error = SendQuery(*query, aInfo, /* aUpdateTimer */ true)) != kErrorNone) + { + FreeQuery(*query); + } exit: return error; @@ -761,7 +807,7 @@ exit: void Client::FreeQuery(Query &aQuery) { mQueries.DequeueAndFree(aQuery); } -void Client::SendQuery(Query &aQuery, QueryInfo &aInfo, bool aUpdateTimer) +Error Client::SendQuery(Query &aQuery, QueryInfo &aInfo, bool aUpdateTimer) { // This method prepares and sends a query message represented by // `aQuery` and `aInfo`. This method updates `aInfo` (e.g., sets @@ -774,6 +820,7 @@ void Client::SendQuery(Query &aQuery, QueryInfo &aInfo, bool aUpdateTimer) Message *message = nullptr; Header header; Ip6::MessageInfo messageInfo; + uint16_t length = 0; aInfo.mTransmissionCount++; aInfo.mRetransmissionTime = TimerMilli::GetNow() + aInfo.mConfig.GetResponseTimeout(); @@ -815,20 +862,76 @@ void Client::SendQuery(Query &aQuery, QueryInfo &aInfo, bool aUpdateTimer) SuccessOrExit(error = message->Append(Question(kQuestionRecordTypes[aInfo.mQueryType][num]))); } - messageInfo.SetPeerAddr(aInfo.mConfig.GetServerSockAddr().GetAddress()); - messageInfo.SetPeerPort(aInfo.mConfig.GetServerSockAddr().GetPort()); + length = message->GetLength() - message->GetOffset(); - SuccessOrExit(error = mSocket.SendTo(*message, messageInfo)); + if (aInfo.mConfig.GetTransportProto() == QueryConfig::kDnsTransportTcp) +#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE + { + // Check if query will fit into tcp buffer if not return error. + VerifyOrExit(length + sizeof(uint16_t) + mSendLink.mLength <= + OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_QUERY_MAX_SIZE, + error = kErrorNoBufs); + + // In case of initialized connection check if connected peer and new query have the same address. + if (mTcpState != kTcpUninitialized) + { + VerifyOrExit(mEndpoint.GetPeerAddress() == AsCoreType(&aInfo.mConfig.mServerSockAddr), + error = kErrorFailed); + } + + switch (mTcpState) + { + case kTcpUninitialized: + SuccessOrExit(error = InitTcpSocket()); + SuccessOrExit( + error = mEndpoint.Connect(AsCoreType(&aInfo.mConfig.mServerSockAddr), OT_TCP_CONNECT_NO_FAST_OPEN)); + mTcpState = kTcpConecting; + PrepareTcpMessage(*message); + break; + case kTcpConnectedIdle: + PrepareTcpMessage(*message); + SuccessOrExit(error = mEndpoint.SendByReference(mSendLink, /* aFlags */ 0)); + mTcpState = kTcpConnectedSending; + break; + case kTcpConecting: + PrepareTcpMessage(*message); + break; + case kTcpConnectedSending: + WriteUint16(length, mSendBufferBytes + mSendLink.mLength); + SuccessOrAssert(error = message->Read(message->GetOffset(), + (mSendBufferBytes + sizeof(uint16_t) + mSendLink.mLength), length)); + IgnoreError(mEndpoint.SendByExtension(length + sizeof(uint16_t), /* aFlags */ 0)); + break; + } + message->Free(); + message = nullptr; + } +#else + { + error = kErrorInvalidArgs; + LogWarn("DNS query over TCP not supported."); + ExitNow(); + } +#endif + else + { + VerifyOrExit(length <= kUdpQueryMaxSize, error = kErrorInvalidArgs); + messageInfo.SetPeerAddr(aInfo.mConfig.GetServerSockAddr().GetAddress()); + messageInfo.SetPeerPort(aInfo.mConfig.GetServerSockAddr().GetPort()); + SuccessOrExit(error = mSocket.SendTo(*message, messageInfo)); + } exit: + FreeMessageOnError(message, error); - - UpdateQuery(aQuery, aInfo); - if (aUpdateTimer) { mTimer.FireAtIfEarlier(aInfo.mRetransmissionTime); } + + UpdateQuery(aQuery, aInfo); + + return error; } Error Client::AppendNameFromQuery(const Query &aQuery, Message &aMessage) @@ -1043,7 +1146,7 @@ Error Client::ParseResponse(Response &aResponse, QueryType &aType, Error &aRespo info.mMessageId = 0; info.mTransmissionCount = 0; - SendQuery(*aResponse.mQuery, info, /* aUpdateTimer */ true); + IgnoreReturnValue(SendQuery(*aResponse.mQuery, info, /* aUpdateTimer */ true)); error = kErrorPending; } @@ -1064,6 +1167,9 @@ void Client::HandleTimer(void) TimeMilli now = TimerMilli::GetNow(); TimeMilli nextTime = now.GetDistantFuture(); QueryInfo info; +#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE + bool hasTcpQuery = false; +#endif for (Query &query : mQueries) { @@ -1077,18 +1183,223 @@ void Client::HandleTimer(void) continue; } - SendQuery(query, info, /* aUpdateTimer */ false); + IgnoreReturnValue(SendQuery(query, info, /* aUpdateTimer */ false)); } nextTime = Min(nextTime, info.mRetransmissionTime); + +#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE + if (info.mConfig.GetTransportProto() == QueryConfig::kDnsTransportTcp) + { + hasTcpQuery = true; + } +#endif } if (nextTime < now.GetDistantFuture()) { mTimer.FireAt(nextTime); } + +#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE + if (!hasTcpQuery && mTcpState != kTcpUninitialized) + { + IgnoreError(mEndpoint.SendEndOfStream()); + } +#endif } +#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE +void Client::PrepareTcpMessage(Message &aMessage) +{ + uint16_t length = aMessage.GetLength() - aMessage.GetOffset(); + + // Prepending the DNS query with length of the packet according to RFC1035. + WriteUint16(length, mSendBufferBytes + mSendLink.mLength); + SuccessOrAssert( + aMessage.Read(aMessage.GetOffset(), (mSendBufferBytes + sizeof(uint16_t) + mSendLink.mLength), length)); + mSendLink.mLength += length + sizeof(uint16_t); +} + +void Client::HandleTcpSendDone(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData) +{ + OT_UNUSED_VARIABLE(aEndpoint); + OT_UNUSED_VARIABLE(aData); + OT_ASSERT(mTcpState == kTcpConnectedSending); + + mSendLink.mLength = 0; + mTcpState = kTcpConnectedIdle; +} + +void Client::HandleTcpSendDoneCallback(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData) +{ + static_cast(otTcpEndpointGetContext(aEndpoint))->HandleTcpSendDone(aEndpoint, aData); +} + +void Client::HandleTcpEstablished(otTcpEndpoint *aEndpoint) +{ + OT_UNUSED_VARIABLE(aEndpoint); + IgnoreError(mEndpoint.SendByReference(mSendLink, /* aFlags */ 0)); + mTcpState = kTcpConnectedSending; +} + +void Client::HandleTcpEstablishedCallback(otTcpEndpoint *aEndpoint) +{ + static_cast(otTcpEndpointGetContext(aEndpoint))->HandleTcpEstablished(aEndpoint); +} + +Error Client::ReadFromLinkBuffer(const otLinkedBuffer *&aLinkedBuffer, + size_t &aOffset, + Message &aMessage, + uint16_t aLength) +{ + // Read `aLength` bytes from `aLinkedBuffer` starting at `aOffset` + // and copy the content into `aMessage`. As we read we can move + // to the next `aLinkedBuffer` and update `aOffset`. + // Returns: + // - `kErrorNone` if `aLengh` bytes are successfully read and + // `aOffset` and `aLinkedBuffer` are updated. + // - `kErrorNotFound` is not enough bytes available to read + // from `aLinkedBuffer`. + // - `kErrorNotBufs` if cannot grow `aMessage` to append bytes. + + Error error = kErrorNone; + + while (aLength > 0) + { + uint16_t bytesToRead = aLength; + + VerifyOrExit(aLinkedBuffer != nullptr, error = kErrorNotFound); + + if (bytesToRead > aLinkedBuffer->mLength - aOffset) + { + bytesToRead = static_cast(aLinkedBuffer->mLength - aOffset); + } + + SuccessOrExit(error = aMessage.AppendBytes(&aLinkedBuffer->mData[aOffset], bytesToRead)); + + aLength -= bytesToRead; + aOffset += bytesToRead; + + if (aOffset == aLinkedBuffer->mLength) + { + aLinkedBuffer = aLinkedBuffer->mNext; + aOffset = 0; + } + } + +exit: + return error; +} + +void Client::HandleTcpReceiveAvailable(otTcpEndpoint *aEndpoint, + size_t aBytesAvailable, + bool aEndOfStream, + size_t aBytesRemaining) +{ + OT_UNUSED_VARIABLE(aEndpoint); + OT_UNUSED_VARIABLE(aBytesRemaining); + + Message *message = nullptr; + size_t totalRead = 0; + size_t offset = 0; + const otLinkedBuffer *data; + + if (aEndOfStream) + { + // Cleanup is done in disconnected callback. + IgnoreError(mEndpoint.SendEndOfStream()); + } + + SuccessOrExit(mEndpoint.ReceiveByReference(data)); + VerifyOrExit(data != nullptr); + + message = mSocket.NewMessage(0); + VerifyOrExit(message != nullptr); + + while (aBytesAvailable > totalRead) + { + uint16_t length; + + // Read the `length` field. + SuccessOrExit(ReadFromLinkBuffer(data, offset, *message, sizeof(uint16_t))); + + IgnoreError(message->Read(/* aOffset */ 0, length)); + length = HostSwap16(length); + + // Try to read `length` bytes. + IgnoreError(message->SetLength(0)); + SuccessOrExit(ReadFromLinkBuffer(data, offset, *message, length)); + + totalRead += length + sizeof(uint16_t); + + // Now process the read message as query response. + { + Response response; + QueryType type; + Error responseError; + + response.mInstance = &Get(); + response.mMessage = message; + + if (ParseResponse(response, type, responseError) == kErrorNone) + { + if (responseError == kErrorNone && length > OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_QUERY_MAX_SIZE) + { + LogWarn("Dns query over TCP wasn't received - message is too big."); + responseError = kErrorNoBufs; + } + FinalizeQuery(response, type, responseError); + } + } + + IgnoreError(message->SetLength(0)); + + // Loop again to see if we can read another response. + } + +exit: + // Inform `mEndPoint` about the total read and processed bytes + IgnoreError(mEndpoint.CommitReceive(totalRead, /* aFlags */ 0)); + FreeMessage(message); +} + +void Client::HandleTcpReceiveAvailableCallback(otTcpEndpoint *aEndpoint, + size_t aBytesAvailable, + bool aEndOfStream, + size_t aBytesRemaining) +{ + static_cast(otTcpEndpointGetContext(aEndpoint)) + ->HandleTcpReceiveAvailable(aEndpoint, aBytesAvailable, aEndOfStream, aBytesRemaining); +} + +void Client::HandleTcpDisconnected(otTcpEndpoint *aEndpoint, otTcpDisconnectedReason aReason) +{ + OT_UNUSED_VARIABLE(aEndpoint); + OT_UNUSED_VARIABLE(aReason); + QueryInfo info; + + IgnoreError(mEndpoint.Deinitialize()); + mTcpState = kTcpUninitialized; + + // Abort queries in case of connection failures + for (Query &query : mQueries) + { + info.ReadFrom(query); + if (info.mConfig.GetTransportProto() == QueryConfig::kDnsTransportTcp) + { + FinalizeQuery(query, kErrorAbort); + } + } +} + +void Client::HandleTcpDisconnectedCallback(otTcpEndpoint *aEndpoint, otTcpDisconnectedReason aReason) +{ + static_cast(otTcpEndpointGetContext(aEndpoint))->HandleTcpDisconnected(aEndpoint, aReason); +} + +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE + } // namespace Dns } // namespace ot diff --git a/src/core/net/dns_client.hpp b/src/core/net/dns_client.hpp index 729b6441f..e3147455b 100644 --- a/src/core/net/dns_client.hpp +++ b/src/core/net/dns_client.hpp @@ -61,6 +61,10 @@ #endif +#if !OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE +#error "OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE requires OPENTHREAD_CONFIG_TCP_ENABLE" +#endif + /** * This struct represents an opaque (and empty) type for a response to an address resolution DNS query. * @@ -136,11 +140,22 @@ public: enum Nat64Mode : uint8_t { kNat64Unspecified = OT_DNS_NAT64_UNSPECIFIED, ///< NAT64 mode is not specified. Use default NAT64 mode. - kNat64Allow = OT_DNS_NAT64_ALLOW, ///< Allow NAT64 address translation + kNat64Allow = OT_DNS_NAT64_ALLOW, ///< Allow NAT64 address translation. kNat64Disallow = OT_DNS_NAT64_DISALLOW, ///< Disallow NAT64 address translation. }; #endif + /** + * This enumeration type represents the DNS transport protocol selection. + * + */ + enum TransportProto : uint8_t + { + kDnsTransportUnspecified = OT_DNS_TRANSPORT_UNSPECIFIED, /// Dns transport is unspecified. + kDnsTransportUdp = OT_DNS_TRANSPORT_UDP, /// Dns query should be sent via UDP. + kDnsTransportTcp = OT_DNS_TRANSPORT_TCP, /// Dns query should be sent via TCP. + }; + /** * This is the default constructor for `QueryConfig` object. * @@ -192,6 +207,14 @@ public: Nat64Mode GetNat64Mode(void) const { return static_cast(mNat64Mode); } #endif + /** + * This method gets the transport protocol. + * + * @returns The transport protocol. + * + */ + TransportProto GetTransportProto(void) const { return static_cast(mTransportProto); }; + private: static constexpr uint32_t kDefaultResponseTimeout = OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_RESPONSE_TIMEOUT; static constexpr uint16_t kDefaultServerPort = OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_PORT; @@ -219,7 +242,10 @@ public: #if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE void SetNat64Mode(Nat64Mode aMode) { mNat64Mode = static_cast(aMode); } #endif - + void SetTransportProto(TransportProto aTransportProto) + { + mTransportProto = static_cast(aTransportProto); + } void SetFrom(const QueryConfig &aConfig, const QueryConfig &aDefaultConfig); }; @@ -699,6 +725,16 @@ private: #endif }; +#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE + enum TcpState : uint8_t + { + kTcpUninitialized = 0, + kTcpConecting, + kTcpConnectedIdle, + kTcpConnectedSending, + }; +#endif + union Callback { AddressCallback mAddressCallback; @@ -734,7 +770,7 @@ private: Error AllocateQuery(const QueryInfo &aInfo, const char *aLabel, const char *aName, Query *&aQuery); void FreeQuery(Query &aQuery); void UpdateQuery(Query &aQuery, const QueryInfo &aInfo) { aQuery.Write(0, aInfo); } - void SendQuery(Query &aQuery, QueryInfo &aInfo, bool aUpdateTimer); + Error SendQuery(Query &aQuery, QueryInfo &aInfo, bool aUpdateTimer); void FinalizeQuery(Query &aQuery, Error aError); void FinalizeQuery(Response &Response, QueryType aType, Error aError); static void GetCallback(const Query &aQuery, Callback &aCallback, void *&aContext); @@ -744,6 +780,31 @@ private: void ProcessResponse(const Message &aMessage); Error ParseResponse(Response &aResponse, QueryType &aType, Error &aResponseError); void HandleTimer(void); + +#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE + static void HandleTcpEstablishedCallback(otTcpEndpoint *aEndpoint); + static void HandleTcpSendDoneCallback(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData); + static void HandleTcpDisconnectedCallback(otTcpEndpoint *aEndpoint, otTcpDisconnectedReason aReason); + static void HandleTcpReceiveAvailableCallback(otTcpEndpoint *aEndpoint, + size_t aBytesAvailable, + bool aEndOfStream, + size_t aBytesRemaining); + + void HandleTcpEstablished(otTcpEndpoint *aEndpoint); + void HandleTcpSendDone(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData); + void HandleTcpDisconnected(otTcpEndpoint *aEndpoint, otTcpDisconnectedReason aReason); + void HandleTcpReceiveAvailable(otTcpEndpoint *aEndpoint, + size_t aBytesAvailable, + bool aEndOfStream, + size_t aBytesRemaining); + Error InitTcpSocket(void); + Error ReadFromLinkBuffer(const otLinkedBuffer *&aLinkedBuffer, + size_t &aOffset, + Message &aMessage, + uint16_t aLength); + void PrepareTcpMessage(Message &aMessage); +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE + #if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE Error CheckAddressResponse(Response &aResponse, Error aResponseError) const; #endif @@ -763,12 +824,25 @@ private: static const uint16_t kServiceQueryRecordTypes[]; #endif + static constexpr uint16_t kUdpQueryMaxSize = 512; + using RetryTimer = TimerMilliIn; Ip6::Udp::Socket mSocket; - QueryList mQueries; - RetryTimer mTimer; - QueryConfig mDefaultConfig; + +#if OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE + Ip6::Tcp::Endpoint mEndpoint; + + otLinkedBuffer mSendLink; + uint8_t mSendBufferBytes[OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_QUERY_MAX_SIZE]; + uint8_t mReceiveBufferBytes[OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_QUERY_MAX_SIZE]; + + TcpState mTcpState; +#endif + + QueryList mQueries; + RetryTimer mTimer; + QueryConfig mDefaultConfig; #if OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE bool mUserDidSetDefaultAddress; #endif diff --git a/tests/scripts/expect/cli-tcp-tls.exp b/tests/scripts/expect/cli-tcp-tls.exp index acc06258f..ccc44244f 100644 --- a/tests/scripts/expect/cli-tcp-tls.exp +++ b/tests/scripts/expect/cli-tcp-tls.exp @@ -30,7 +30,10 @@ source "tests/scripts/expect/_common.exp" source "tests/scripts/expect/_multinode.exp" -setup_two_nodes +spawn_node 2 "cli" +spawn_node 1 "cli" +setup_leader +setup_node 2 "rnd" "router" switch_node 1 send "tcp init tls\n" diff --git a/tests/scripts/expect/cli-tcp.exp b/tests/scripts/expect/cli-tcp.exp index 46fa325c1..6ae03855a 100755 --- a/tests/scripts/expect/cli-tcp.exp +++ b/tests/scripts/expect/cli-tcp.exp @@ -30,7 +30,10 @@ source "tests/scripts/expect/_common.exp" source "tests/scripts/expect/_multinode.exp" -setup_two_nodes +spawn_node 2 "cli" +spawn_node 1 "cli" +setup_leader +setup_node 2 "rnd" "router" switch_node 1 send "tcp init circular\n" diff --git a/tests/scripts/expect/tun-dns-over-tcp-client.exp b/tests/scripts/expect/tun-dns-over-tcp-client.exp new file mode 100755 index 000000000..3e74bfdb8 --- /dev/null +++ b/tests/scripts/expect/tun-dns-over-tcp-client.exp @@ -0,0 +1,46 @@ +#!/usr/bin/expect -f +# +# 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. +# + +source "tests/scripts/expect/_common.exp" +source "tests/scripts/expect/_multinode.exp" + +spawn_node 2 "cli" +spawn_node 1 +setup_leader +setup_node 2 "rnd" "router" + +switch_node 1 +set addr_1 [get_ipaddr mleid] + +switch_node 2 +send "dns resolve ipv6.google.com $addr_1 2000 6000 4 1 tcp\n" +expect "DNS response for ipv6.google.com" +expect_line "Done" + +dispose_all