From 519537dd9b750a0f541d774b70c4aa682de4d93f Mon Sep 17 00:00:00 2001 From: Sam Kumar Date: Mon, 21 Aug 2023 15:00:52 -0700 Subject: [PATCH] [tcplp] add support for TCP Fast Open (without cookie management) (#9165) This commit adds support for TCP Fast Open, without cookie management. To add support for this, I looked at the FreeBSD codebase and brought in some code from FreeBSD 12.0 that implements TCP Fast Open --- the version of FreeBSD that TCPlp is based on did not fully support TCP Fast Open. Normally, a part of TFO is cookie management --- the server generates a cookie and includes it in the initial handshake, and client is expected to present this cookie on future handshakes. This part is not yet implemented, and I changed the logic from FreeBSD to allow data to be exchanged in the TFO handshake even if the client does not present a cookie. If we implement this functionality for TFO later, it is probably worth departing from FreeBSD's data structures and policies for maintaining cookie state in favor of something that is simpler and more memory-efficient. --- include/openthread/instance.h | 2 +- include/openthread/tcp.h | 15 +- src/cli/README_TCP.md | 5 +- src/cli/cli_tcp.cpp | 133 ++- src/cli/cli_tcp.hpp | 4 +- src/core/net/tcp6.cpp | 68 +- tests/scripts/expect/cli-tcp-tfo-tls.exp | 112 ++ tests/scripts/expect/cli-tcp-tfo.exp | 103 ++ tests/scripts/expect/cli-tcp-tls.exp | 0 third_party/tcplp/CMakeLists.txt | 1 + third_party/tcplp/bsdtcp/tcp.h | 5 + third_party/tcplp/bsdtcp/tcp_const.h | 10 +- third_party/tcplp/bsdtcp/tcp_fastopen.c | 1295 ++++++++++++++++++++++ third_party/tcplp/bsdtcp/tcp_fastopen.h | 111 ++ third_party/tcplp/bsdtcp/tcp_input.c | 193 +++- third_party/tcplp/bsdtcp/tcp_output.c | 115 +- third_party/tcplp/bsdtcp/tcp_subr.c | 12 +- third_party/tcplp/bsdtcp/tcp_usrreq.c | 37 +- third_party/tcplp/bsdtcp/tcp_var.h | 24 +- third_party/tcplp/tcplp.h | 11 +- 20 files changed, 2163 insertions(+), 93 deletions(-) create mode 100755 tests/scripts/expect/cli-tcp-tfo-tls.exp create mode 100755 tests/scripts/expect/cli-tcp-tfo.exp mode change 100644 => 100755 tests/scripts/expect/cli-tcp-tls.exp create mode 100644 third_party/tcplp/bsdtcp/tcp_fastopen.c create mode 100644 third_party/tcplp/bsdtcp/tcp_fastopen.h diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 5fb07daba..337a8b46c 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 (350) +#define OPENTHREAD_API_VERSION (351) /** * @addtogroup api-instance diff --git a/include/openthread/tcp.h b/include/openthread/tcp.h index bbcd0ac07..caa0d4efd 100644 --- a/include/openthread/tcp.h +++ b/include/openthread/tcp.h @@ -225,7 +225,7 @@ typedef void (*otTcpDisconnected)(otTcpEndpoint *aEndpoint, otTcpDisconnectedRea * opaque in its declaration, is treated as struct tcpcb in the TCP * implementation. */ -#define OT_TCP_ENDPOINT_TCB_SIZE_BASE 368 +#define OT_TCP_ENDPOINT_TCB_SIZE_BASE 392 #define OT_TCP_ENDPOINT_TCB_NUM_PTR 36 /** @@ -401,11 +401,16 @@ enum /** * Records the remote host and port for this connection. * - * Caller must wait for `otTcpEstablished` callback indicating that TCP - * connection establishment handshake is done before it can start sending data - * e.g., calling `otTcpSendByReference()`. + * TCP Fast Open must be enabled or disabled using @p aFlags. If it is + * disabled, then the TCP connection establishment handshake is initiated + * immediately. If it is enabled, then this function merely records the + * the remote host and port, and the TCP connection establishment handshake + * only happens on the first call to `otTcpSendByReference()`. * - * The TCP Fast Open is not yet supported and @p aFlags is ignored. + * If TCP Fast Open is disabled, then the caller must wait for the + * `otTcpEstablished` callback indicating that TCP connection establishment + * handshake is done before it can start sending data e.g., by calling + * `otTcpSendByReference()`. * * @param[in] aEndpoint A pointer to the TCP endpoint structure to connect. * @param[in] aSockName The IP address and port of the host to which to connect. diff --git a/src/cli/README_TCP.md b/src/cli/README_TCP.md index 6281fd88b..8e3414531 100644 --- a/src/cli/README_TCP.md +++ b/src/cli/README_TCP.md @@ -58,7 +58,7 @@ For a more in-depth example, see [this video](https://youtu.be/ppZ784YUKlI). - [init](#init-size) - [deinit](#deinit) - [bind](#bind-ip-port) -- [connect](#connect-ip-port) +- [connect](#connect-ip-port-fastopen) - [send](#send-message) - [benchmark](#benchmark-run-size) - [sendend](#sendend) @@ -118,7 +118,7 @@ Associates a name (i.e. IPv6 address and port) to the example TCP endpoint. Done ``` -### connect \ \ +### connect \ \ [\] Establishes a connection with the specified peer. @@ -126,6 +126,7 @@ If the connection establishment is successful, the resulting TCP connection is a - ip: the peer's IP address. - port: the peer's TCP port. +- fastopen: if "fast", TCP Fast Open is enabled for this connection; if "slow", it is not. Defaults to "slow". ```bash > tcp connect fe80:0:0:0:a8df:580a:860:ffa4 30000 diff --git a/src/cli/cli_tcp.cpp b/src/cli/cli_tcp.cpp index f74f43331..67534f0b9 100644 --- a/src/cli/cli_tcp.cpp +++ b/src/cli/cli_tcp.cpp @@ -64,6 +64,7 @@ TcpExample::TcpExample(otInstance *aInstance, OutputImplementer &aOutputImplemen : Output(aInstance, aOutputImplementer) , mInitialized(false) , mEndpointConnected(false) + , mEndpointConnectedFastOpen(false) , mSendBusy(false) , mUseCircularSendBuffer(true) , mUseTls(false) @@ -294,6 +295,7 @@ template <> otError TcpExample::Process(Arg aArgs[]) otError error; otSockAddr sockaddr; bool nat64SynthesizedAddress; + uint32_t flags; VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE); @@ -306,7 +308,26 @@ template <> otError TcpExample::Process(Arg aArgs[]) } SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort)); - VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + if (aArgs[2].IsEmpty()) + { + flags = OT_TCP_CONNECT_NO_FAST_OPEN; + } + else + { + if (aArgs[2] == "slow") + { + flags = OT_TCP_CONNECT_NO_FAST_OPEN; + } + else if (aArgs[2] == "fast") + { + flags = 0; + } + else + { + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + VerifyOrExit(aArgs[3].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + } #if OPENTHREAD_CONFIG_TLS_ENABLE if (mUseTls) @@ -320,8 +341,17 @@ template <> otError TcpExample::Process(Arg aArgs[]) } #endif // OPENTHREAD_CONFIG_TLS_ENABLE - SuccessOrExit(error = otTcpConnect(&mEndpoint, &sockaddr, OT_TCP_CONNECT_NO_FAST_OPEN)); - mEndpointConnected = true; + SuccessOrExit(error = otTcpConnect(&mEndpoint, &sockaddr, flags)); + mEndpointConnected = true; + mEndpointConnectedFastOpen = ((flags & OT_TCP_CONNECT_NO_FAST_OPEN) == 0); + +#if OPENTHREAD_CONFIG_TLS_ENABLE + if (mUseTls && mEndpointConnectedFastOpen) + { + PrepareTlsHandshake(); + ContinueTlsHandshake(); + } +#endif exit: return error; @@ -472,7 +502,8 @@ template <> otError TcpExample::Process(Arg aArgs[]) VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE); SuccessOrExit(error = otTcpAbort(&mEndpoint)); - mEndpointConnected = false; + mEndpointConnected = false; + mEndpointConnectedFastOpen = false; exit: return error; @@ -591,24 +622,10 @@ void TcpExample::HandleTcpEstablished(otTcpEndpoint *aEndpoint) OT_UNUSED_VARIABLE(aEndpoint); OutputLine("TCP: Connection established"); #if OPENTHREAD_CONFIG_TLS_ENABLE - if (mUseTls) + if (mUseTls && !mEndpointConnectedFastOpen) { - int rv; - rv = mbedtls_ssl_set_hostname(&mSslContext, "localhost"); - if (rv != 0) - { - OutputLine("mbedtls_ssl_set_hostname returned %d", rv); - } - rv = mbedtls_ssl_set_hs_ecjpake_password( - &mSslContext, reinterpret_cast(sEcjpakePassword), sEcjpakePasswordLength); - if (rv != 0) - { - OutputLine("mbedtls_ssl_set_hs_ecjpake_password returned %d", rv); - } - mbedtls_ssl_set_bio(&mSslContext, &mEndpointAndCircularSendBuffer, otTcpMbedTlsSslSendCallback, - otTcpMbedTlsSslRecvCallback, nullptr); - mTlsHandshakeComplete = false; - ContinueTLSHandshake(); + PrepareTlsHandshake(); + ContinueTlsHandshake(); } #endif // OPENTHREAD_CONFIG_TLS_ENABLE } @@ -660,7 +677,7 @@ void TcpExample::HandleTcpForwardProgress(otTcpEndpoint *aEndpoint, size_t aInSe #if OPENTHREAD_CONFIG_TLS_ENABLE if (mUseTls) { - ContinueTLSHandshake(); + ContinueTlsHandshake(); } #endif @@ -688,8 +705,22 @@ void TcpExample::HandleTcpReceiveAvailable(otTcpEndpoint *aEndpoint, OT_UNUSED_VARIABLE(aBytesRemaining); OT_ASSERT(aEndpoint == &mEndpoint); + /* If we get data before the handshake completes, then this is a TFO connection. */ + if (!mEndpointConnected) + { + mEndpointConnected = true; + mEndpointConnectedFastOpen = true; + #if OPENTHREAD_CONFIG_TLS_ENABLE - if (mUseTls && ContinueTLSHandshake()) + if (mUseTls) + { + PrepareTlsHandshake(); + } +#endif + } + +#if OPENTHREAD_CONFIG_TLS_ENABLE + if (mUseTls && ContinueTlsHandshake()) { return; } @@ -773,8 +804,9 @@ void TcpExample::HandleTcpDisconnected(otTcpEndpoint *aEndpoint, otTcpDisconnect // We set this to false even for the TIME-WAIT state, so that we can reuse // the active socket if an incoming connection comes in instead of waiting // for the 2MSL timeout. - mEndpointConnected = false; - mSendBusy = false; + mEndpointConnected = false; + mEndpointConnectedFastOpen = false; + mSendBusy = false; // Mark the benchmark as inactive if the connection was disconnected. mBenchmarkBytesTotal = 0; @@ -803,20 +835,11 @@ otTcpIncomingConnectionAction TcpExample::HandleTcpAcceptReady(otTcpListener *aAcceptInto = &mEndpoint; action = OT_TCP_INCOMING_CONNECTION_ACTION_ACCEPT; -exit: - return action; -} - -void TcpExample::HandleTcpAcceptDone(otTcpListener *aListener, otTcpEndpoint *aEndpoint, const otSockAddr *aPeer) -{ - OT_UNUSED_VARIABLE(aListener); - OT_UNUSED_VARIABLE(aEndpoint); - - mEndpointConnected = true; - OutputFormat("Accepted connection from "); - OutputSockAddrLine(*aPeer); - #if OPENTHREAD_CONFIG_TLS_ENABLE + /* + * Natural to wait until the AcceptDone callback but with TFO we could get data before that + * so it doesn't make sense to wait until then. + */ if (mUseTls) { int rv; @@ -835,6 +858,19 @@ void TcpExample::HandleTcpAcceptDone(otTcpListener *aListener, otTcpEndpoint *aE } } #endif // OPENTHREAD_CONFIG_TLS_ENABLE + +exit: + return action; +} + +void TcpExample::HandleTcpAcceptDone(otTcpListener *aListener, otTcpEndpoint *aEndpoint, const otSockAddr *aPeer) +{ + OT_UNUSED_VARIABLE(aListener); + OT_UNUSED_VARIABLE(aEndpoint); + + mEndpointConnected = true; + OutputFormat("Accepted connection from "); + OutputSockAddrLine(*aPeer); } otError TcpExample::ContinueBenchmarkCircularSend(void) @@ -908,7 +944,26 @@ void TcpExample::CompleteBenchmark(void) } #if OPENTHREAD_CONFIG_TLS_ENABLE -bool TcpExample::ContinueTLSHandshake(void) +void TcpExample::PrepareTlsHandshake(void) +{ + int rv; + rv = mbedtls_ssl_set_hostname(&mSslContext, "localhost"); + if (rv != 0) + { + OutputLine("mbedtls_ssl_set_hostname returned %d", rv); + } + rv = mbedtls_ssl_set_hs_ecjpake_password(&mSslContext, reinterpret_cast(sEcjpakePassword), + sEcjpakePasswordLength); + if (rv != 0) + { + OutputLine("mbedtls_ssl_set_hs_ecjpake_password returned %d", rv); + } + mbedtls_ssl_set_bio(&mSslContext, &mEndpointAndCircularSendBuffer, otTcpMbedTlsSslSendCallback, + otTcpMbedTlsSslRecvCallback, nullptr); + mTlsHandshakeComplete = false; +} + +bool TcpExample::ContinueTlsHandshake(void) { bool wasNotAlreadyDone = false; int rv; diff --git a/src/cli/cli_tcp.hpp b/src/cli/cli_tcp.hpp index 24bb75fd1..50e728025 100644 --- a/src/cli/cli_tcp.hpp +++ b/src/cli/cli_tcp.hpp @@ -96,7 +96,8 @@ private: void CompleteBenchmark(void); #if OPENTHREAD_CONFIG_TLS_ENABLE - bool ContinueTLSHandshake(void); + void PrepareTlsHandshake(void); + bool ContinueTlsHandshake(void); #endif static void HandleTcpEstablishedCallback(otTcpEndpoint *aEndpoint); @@ -138,6 +139,7 @@ private: bool mInitialized; bool mEndpointConnected; + bool mEndpointConnectedFastOpen; bool mSendBusy; bool mUseCircularSendBuffer; bool mUseTls; diff --git a/src/core/net/tcp6.cpp b/src/core/net/tcp6.cpp index 7c9b1d3cc..c11e52e1c 100644 --- a/src/core/net/tcp6.cpp +++ b/src/core/net/tcp6.cpp @@ -168,17 +168,28 @@ exit: Error Tcp::Endpoint::Connect(const SockAddr &aSockName, uint32_t aFlags) { - Error error = kErrorNone; - struct tcpcb &tp = GetTcb(); - struct sockaddr_in6 sin6p; - - OT_UNUSED_VARIABLE(aFlags); + Error error = kErrorNone; + struct tcpcb &tp = GetTcb(); VerifyOrExit(tp.t_state == TCP6S_CLOSED, error = kErrorInvalidState); - memcpy(&sin6p.sin6_addr, &aSockName.mAddress, sizeof(sin6p.sin6_addr)); - sin6p.sin6_port = HostSwap16(aSockName.mPort); - error = BsdErrorToOtError(tcp6_usr_connect(&tp, &sin6p)); + if (aFlags & OT_TCP_CONNECT_NO_FAST_OPEN) + { + struct sockaddr_in6 sin6p; + + tp.t_flags &= ~TF_FASTOPEN; + memcpy(&sin6p.sin6_addr, &aSockName.mAddress, sizeof(sin6p.sin6_addr)); + sin6p.sin6_port = HostSwap16(aSockName.mPort); + error = BsdErrorToOtError(tcp6_usr_connect(&tp, &sin6p)); + } + else + { + tp.t_flags |= TF_FASTOPEN; + + /* Stash the destination address in tp. */ + memcpy(&tp.faddr, &aSockName.mAddress, sizeof(tp.faddr)); + tp.fport = HostSwap16(aSockName.mPort); + } exit: return error; @@ -192,7 +203,17 @@ Error Tcp::Endpoint::SendByReference(otLinkedBuffer &aBuffer, uint32_t aFlags) size_t backlogBefore = GetBacklogBytes(); size_t sent = aBuffer.mLength; - SuccessOrExit(error = BsdErrorToOtError(tcp_usr_send(&tp, (aFlags & OT_TCP_SEND_MORE_TO_COME) != 0, &aBuffer, 0))); + struct sockaddr_in6 sin6p; + struct sockaddr_in6 *name = nullptr; + + if (IS_FASTOPEN(tp.t_flags)) + { + memcpy(&sin6p.sin6_addr, &tp.faddr, sizeof(sin6p.sin6_addr)); + sin6p.sin6_port = tp.fport; + name = &sin6p; + } + SuccessOrExit( + error = BsdErrorToOtError(tcp_usr_send(&tp, (aFlags & OT_TCP_SEND_MORE_TO_COME) != 0, &aBuffer, 0, name))); PostCallbacksAfterSend(sent, backlogBefore); @@ -208,9 +229,19 @@ Error Tcp::Endpoint::SendByExtension(size_t aNumBytes, uint32_t aFlags) size_t backlogBefore = GetBacklogBytes(); int bsdError; + struct sockaddr_in6 sin6p; + struct sockaddr_in6 *name = nullptr; + VerifyOrExit(lbuf_head(&tp.sendbuf) != nullptr, error = kErrorInvalidState); - bsdError = tcp_usr_send(&tp, moreToCome ? 1 : 0, nullptr, aNumBytes); + if (IS_FASTOPEN(tp.t_flags)) + { + memcpy(&sin6p.sin6_addr, &tp.faddr, sizeof(sin6p.sin6_addr)); + sin6p.sin6_port = tp.fport; + name = &sin6p; + } + + bsdError = tcp_usr_send(&tp, moreToCome ? 1 : 0, nullptr, aNumBytes, name); SuccessOrExit(error = BsdErrorToOtError(bsdError)); PostCallbacksAfterSend(aNumBytes, backlogBefore); @@ -614,6 +645,9 @@ Error Tcp::HandleMessage(ot::Ip6::Header &aIp6Header, Message &aMessage, Message Listener *listener; Listener *listenerPrev; + struct tcplp_signals sig; + int nextAction; + VerifyOrExit(length == aMessage.GetLength() - aMessage.GetOffset(), error = kErrorParse); VerifyOrExit(length >= sizeof(Tcp::Header), error = kErrorParse); SuccessOrExit(error = aMessage.Read(aMessage.GetOffset() + offsetof(struct tcphdr, th_off_x2), headerSize)); @@ -634,9 +668,7 @@ Error Tcp::HandleMessage(ot::Ip6::Header &aIp6Header, Message &aMessage, Message endpoint = mEndpoints.FindMatching(aMessageInfo, endpointPrev); if (endpoint != nullptr) { - struct tcplp_signals sig; - int nextAction; - struct tcpcb *tp = &endpoint->GetTcb(); + struct tcpcb *tp = &endpoint->GetTcb(); otLinkedBuffer *priorHead = lbuf_head(&tp->sendbuf); size_t priorBacklog = endpoint->GetSendBufferBytes() - endpoint->GetInFlightBytes(); @@ -656,7 +688,13 @@ Error Tcp::HandleMessage(ot::Ip6::Header &aIp6Header, Message &aMessage, Message { struct tcpcb_listen *tpl = &listener->GetTcbListen(); - tcp_input(ip6Header, tcpHeader, &aMessage, nullptr, tpl, nullptr); + memset(&sig, 0x00, sizeof(sig)); + nextAction = tcp_input(ip6Header, tcpHeader, &aMessage, nullptr, tpl, &sig); + OT_ASSERT(nextAction != RELOOKUP_REQUIRED); + if (sig.accepted_connection != nullptr) + { + ProcessSignals(Tcp::Endpoint::FromTcb(*sig.accepted_connection), nullptr, 0, sig); + } ExitNow(); } @@ -683,6 +721,8 @@ void Tcp::ProcessSignals(Endpoint &aEndpoint, { otLinkedBuffer *curr = aPriorHead; + OT_ASSERT(curr != nullptr || aSignals.links_popped == 0); + for (uint32_t i = 0; i != aSignals.links_popped; i++) { otLinkedBuffer *next = curr->mNext; diff --git a/tests/scripts/expect/cli-tcp-tfo-tls.exp b/tests/scripts/expect/cli-tcp-tfo-tls.exp new file mode 100755 index 000000000..cc6170930 --- /dev/null +++ b/tests/scripts/expect/cli-tcp-tfo-tls.exp @@ -0,0 +1,112 @@ +#!/usr/bin/expect -f +# +# Copyright (c) 2023, 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 "cli" +setup_leader +setup_node 2 "rnd" "router" + +switch_node 1 +send "tcp init tls\n" +expect_line "Done" + +switch_node 2 +send "tcp init tls\n" +expect_line "Done" +set addr_2 [get_ipaddr mleid] +send "tcp listen :: 30000\n" +expect_line "Done" +send "tcp stoplistening\n" +expect_line "Done" + +switch_node 1 +send "tcp connect $addr_2 30000\n" +expect_line "Done" +expect "TCP: Connection refused" + +switch_node 2 +send "tcp listen :: 30000\n" +expect_line "Done" + +switch_node 1 +set addr_1 [get_ipaddr mleid] +send "tcp bind $addr_1 25000\n" +expect_line "Done" +send "tcp connect $addr_2 30000 fast\n" +expect_line "Done" +expect "TCP: Connection established" +expect "TLS Handshake Complete" + +switch_node 2 +expect "Accepted connection from \\\[$addr_1\\\]:25000" +expect "TCP: Connection established" +expect "TLS Handshake Complete" + +switch_node 1 +send "tcp send hello\n" +expect_line "Done" + +switch_node 2 +expect "TLS: Received 5 bytes: hello" +expect "(TCP: Received 26 bytes)" +send "tcp send world\n" +expect_line "Done" + +switch_node 1 +expect "TLS: Received 5 bytes: world" +expect "(TCP: Received 26 bytes)" +send "tcp sendend\n" +expect_line "Done" +send "tcp send more\n" +expect_line "Error 1: Failed" + +switch_node 2 +expect "TCP: Reached end of stream" +send "tcp send goodbye\n" +expect_line "Done" + +switch_node 1 +expect "TLS: Received 7 bytes: goodbye" +expect "(TCP: Received 28 bytes)" + +switch_node 2 +send "tcp sendend\n" +expect_line "Done" +expect "TCP: Disconnected" + +switch_node 1 +expect "TCP: Reached end of stream" +expect "TCP: Entered TIME-WAIT state" +set timeout 130 +expect "TCP: Disconnected" + +dispose_all diff --git a/tests/scripts/expect/cli-tcp-tfo.exp b/tests/scripts/expect/cli-tcp-tfo.exp new file mode 100755 index 000000000..b2d2ed918 --- /dev/null +++ b/tests/scripts/expect/cli-tcp-tfo.exp @@ -0,0 +1,103 @@ +#!/usr/bin/expect -f +# +# Copyright (c) 2023, 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 "cli" +setup_leader +setup_node 2 "rnd" "router" + +switch_node 1 +send "tcp init circular\n" +expect_line "Done" + +switch_node 2 +send "tcp init linked\n" +expect_line "Done" +set addr_2 [get_ipaddr mleid] +send "tcp listen :: 30000\n" +expect_line "Done" +send "tcp stoplistening\n" +expect_line "Done" + +switch_node 1 +send "tcp connect $addr_2 30000\n" +expect_line "Done" +expect "TCP: Connection refused" + +switch_node 2 +send "tcp listen :: 30000\n" +expect_line "Done" + +switch_node 1 +set addr_1 [get_ipaddr mleid] +send "tcp bind $addr_1 25000\n" +expect_line "Done" +send "tcp connect $addr_2 30000 fast\n" +expect_line "Done" +send "tcp send hello\n" +expect_line "Done" +expect "TCP: Connection established" + +switch_node 2 +expect "TCP: Received 5 bytes: hello" +expect "Accepted connection from \\\[$addr_1\\\]:25000" +expect "TCP: Connection established" +send "tcp send world\n" +expect_line "Done" + +switch_node 1 +expect "TCP: Received 5 bytes: world" +send "tcp sendend\n" +expect_line "Done" +send "tcp send more\n" +expect_line "Error 1: Failed" + +switch_node 2 +expect "TCP: Reached end of stream" +send "tcp send goodbye\n" +expect_line "Done" + +switch_node 1 +expect "TCP: Received 7 bytes: goodbye" + +switch_node 2 +send "tcp sendend\n" +expect_line "Done" +expect "TCP: Disconnected" + +switch_node 1 +expect "TCP: Reached end of stream" +expect "TCP: Entered TIME-WAIT state" +set timeout 130 +expect "TCP: Disconnected" + +dispose_all diff --git a/tests/scripts/expect/cli-tcp-tls.exp b/tests/scripts/expect/cli-tcp-tls.exp old mode 100644 new mode 100755 diff --git a/third_party/tcplp/CMakeLists.txt b/third_party/tcplp/CMakeLists.txt index 9ec23b8d7..ec6ec9573 100644 --- a/third_party/tcplp/CMakeLists.txt +++ b/third_party/tcplp/CMakeLists.txt @@ -30,6 +30,7 @@ project("TCPlp" C) set(src_tcplp bsdtcp/cc/cc_newreno.c + bsdtcp/tcp_fastopen.c bsdtcp/tcp_input.c bsdtcp/tcp_output.c bsdtcp/tcp_reass.c diff --git a/third_party/tcplp/bsdtcp/tcp.h b/third_party/tcplp/bsdtcp/tcp.h index 1faf353d4..59e56fd75 100644 --- a/third_party/tcplp/bsdtcp/tcp.h +++ b/third_party/tcplp/bsdtcp/tcp.h @@ -118,6 +118,8 @@ struct tcphdr { #define TCPOLEN_TSTAMP_APPA (TCPOLEN_TIMESTAMP+2) /* appendix A */ #define TCPOPT_SIGNATURE 19 /* Keyed MD5: RFC 2385 */ #define TCPOLEN_SIGNATURE 18 +#define TCPOPT_FAST_OPEN 34 +#define TCPOLEN_FAST_OPEN_EMPTY 2 /* Miscellaneous constants */ #define MAX_SACK_BLKS 6 /* Max # SACK blocks stored at receiver side */ @@ -167,6 +169,9 @@ struct tcphdr { #define TCP_MAXHLEN (0xf<<2) /* max length of header in bytes */ #define TCP_MAXOLEN (TCP_MAXHLEN - sizeof(struct tcphdr)) /* max space left for options */ +#define TCP_FASTOPEN_MIN_COOKIE_LEN 4 /* Per RFC7413 */ +#define TCP_FASTOPEN_MAX_COOKIE_LEN 16 /* Per RFC7413 */ +#define TCP_FASTOPEN_PSK_LEN 16 /* Same as TCP_FASTOPEN_KEY_LEN */ /* * User-settable options (used with setsockopt). These are discrete diff --git a/third_party/tcplp/bsdtcp/tcp_const.h b/third_party/tcplp/bsdtcp/tcp_const.h index 851643891..c0ef6de4d 100644 --- a/third_party/tcplp/bsdtcp/tcp_const.h +++ b/third_party/tcplp/bsdtcp/tcp_const.h @@ -95,9 +95,17 @@ enum tcp_subr_consts { enum tcp_timer_consts { // V_tcp_v6pmtud_blackhole_mss = FRAMECAP_6LOWPAN - sizeof(struct ip6_hdr) - sizeof(struct tcphdr), // Doesn't matter unless blackhole_detect is 1. tcp_rexmit_drop_options = 0, // drop options after a few retransmits - always_keepalive = 1, + always_keepalive = 1 }; +enum tcp_fastopen_consts { + V_tcp_fastopen_client_enable = 1, + V_tcp_fastopen_server_enable = 1, + V_tcp_fastopen_acceptany = 1, + V_tcp_fastopen_numkeys = 4 +}; +#define TCP_RFC7413 + /* * Force a time value to be in a certain range. */ diff --git a/third_party/tcplp/bsdtcp/tcp_fastopen.c b/third_party/tcplp/bsdtcp/tcp_fastopen.c new file mode 100644 index 000000000..9dc6c2cf3 --- /dev/null +++ b/third_party/tcplp/bsdtcp/tcp_fastopen.c @@ -0,0 +1,1295 @@ +/*- + * Copyright (c) 2015-2017 Patrick Kelsey + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + */ + +/* + * This is an implementation of TCP Fast Open (TFO) [RFC7413]. To include + * this code, add the following line to your kernel config: + * + * options TCP_RFC7413 + * + * + * The generated TFO cookies are the 64-bit output of + * SipHash24(key=<16-byte-key>, msg=). Multiple concurrent valid + * keys are supported so that time-based rolling cookie invalidation + * policies can be implemented in the system. The default number of + * concurrent keys is 2. This can be adjusted in the kernel config as + * follows: + * + * options TCP_RFC7413_MAX_KEYS= + * + * + * In addition to the facilities defined in RFC7413, this implementation + * supports a pre-shared key (PSK) mode of operation in which the TFO server + * requires the client to be in posession of a shared secret in order for + * the client to be able to successfully open TFO connections with the + * server. This is useful, for example, in environments where TFO servers + * are exposed to both internal and external clients and only wish to allow + * TFO connections from internal clients. + * + * In the PSK mode of operation, the server generates and sends TFO cookies + * to requesting clients as usual. However, when validating cookies + * received in TFO SYNs from clients, the server requires the + * client-supplied cookie to equal SipHash24(key=<16-byte-psk>, + * msg=). + * + * Multiple concurrent valid pre-shared keys are supported so that + * time-based rolling PSK invalidation policies can be implemented in the + * system. The default number of concurrent pre-shared keys is 2. This can + * be adjusted in the kernel config as follows: + * + * options TCP_RFC7413_MAX_PSKS= + * + * + * The following TFO-specific sysctls are defined: + * + * net.inet.tcp.fastopen.acceptany (RW, default 0) + * When non-zero, all client-supplied TFO cookies will be considered to + * be valid. + * + * net.inet.tcp.fastopen.autokey (RW, default 120) + * When this and net.inet.tcp.fastopen.server_enable are non-zero, a new + * key will be automatically generated after this many seconds. + * + * net.inet.tcp.fastopen.ccache_bucket_limit + * (RWTUN, default TCP_FASTOPEN_CCACHE_BUCKET_LIMIT_DEFAULT) + * The maximum number of entries in a client cookie cache bucket. + * + * net.inet.tcp.fastopen.ccache_buckets + * (RDTUN, default TCP_FASTOPEN_CCACHE_BUCKETS_DEFAULT) + * The number of client cookie cache buckets. + * + * net.inet.tcp.fastopen.ccache_list (RO) + * Print the client cookie cache. + * + * net.inet.tcp.fastopen.client_enable (RW, default 0) + * When zero, no new active (i.e., client) TFO connections can be + * created. On the transition from enabled to disabled, the client + * cookie cache is cleared and disabled. The transition from enabled to + * disabled does not affect any active TFO connections in progress; it + * only prevents new ones from being made. + * + * net.inet.tcp.fastopen.keylen (RD) + * The key length in bytes. + * + * net.inet.tcp.fastopen.maxkeys (RD) + * The maximum number of keys supported. + * + * net.inet.tcp.fastopen.maxpsks (RD) + * The maximum number of pre-shared keys supported. + * + * net.inet.tcp.fastopen.numkeys (RD) + * The current number of keys installed. + * + * net.inet.tcp.fastopen.numpsks (RD) + * The current number of pre-shared keys installed. + * + * net.inet.tcp.fastopen.path_disable_time + * (RW, default TCP_FASTOPEN_PATH_DISABLE_TIME_DEFAULT) + * When a failure occurs while trying to create a new active (i.e., + * client) TFO connection, new active connections on the same path, as + * determined by the tuple {client_ip, server_ip, server_port}, will be + * forced to be non-TFO for this many seconds. Note that the path + * disable mechanism relies on state stored in client cookie cache + * entries, so it is possible for the disable time for a given path to + * be reduced if the corresponding client cookie cache entry is reused + * due to resource pressure before the disable period has elapsed. + * + * net.inet.tcp.fastopen.psk_enable (RW, default 0) + * When non-zero, pre-shared key (PSK) mode is enabled for all TFO + * servers. On the transition from enabled to disabled, all installed + * pre-shared keys are removed. + * + * net.inet.tcp.fastopen.server_enable (RW, default 0) + * When zero, no new passive (i.e., server) TFO connections can be + * created. On the transition from enabled to disabled, all installed + * keys and pre-shared keys are removed. On the transition from + * disabled to enabled, if net.inet.tcp.fastopen.autokey is non-zero and + * there are no keys installed, a new key will be generated immediately. + * The transition from enabled to disabled does not affect any passive + * TFO connections in progress; it only prevents new ones from being + * made. + * + * net.inet.tcp.fastopen.setkey (WR) + * Install a new key by writing net.inet.tcp.fastopen.keylen bytes to + * this sysctl. + * + * net.inet.tcp.fastopen.setpsk (WR) + * Install a new pre-shared key by writing net.inet.tcp.fastopen.keylen + * bytes to this sysctl. + * + * In order for TFO connections to be created via a listen socket, that + * socket must have the TCP_FASTOPEN socket option set on it. This option + * can be set on the socket either before or after the listen() is invoked. + * Clearing this option on a listen socket after it has been set has no + * effect on existing TFO connections or TFO connections in progress; it + * only prevents new TFO connections from being made. + * + * For passively-created sockets, the TCP_FASTOPEN socket option can be + * queried to determine whether the connection was established using TFO. + * Note that connections that are established via a TFO SYN, but that fall + * back to using a non-TFO SYN|ACK will have the TCP_FASTOPEN socket option + * set. + * + * Per the RFC, this implementation limits the number of TFO connections + * that can be in the SYN_RECEIVED state on a per listen-socket basis. + * Whenever this limit is exceeded, requests for new TFO connections are + * serviced as non-TFO requests. Without such a limit, given a valid TFO + * cookie, an attacker could keep the listen queue in an overflow condition + * using a TFO SYN flood. This implementation sets the limit at half the + * configured listen backlog. + * + */ + +#if 0 +#include +__FBSDID("$FreeBSD$"); + +#include "opt_inet.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#endif + +#include "../tcplp.h" +#include "ip.h" +#include "tcp_const.h" +#include "tcp_var.h" +#include "tcp_fastopen.h" + +#if 0 + +#define TCP_FASTOPEN_KEY_LEN SIPHASH_KEY_LENGTH + +#if TCP_FASTOPEN_PSK_LEN != TCP_FASTOPEN_KEY_LEN +#error TCP_FASTOPEN_PSK_LEN must be equal to TCP_FASTOPEN_KEY_LEN +#endif + +/* + * Because a PSK-mode setsockopt() uses tcpcb.t_tfo_cookie.client to hold + * the PSK until the connect occurs. + */ +#if TCP_FASTOPEN_MAX_COOKIE_LEN < TCP_FASTOPEN_PSK_LEN +#error TCP_FASTOPEN_MAX_COOKIE_LEN must be >= TCP_FASTOPEN_PSK_LEN +#endif + +#define TCP_FASTOPEN_CCACHE_BUCKET_LIMIT_DEFAULT 16 +#define TCP_FASTOPEN_CCACHE_BUCKETS_DEFAULT 2048 /* must be power of 2 */ + +#define TCP_FASTOPEN_PATH_DISABLE_TIME_DEFAULT 900 /* seconds */ + +#if !defined(TCP_RFC7413_MAX_KEYS) || (TCP_RFC7413_MAX_KEYS < 1) +#define TCP_FASTOPEN_MAX_KEYS 2 +#else +#define TCP_FASTOPEN_MAX_KEYS TCP_RFC7413_MAX_KEYS +#endif + +#if TCP_FASTOPEN_MAX_KEYS > 10 +#undef TCP_FASTOPEN_MAX_KEYS +#define TCP_FASTOPEN_MAX_KEYS 10 +#endif + +#if !defined(TCP_RFC7413_MAX_PSKS) || (TCP_RFC7413_MAX_PSKS < 1) +#define TCP_FASTOPEN_MAX_PSKS 2 +#else +#define TCP_FASTOPEN_MAX_PSKS TCP_RFC7413_MAX_PSKS +#endif + +#if TCP_FASTOPEN_MAX_PSKS > 10 +#undef TCP_FASTOPEN_MAX_PSKS +#define TCP_FASTOPEN_MAX_PSKS 10 +#endif + +struct tcp_fastopen_keylist { + unsigned int newest; + unsigned int newest_psk; + uint8_t key[TCP_FASTOPEN_MAX_KEYS][TCP_FASTOPEN_KEY_LEN]; + uint8_t psk[TCP_FASTOPEN_MAX_PSKS][TCP_FASTOPEN_KEY_LEN]; +}; + +struct tcp_fastopen_callout { + struct callout c; + struct vnet *v; +}; + +static struct tcp_fastopen_ccache_entry *tcp_fastopen_ccache_lookup( + struct in_conninfo *, struct tcp_fastopen_ccache_bucket **); +static struct tcp_fastopen_ccache_entry *tcp_fastopen_ccache_create( + struct tcp_fastopen_ccache_bucket *, struct in_conninfo *, uint16_t, uint8_t, + uint8_t *); +static void tcp_fastopen_ccache_bucket_trim(struct tcp_fastopen_ccache_bucket *, + unsigned int); +static void tcp_fastopen_ccache_entry_drop(struct tcp_fastopen_ccache_entry *, + struct tcp_fastopen_ccache_bucket *); + +SYSCTL_NODE(_net_inet_tcp, OID_AUTO, fastopen, CTLFLAG_RW, 0, "TCP Fast Open"); + +VNET_DEFINE_STATIC(int, tcp_fastopen_acceptany) = 0; +#define V_tcp_fastopen_acceptany VNET(tcp_fastopen_acceptany) +SYSCTL_INT(_net_inet_tcp_fastopen, OID_AUTO, acceptany, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(tcp_fastopen_acceptany), 0, + "Accept any non-empty cookie"); + +VNET_DEFINE_STATIC(unsigned int, tcp_fastopen_autokey) = 120; +#define V_tcp_fastopen_autokey VNET(tcp_fastopen_autokey) +static int sysctl_net_inet_tcp_fastopen_autokey(SYSCTL_HANDLER_ARGS); +SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, autokey, + CTLFLAG_VNET | CTLTYPE_UINT | CTLFLAG_RW, NULL, 0, + &sysctl_net_inet_tcp_fastopen_autokey, "IU", + "Number of seconds between auto-generation of a new key; zero disables"); + +static int sysctl_net_inet_tcp_fastopen_ccache_bucket_limit(SYSCTL_HANDLER_ARGS); +SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, ccache_bucket_limit, + CTLFLAG_VNET | CTLTYPE_UINT | CTLFLAG_RWTUN, NULL, 0, + &sysctl_net_inet_tcp_fastopen_ccache_bucket_limit, "IU", + "Max entries per bucket in client cookie cache"); + +VNET_DEFINE_STATIC(unsigned int, tcp_fastopen_ccache_buckets) = + TCP_FASTOPEN_CCACHE_BUCKETS_DEFAULT; +#define V_tcp_fastopen_ccache_buckets VNET(tcp_fastopen_ccache_buckets) +SYSCTL_UINT(_net_inet_tcp_fastopen, OID_AUTO, ccache_buckets, + CTLFLAG_VNET | CTLFLAG_RDTUN, &VNET_NAME(tcp_fastopen_ccache_buckets), 0, + "Client cookie cache number of buckets (power of 2)"); + +VNET_DEFINE(unsigned int, tcp_fastopen_client_enable) = 1; +static int sysctl_net_inet_tcp_fastopen_client_enable(SYSCTL_HANDLER_ARGS); +SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, client_enable, + CTLFLAG_VNET | CTLTYPE_UINT | CTLFLAG_RW, NULL, 0, + &sysctl_net_inet_tcp_fastopen_client_enable, "IU", + "Enable/disable TCP Fast Open client functionality"); + +SYSCTL_INT(_net_inet_tcp_fastopen, OID_AUTO, keylen, + CTLFLAG_RD, SYSCTL_NULL_INT_PTR, TCP_FASTOPEN_KEY_LEN, + "Key length in bytes"); + +SYSCTL_INT(_net_inet_tcp_fastopen, OID_AUTO, maxkeys, + CTLFLAG_RD, SYSCTL_NULL_INT_PTR, TCP_FASTOPEN_MAX_KEYS, + "Maximum number of keys supported"); + +SYSCTL_INT(_net_inet_tcp_fastopen, OID_AUTO, maxpsks, + CTLFLAG_RD, SYSCTL_NULL_INT_PTR, TCP_FASTOPEN_MAX_PSKS, + "Maximum number of pre-shared keys supported"); + +VNET_DEFINE_STATIC(unsigned int, tcp_fastopen_numkeys) = 0; +#define V_tcp_fastopen_numkeys VNET(tcp_fastopen_numkeys) +SYSCTL_UINT(_net_inet_tcp_fastopen, OID_AUTO, numkeys, + CTLFLAG_VNET | CTLFLAG_RD, &VNET_NAME(tcp_fastopen_numkeys), 0, + "Number of keys installed"); + +VNET_DEFINE_STATIC(unsigned int, tcp_fastopen_numpsks) = 0; +#define V_tcp_fastopen_numpsks VNET(tcp_fastopen_numpsks) +SYSCTL_UINT(_net_inet_tcp_fastopen, OID_AUTO, numpsks, + CTLFLAG_VNET | CTLFLAG_RD, &VNET_NAME(tcp_fastopen_numpsks), 0, + "Number of pre-shared keys installed"); + +VNET_DEFINE_STATIC(unsigned int, tcp_fastopen_path_disable_time) = + TCP_FASTOPEN_PATH_DISABLE_TIME_DEFAULT; +#define V_tcp_fastopen_path_disable_time VNET(tcp_fastopen_path_disable_time) +SYSCTL_UINT(_net_inet_tcp_fastopen, OID_AUTO, path_disable_time, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(tcp_fastopen_path_disable_time), 0, + "Seconds a TFO failure disables a {client_ip, server_ip, server_port} path"); + +VNET_DEFINE_STATIC(unsigned int, tcp_fastopen_psk_enable) = 0; +#define V_tcp_fastopen_psk_enable VNET(tcp_fastopen_psk_enable) +static int sysctl_net_inet_tcp_fastopen_psk_enable(SYSCTL_HANDLER_ARGS); +SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, psk_enable, + CTLFLAG_VNET | CTLTYPE_UINT | CTLFLAG_RW, NULL, 0, + &sysctl_net_inet_tcp_fastopen_psk_enable, "IU", + "Enable/disable TCP Fast Open server pre-shared key mode"); + +VNET_DEFINE(unsigned int, tcp_fastopen_server_enable) = 0; +static int sysctl_net_inet_tcp_fastopen_server_enable(SYSCTL_HANDLER_ARGS); +SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, server_enable, + CTLFLAG_VNET | CTLTYPE_UINT | CTLFLAG_RW, NULL, 0, + &sysctl_net_inet_tcp_fastopen_server_enable, "IU", + "Enable/disable TCP Fast Open server functionality"); + +static int sysctl_net_inet_tcp_fastopen_setkey(SYSCTL_HANDLER_ARGS); +SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, setkey, + CTLFLAG_VNET | CTLTYPE_OPAQUE | CTLFLAG_WR, NULL, 0, + &sysctl_net_inet_tcp_fastopen_setkey, "", + "Install a new key"); + +static int sysctl_net_inet_tcp_fastopen_setpsk(SYSCTL_HANDLER_ARGS); +SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, setpsk, + CTLFLAG_VNET | CTLTYPE_OPAQUE | CTLFLAG_WR, NULL, 0, + &sysctl_net_inet_tcp_fastopen_setpsk, "", + "Install a new pre-shared key"); + +static int sysctl_net_inet_tcp_fastopen_ccache_list(SYSCTL_HANDLER_ARGS); +SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, ccache_list, + CTLFLAG_VNET | CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_SKIP, NULL, 0, + sysctl_net_inet_tcp_fastopen_ccache_list, "A", + "List of all client cookie cache entries"); + +VNET_DEFINE_STATIC(struct rmlock, tcp_fastopen_keylock); +#define V_tcp_fastopen_keylock VNET(tcp_fastopen_keylock) + +#define TCP_FASTOPEN_KEYS_RLOCK(t) rm_rlock(&V_tcp_fastopen_keylock, (t)) +#define TCP_FASTOPEN_KEYS_RUNLOCK(t) rm_runlock(&V_tcp_fastopen_keylock, (t)) +#define TCP_FASTOPEN_KEYS_WLOCK() rm_wlock(&V_tcp_fastopen_keylock) +#define TCP_FASTOPEN_KEYS_WUNLOCK() rm_wunlock(&V_tcp_fastopen_keylock) + +VNET_DEFINE_STATIC(struct tcp_fastopen_keylist, tcp_fastopen_keys); +#define V_tcp_fastopen_keys VNET(tcp_fastopen_keys) + +VNET_DEFINE_STATIC(struct tcp_fastopen_callout, tcp_fastopen_autokey_ctx); +#define V_tcp_fastopen_autokey_ctx VNET(tcp_fastopen_autokey_ctx) + +VNET_DEFINE_STATIC(uma_zone_t, counter_zone); +#define V_counter_zone VNET(counter_zone) + +static MALLOC_DEFINE(M_TCP_FASTOPEN_CCACHE, "tfo_ccache", "TFO client cookie cache buckets"); + +VNET_DEFINE_STATIC(struct tcp_fastopen_ccache, tcp_fastopen_ccache); +#define V_tcp_fastopen_ccache VNET(tcp_fastopen_ccache) + +#define CCB_LOCK(ccb) mtx_lock(&(ccb)->ccb_mtx) +#define CCB_UNLOCK(ccb) mtx_unlock(&(ccb)->ccb_mtx) +#define CCB_LOCK_ASSERT(ccb) mtx_assert(&(ccb)->ccb_mtx, MA_OWNED) + +#endif + +void +tcp_fastopen_init(void) +{ +#if 0 + unsigned int i; + + V_counter_zone = uma_zcreate("tfo", sizeof(unsigned int), + NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); + rm_init(&V_tcp_fastopen_keylock, "tfo_keylock"); + callout_init_rm(&V_tcp_fastopen_autokey_ctx.c, + &V_tcp_fastopen_keylock, 0); + V_tcp_fastopen_autokey_ctx.v = curvnet; + V_tcp_fastopen_keys.newest = TCP_FASTOPEN_MAX_KEYS - 1; + V_tcp_fastopen_keys.newest_psk = TCP_FASTOPEN_MAX_PSKS - 1; + + /* May already be non-zero if kernel tunable was set */ + if (V_tcp_fastopen_ccache.bucket_limit == 0) + V_tcp_fastopen_ccache.bucket_limit = + TCP_FASTOPEN_CCACHE_BUCKET_LIMIT_DEFAULT; + + /* May already be non-zero if kernel tunable was set */ + if ((V_tcp_fastopen_ccache_buckets == 0) || + !powerof2(V_tcp_fastopen_ccache_buckets)) + V_tcp_fastopen_ccache.buckets = + TCP_FASTOPEN_CCACHE_BUCKETS_DEFAULT; + else + V_tcp_fastopen_ccache.buckets = V_tcp_fastopen_ccache_buckets; + + V_tcp_fastopen_ccache.mask = V_tcp_fastopen_ccache.buckets - 1; + V_tcp_fastopen_ccache.secret = arc4random(); + + V_tcp_fastopen_ccache.base = malloc(V_tcp_fastopen_ccache.buckets * + sizeof(struct tcp_fastopen_ccache_bucket), M_TCP_FASTOPEN_CCACHE, + M_WAITOK | M_ZERO); + + for (i = 0; i < V_tcp_fastopen_ccache.buckets; i++) { + TAILQ_INIT(&V_tcp_fastopen_ccache.base[i].ccb_entries); + mtx_init(&V_tcp_fastopen_ccache.base[i].ccb_mtx, "tfo_ccache_bucket", + NULL, MTX_DEF); + if (V_tcp_fastopen_client_enable) { + /* enable bucket */ + V_tcp_fastopen_ccache.base[i].ccb_num_entries = 0; + } else { + /* disable bucket */ + V_tcp_fastopen_ccache.base[i].ccb_num_entries = -1; + } + V_tcp_fastopen_ccache.base[i].ccb_ccache = &V_tcp_fastopen_ccache; + } + + /* + * Note that while the total number of entries in the cookie cache + * is limited by the table management logic to + * V_tcp_fastopen_ccache.buckets * + * V_tcp_fastopen_ccache.bucket_limit, the total number of items in + * this zone can exceed that amount by the number of CPUs in the + * system times the maximum number of unallocated items that can be + * present in each UMA per-CPU cache for this zone. + */ + V_tcp_fastopen_ccache.zone = uma_zcreate("tfo_ccache_entries", + sizeof(struct tcp_fastopen_ccache_entry), NULL, NULL, NULL, NULL, + UMA_ALIGN_CACHE, 0); +#endif +} + +void +tcp_fastopen_destroy(void) +{ +#if 0 + struct tcp_fastopen_ccache_bucket *ccb; + unsigned int i; + + for (i = 0; i < V_tcp_fastopen_ccache.buckets; i++) { + ccb = &V_tcp_fastopen_ccache.base[i]; + tcp_fastopen_ccache_bucket_trim(ccb, 0); + mtx_destroy(&ccb->ccb_mtx); + } + + KASSERT(uma_zone_get_cur(V_tcp_fastopen_ccache.zone) == 0, + ("%s: TFO ccache zone allocation count not 0", __func__)); + uma_zdestroy(V_tcp_fastopen_ccache.zone); + free(V_tcp_fastopen_ccache.base, M_TCP_FASTOPEN_CCACHE); + + callout_drain(&V_tcp_fastopen_autokey_ctx.c); + rm_destroy(&V_tcp_fastopen_keylock); + uma_zdestroy(V_counter_zone); +#endif +} + +/* + * samkumar: The original FreeBSD code has a counter for each listen + * socket keeping track of how many TFO connections on that socket + * are "pending" (i.e., in the SYN-RCVD state). This counter is + * heap-allocated because the connections may decrement it upon + * leaving that state after the listen socket has been deallocated. + * + * In TCPlp, we skip this counter. Technically, RFC 7413 mandates + * only a global counter (not yet implemented in TCPlp). So I've + * replaced these with stubs. + */ + +unsigned int * +tcp_fastopen_alloc_counter(void) +{ +#if 0 + unsigned int *counter; + counter = uma_zalloc(V_counter_zone, M_NOWAIT); + if (counter) + *counter = 1; + return (counter); +#endif + return NULL; +} + +void +tcp_fastopen_decrement_counter(unsigned int *counter) +{ +#if 0 + if (*counter == 1) + uma_zfree(V_counter_zone, counter); + else + atomic_subtract_int(counter, 1); +#endif +} + +#if 0 + +static void +tcp_fastopen_addkey_locked(uint8_t *key) +{ + + V_tcp_fastopen_keys.newest++; + if (V_tcp_fastopen_keys.newest == TCP_FASTOPEN_MAX_KEYS) + V_tcp_fastopen_keys.newest = 0; + memcpy(V_tcp_fastopen_keys.key[V_tcp_fastopen_keys.newest], key, + TCP_FASTOPEN_KEY_LEN); + if (V_tcp_fastopen_numkeys < TCP_FASTOPEN_MAX_KEYS) + V_tcp_fastopen_numkeys++; +} + +static void +tcp_fastopen_addpsk_locked(uint8_t *psk) +{ + + V_tcp_fastopen_keys.newest_psk++; + if (V_tcp_fastopen_keys.newest_psk == TCP_FASTOPEN_MAX_PSKS) + V_tcp_fastopen_keys.newest_psk = 0; + memcpy(V_tcp_fastopen_keys.psk[V_tcp_fastopen_keys.newest_psk], psk, + TCP_FASTOPEN_KEY_LEN); + if (V_tcp_fastopen_numpsks < TCP_FASTOPEN_MAX_PSKS) + V_tcp_fastopen_numpsks++; +} + +static void +tcp_fastopen_autokey_locked(void) +{ + uint8_t newkey[TCP_FASTOPEN_KEY_LEN]; + + arc4rand(newkey, TCP_FASTOPEN_KEY_LEN, 0); + tcp_fastopen_addkey_locked(newkey); +} + +static void +tcp_fastopen_autokey_callout(void *arg) +{ + struct tcp_fastopen_callout *ctx = arg; + + CURVNET_SET(ctx->v); + tcp_fastopen_autokey_locked(); + callout_reset(&ctx->c, V_tcp_fastopen_autokey * hz, + tcp_fastopen_autokey_callout, ctx); + CURVNET_RESTORE(); +} + + +static uint64_t +tcp_fastopen_make_cookie(uint8_t key[SIPHASH_KEY_LENGTH], struct in_conninfo *inc) +{ + SIPHASH_CTX ctx; + uint64_t siphash; + + SipHash24_Init(&ctx); + SipHash_SetKey(&ctx, key); + switch (inc->inc_flags & INC_ISIPV6) { +#ifdef INET + case 0: + SipHash_Update(&ctx, &inc->inc_faddr, sizeof(inc->inc_faddr)); + break; +#endif +#ifdef INET6 + case INC_ISIPV6: + SipHash_Update(&ctx, &inc->inc6_faddr, sizeof(inc->inc6_faddr)); + break; +#endif + } + SipHash_Final((u_int8_t *)&siphash, &ctx); + + return (siphash); +} + +static uint64_t +tcp_fastopen_make_psk_cookie(uint8_t *psk, uint8_t *cookie, uint8_t cookie_len) +{ + SIPHASH_CTX ctx; + uint64_t psk_cookie; + + SipHash24_Init(&ctx); + SipHash_SetKey(&ctx, psk); + SipHash_Update(&ctx, cookie, cookie_len); + SipHash_Final((u_int8_t *)&psk_cookie, &ctx); + + return (psk_cookie); +} + +static int +tcp_fastopen_find_cookie_match_locked(uint8_t *wire_cookie, uint64_t *cur_cookie) +{ + unsigned int i, psk_index; + uint64_t psk_cookie; + + if (V_tcp_fastopen_psk_enable) { + psk_index = V_tcp_fastopen_keys.newest_psk; + for (i = 0; i < V_tcp_fastopen_numpsks; i++) { + psk_cookie = + tcp_fastopen_make_psk_cookie( + V_tcp_fastopen_keys.psk[psk_index], + (uint8_t *)cur_cookie, + TCP_FASTOPEN_COOKIE_LEN); + + if (memcmp(wire_cookie, &psk_cookie, + TCP_FASTOPEN_COOKIE_LEN) == 0) + return (1); + + if (psk_index == 0) + psk_index = TCP_FASTOPEN_MAX_PSKS - 1; + else + psk_index--; + } + } else if (memcmp(wire_cookie, cur_cookie, TCP_FASTOPEN_COOKIE_LEN) == 0) + return (1); + + return (0); +} + +#endif + +/* + * Return values: + * -1 the cookie is invalid and no valid cookie is available + * 0 the cookie is invalid and the latest cookie has been returned + * 1 the cookie is valid and the latest cookie has been returned + */ +/* + * samkumar: In the signature, changed "struct in_conninfo *inc" to + * "struct tcpcb* tp". + */ +int +tcp_fastopen_check_cookie(struct tcpcb* tp, uint8_t *cookie, + unsigned int len, uint64_t *latest_cookie) +{ +#if 0 + struct rm_priotracker tracker; + unsigned int i, key_index; + int rv; + uint64_t cur_cookie; +#endif + + if (V_tcp_fastopen_acceptany) { + *latest_cookie = 0; + return (1); + } + + /* + * samkumar: For TCPlp, assume that we always accept any cookie. + * + * The original code is commented out below this return statement. + */ + + return -1; + +#if 0 + + TCP_FASTOPEN_KEYS_RLOCK(&tracker); + if (len != TCP_FASTOPEN_COOKIE_LEN) { + if (V_tcp_fastopen_numkeys > 0) { + *latest_cookie = + tcp_fastopen_make_cookie( + V_tcp_fastopen_keys.key[V_tcp_fastopen_keys.newest], + inc); + rv = 0; + } else + rv = -1; + goto out; + } + + /* + * Check against each available key, from newest to oldest. + */ + key_index = V_tcp_fastopen_keys.newest; + for (i = 0; i < V_tcp_fastopen_numkeys; i++) { + cur_cookie = + tcp_fastopen_make_cookie(V_tcp_fastopen_keys.key[key_index], + inc); + if (i == 0) + *latest_cookie = cur_cookie; + rv = tcp_fastopen_find_cookie_match_locked(cookie, &cur_cookie); + if (rv) + goto out; + if (key_index == 0) + key_index = TCP_FASTOPEN_MAX_KEYS - 1; + else + key_index--; + } + rv = 0; + + out: + TCP_FASTOPEN_KEYS_RUNLOCK(&tracker); + return (rv); +#endif +} + +#if 0 +static int +sysctl_net_inet_tcp_fastopen_autokey(SYSCTL_HANDLER_ARGS) +{ + int error; + unsigned int new; + + new = V_tcp_fastopen_autokey; + error = sysctl_handle_int(oidp, &new, 0, req); + if (error == 0 && req->newptr) { + if (new > (INT_MAX / hz)) + return (EINVAL); + + TCP_FASTOPEN_KEYS_WLOCK(); + if (V_tcp_fastopen_server_enable) { + if (V_tcp_fastopen_autokey && !new) + callout_stop(&V_tcp_fastopen_autokey_ctx.c); + else if (new) + callout_reset(&V_tcp_fastopen_autokey_ctx.c, + new * hz, tcp_fastopen_autokey_callout, + &V_tcp_fastopen_autokey_ctx); + } + V_tcp_fastopen_autokey = new; + TCP_FASTOPEN_KEYS_WUNLOCK(); + } + + return (error); +} + +static int +sysctl_net_inet_tcp_fastopen_psk_enable(SYSCTL_HANDLER_ARGS) +{ + int error; + unsigned int new; + + new = V_tcp_fastopen_psk_enable; + error = sysctl_handle_int(oidp, &new, 0, req); + if (error == 0 && req->newptr) { + if (V_tcp_fastopen_psk_enable && !new) { + /* enabled -> disabled */ + TCP_FASTOPEN_KEYS_WLOCK(); + V_tcp_fastopen_numpsks = 0; + V_tcp_fastopen_keys.newest_psk = + TCP_FASTOPEN_MAX_PSKS - 1; + V_tcp_fastopen_psk_enable = 0; + TCP_FASTOPEN_KEYS_WUNLOCK(); + } else if (!V_tcp_fastopen_psk_enable && new) { + /* disabled -> enabled */ + TCP_FASTOPEN_KEYS_WLOCK(); + V_tcp_fastopen_psk_enable = 1; + TCP_FASTOPEN_KEYS_WUNLOCK(); + } + } + return (error); +} + +static int +sysctl_net_inet_tcp_fastopen_server_enable(SYSCTL_HANDLER_ARGS) +{ + int error; + unsigned int new; + + new = V_tcp_fastopen_server_enable; + error = sysctl_handle_int(oidp, &new, 0, req); + if (error == 0 && req->newptr) { + if (V_tcp_fastopen_server_enable && !new) { + /* enabled -> disabled */ + TCP_FASTOPEN_KEYS_WLOCK(); + V_tcp_fastopen_numkeys = 0; + V_tcp_fastopen_keys.newest = TCP_FASTOPEN_MAX_KEYS - 1; + if (V_tcp_fastopen_autokey) + callout_stop(&V_tcp_fastopen_autokey_ctx.c); + V_tcp_fastopen_numpsks = 0; + V_tcp_fastopen_keys.newest_psk = + TCP_FASTOPEN_MAX_PSKS - 1; + V_tcp_fastopen_server_enable = 0; + TCP_FASTOPEN_KEYS_WUNLOCK(); + } else if (!V_tcp_fastopen_server_enable && new) { + /* disabled -> enabled */ + TCP_FASTOPEN_KEYS_WLOCK(); + if (V_tcp_fastopen_autokey && + (V_tcp_fastopen_numkeys == 0)) { + tcp_fastopen_autokey_locked(); + callout_reset(&V_tcp_fastopen_autokey_ctx.c, + V_tcp_fastopen_autokey * hz, + tcp_fastopen_autokey_callout, + &V_tcp_fastopen_autokey_ctx); + } + V_tcp_fastopen_server_enable = 1; + TCP_FASTOPEN_KEYS_WUNLOCK(); + } + } + return (error); +} + +static int +sysctl_net_inet_tcp_fastopen_setkey(SYSCTL_HANDLER_ARGS) +{ + int error; + uint8_t newkey[TCP_FASTOPEN_KEY_LEN]; + + if (req->oldptr != NULL || req->oldlen != 0) + return (EINVAL); + if (req->newptr == NULL) + return (EPERM); + if (req->newlen != sizeof(newkey)) + return (EINVAL); + error = SYSCTL_IN(req, newkey, sizeof(newkey)); + if (error) + return (error); + + TCP_FASTOPEN_KEYS_WLOCK(); + tcp_fastopen_addkey_locked(newkey); + TCP_FASTOPEN_KEYS_WUNLOCK(); + + return (0); +} + +static int +sysctl_net_inet_tcp_fastopen_setpsk(SYSCTL_HANDLER_ARGS) +{ + int error; + uint8_t newpsk[TCP_FASTOPEN_KEY_LEN]; + + if (req->oldptr != NULL || req->oldlen != 0) + return (EINVAL); + if (req->newptr == NULL) + return (EPERM); + if (req->newlen != sizeof(newpsk)) + return (EINVAL); + error = SYSCTL_IN(req, newpsk, sizeof(newpsk)); + if (error) + return (error); + + TCP_FASTOPEN_KEYS_WLOCK(); + tcp_fastopen_addpsk_locked(newpsk); + TCP_FASTOPEN_KEYS_WUNLOCK(); + + return (0); +} + +static int +sysctl_net_inet_tcp_fastopen_ccache_bucket_limit(SYSCTL_HANDLER_ARGS) +{ + struct tcp_fastopen_ccache_bucket *ccb; + int error; + unsigned int new; + unsigned int i; + + new = V_tcp_fastopen_ccache.bucket_limit; + error = sysctl_handle_int(oidp, &new, 0, req); + if (error == 0 && req->newptr) { + if ((new == 0) || (new > INT_MAX)) + error = EINVAL; + else { + if (new < V_tcp_fastopen_ccache.bucket_limit) { + for (i = 0; i < V_tcp_fastopen_ccache.buckets; + i++) { + ccb = &V_tcp_fastopen_ccache.base[i]; + tcp_fastopen_ccache_bucket_trim(ccb, new); + } + } + V_tcp_fastopen_ccache.bucket_limit = new; + } + + } + return (error); +} + +static int +sysctl_net_inet_tcp_fastopen_client_enable(SYSCTL_HANDLER_ARGS) +{ + struct tcp_fastopen_ccache_bucket *ccb; + int error; + unsigned int new, i; + + new = V_tcp_fastopen_client_enable; + error = sysctl_handle_int(oidp, &new, 0, req); + if (error == 0 && req->newptr) { + if (V_tcp_fastopen_client_enable && !new) { + /* enabled -> disabled */ + for (i = 0; i < V_tcp_fastopen_ccache.buckets; i++) { + ccb = &V_tcp_fastopen_ccache.base[i]; + KASSERT(ccb->ccb_num_entries > -1, + ("%s: ccb->ccb_num_entries %d is negative", + __func__, ccb->ccb_num_entries)); + tcp_fastopen_ccache_bucket_trim(ccb, 0); + } + V_tcp_fastopen_client_enable = 0; + } else if (!V_tcp_fastopen_client_enable && new) { + /* disabled -> enabled */ + for (i = 0; i < V_tcp_fastopen_ccache.buckets; i++) { + ccb = &V_tcp_fastopen_ccache.base[i]; + CCB_LOCK(ccb); + KASSERT(TAILQ_EMPTY(&ccb->ccb_entries), + ("%s: ccb->ccb_entries not empty", __func__)); + KASSERT(ccb->ccb_num_entries == -1, + ("%s: ccb->ccb_num_entries %d not -1", __func__, + ccb->ccb_num_entries)); + ccb->ccb_num_entries = 0; /* enable bucket */ + CCB_UNLOCK(ccb); + } + V_tcp_fastopen_client_enable = 1; + } + } + return (error); +} + +#endif + +void +tcp_fastopen_connect(struct tcpcb *tp) +{ + /* + * samkumar: In TCPlp, we always act as though there is + * a cache miss. + * + * However, we would like to be able to send data with + * cookie requests. Therefore, we leave tp->t_maxseg at + * the default value instead of calling tcp_mss(tp, -1) + * and we set tp->snd_wnd as if there is a cache hit. + */ + tp->snd_wnd = tp->t_maxseg; + +#if 0 + struct inpcb *inp; + struct tcp_fastopen_ccache_bucket *ccb; + struct tcp_fastopen_ccache_entry *cce; + sbintime_t now; + uint16_t server_mss; + uint64_t psk_cookie; + + psk_cookie = 0; + inp = tp->t_inpcb; + cce = tcp_fastopen_ccache_lookup(&inp->inp_inc, &ccb); + if (cce) { + if (cce->disable_time == 0) { + if ((cce->cookie_len > 0) && + (tp->t_tfo_client_cookie_len == + TCP_FASTOPEN_PSK_LEN)) { + psk_cookie = + tcp_fastopen_make_psk_cookie( + tp->t_tfo_cookie.client, + cce->cookie, cce->cookie_len); + } else { + tp->t_tfo_client_cookie_len = cce->cookie_len; + memcpy(tp->t_tfo_cookie.client, cce->cookie, + cce->cookie_len); + } + server_mss = cce->server_mss; + CCB_UNLOCK(ccb); + if (tp->t_tfo_client_cookie_len == + TCP_FASTOPEN_PSK_LEN && psk_cookie) { + tp->t_tfo_client_cookie_len = + TCP_FASTOPEN_COOKIE_LEN; + memcpy(tp->t_tfo_cookie.client, &psk_cookie, + TCP_FASTOPEN_COOKIE_LEN); + } + tcp_mss(tp, server_mss ? server_mss : -1); + tp->snd_wnd = tp->t_maxseg; + } else { + /* + * The path is disabled. Check the time and + * possibly re-enable. + */ + now = getsbinuptime(); + if (now - cce->disable_time > + ((sbintime_t)V_tcp_fastopen_path_disable_time << 32)) { + /* + * Re-enable path. Force a TFO cookie + * request. Forget the old MSS as it may be + * bogus now, and we will rediscover it in + * the SYN|ACK. + */ + cce->disable_time = 0; + cce->server_mss = 0; + cce->cookie_len = 0; + /* + * tp->t_tfo... cookie details are already + * zero from the tcpcb init. + */ + } else { + /* + * Path is disabled, so disable TFO on this + * connection. + */ + tp->t_flags &= ~TF_FASTOPEN; + } + CCB_UNLOCK(ccb); + tcp_mss(tp, -1); + /* + * snd_wnd is irrelevant since we are either forcing + * a TFO cookie request or disabling TFO - either + * way, no data with the SYN. + */ + } + } else { + /* + * A new entry for this path will be created when a SYN|ACK + * comes back, or the attempt otherwise fails. + */ + CCB_UNLOCK(ccb); + tcp_mss(tp, -1); + /* + * snd_wnd is irrelevant since we are forcing a TFO cookie + * request. + */ + } +#endif +} + +void +tcp_fastopen_disable_path(struct tcpcb *tp) +{ +#if 0 + struct in_conninfo *inc = &tp->t_inpcb->inp_inc; + struct tcp_fastopen_ccache_bucket *ccb; + struct tcp_fastopen_ccache_entry *cce; + + cce = tcp_fastopen_ccache_lookup(inc, &ccb); + if (cce) { + cce->server_mss = 0; + cce->cookie_len = 0; + /* + * Preserve the existing disable time if it is already + * disabled. + */ + if (cce->disable_time == 0) + cce->disable_time = getsbinuptime(); + } else /* use invalid cookie len to create disabled entry */ + tcp_fastopen_ccache_create(ccb, inc, 0, + TCP_FASTOPEN_MAX_COOKIE_LEN + 1, NULL); + + CCB_UNLOCK(ccb); +#endif + tp->t_flags &= ~TF_FASTOPEN; +} + +void +tcp_fastopen_update_cache(struct tcpcb *tp, uint16_t mss, + uint8_t cookie_len, uint8_t *cookie) +{ +#if 0 + struct in_conninfo *inc = &tp->t_inpcb->inp_inc; + struct tcp_fastopen_ccache_bucket *ccb; + struct tcp_fastopen_ccache_entry *cce; + + cce = tcp_fastopen_ccache_lookup(inc, &ccb); + if (cce) { + if ((cookie_len >= TCP_FASTOPEN_MIN_COOKIE_LEN) && + (cookie_len <= TCP_FASTOPEN_MAX_COOKIE_LEN) && + ((cookie_len & 0x1) == 0)) { + cce->server_mss = mss; + cce->cookie_len = cookie_len; + memcpy(cce->cookie, cookie, cookie_len); + cce->disable_time = 0; + } else { + /* invalid cookie length, disable entry */ + cce->server_mss = 0; + cce->cookie_len = 0; + /* + * Preserve the existing disable time if it is + * already disabled. + */ + if (cce->disable_time == 0) + cce->disable_time = getsbinuptime(); + } + } else + tcp_fastopen_ccache_create(ccb, inc, mss, cookie_len, cookie); + + CCB_UNLOCK(ccb); +#endif +} + +#if 0 + +static struct tcp_fastopen_ccache_entry * +tcp_fastopen_ccache_lookup(struct in_conninfo *inc, + struct tcp_fastopen_ccache_bucket **ccbp) +{ + struct tcp_fastopen_ccache_bucket *ccb; + struct tcp_fastopen_ccache_entry *cce; + uint32_t last_word; + uint32_t hash; + + hash = jenkins_hash32((uint32_t *)&inc->inc_ie.ie_dependladdr, 4, + V_tcp_fastopen_ccache.secret); + hash = jenkins_hash32((uint32_t *)&inc->inc_ie.ie_dependfaddr, 4, + hash); + last_word = inc->inc_fport; + hash = jenkins_hash32(&last_word, 1, hash); + ccb = &V_tcp_fastopen_ccache.base[hash & V_tcp_fastopen_ccache.mask]; + *ccbp = ccb; + CCB_LOCK(ccb); + + /* + * Always returns with locked bucket. + */ + TAILQ_FOREACH(cce, &ccb->ccb_entries, cce_link) + if ((!(cce->af == AF_INET6) == !(inc->inc_flags & INC_ISIPV6)) && + (cce->server_port == inc->inc_ie.ie_fport) && + (((cce->af == AF_INET) && + (cce->cce_client_ip.v4.s_addr == inc->inc_laddr.s_addr) && + (cce->cce_server_ip.v4.s_addr == inc->inc_faddr.s_addr)) || + ((cce->af == AF_INET6) && + IN6_ARE_ADDR_EQUAL(&cce->cce_client_ip.v6, &inc->inc6_laddr) && + IN6_ARE_ADDR_EQUAL(&cce->cce_server_ip.v6, &inc->inc6_faddr)))) + break; + + return (cce); +} + +static struct tcp_fastopen_ccache_entry * +tcp_fastopen_ccache_create(struct tcp_fastopen_ccache_bucket *ccb, + struct in_conninfo *inc, uint16_t mss, uint8_t cookie_len, uint8_t *cookie) +{ + struct tcp_fastopen_ccache_entry *cce; + + /* + * 1. Create a new entry, or + * 2. Reclaim an existing entry, or + * 3. Fail + */ + + CCB_LOCK_ASSERT(ccb); + + cce = NULL; + if (ccb->ccb_num_entries < V_tcp_fastopen_ccache.bucket_limit) + cce = uma_zalloc(V_tcp_fastopen_ccache.zone, M_NOWAIT); + + if (cce == NULL) { + /* + * At bucket limit, or out of memory - reclaim last + * entry in bucket. + */ + cce = TAILQ_LAST(&ccb->ccb_entries, bucket_entries); + if (cce == NULL) { + /* XXX count this event */ + return (NULL); + } + + TAILQ_REMOVE(&ccb->ccb_entries, cce, cce_link); + } else + ccb->ccb_num_entries++; + + TAILQ_INSERT_HEAD(&ccb->ccb_entries, cce, cce_link); + cce->af = (inc->inc_flags & INC_ISIPV6) ? AF_INET6 : AF_INET; + if (cce->af == AF_INET) { + cce->cce_client_ip.v4 = inc->inc_laddr; + cce->cce_server_ip.v4 = inc->inc_faddr; + } else { + cce->cce_client_ip.v6 = inc->inc6_laddr; + cce->cce_server_ip.v6 = inc->inc6_faddr; + } + cce->server_port = inc->inc_fport; + if ((cookie_len >= TCP_FASTOPEN_MIN_COOKIE_LEN) && + (cookie_len <= TCP_FASTOPEN_MAX_COOKIE_LEN) && + ((cookie_len & 0x1) == 0)) { + cce->server_mss = mss; + cce->cookie_len = cookie_len; + memcpy(cce->cookie, cookie, cookie_len); + cce->disable_time = 0; + } else { + /* invalid cookie length, disable cce */ + cce->server_mss = 0; + cce->cookie_len = 0; + cce->disable_time = getsbinuptime(); + } + + return (cce); +} + +static void +tcp_fastopen_ccache_bucket_trim(struct tcp_fastopen_ccache_bucket *ccb, + unsigned int limit) +{ + struct tcp_fastopen_ccache_entry *cce, *cce_tmp; + unsigned int entries; + + CCB_LOCK(ccb); + entries = 0; + TAILQ_FOREACH_SAFE(cce, &ccb->ccb_entries, cce_link, cce_tmp) { + entries++; + if (entries > limit) + tcp_fastopen_ccache_entry_drop(cce, ccb); + } + KASSERT(ccb->ccb_num_entries <= (int)limit, + ("%s: ccb->ccb_num_entries %d exceeds limit %d", __func__, + ccb->ccb_num_entries, limit)); + if (limit == 0) { + KASSERT(TAILQ_EMPTY(&ccb->ccb_entries), + ("%s: ccb->ccb_entries not empty", __func__)); + ccb->ccb_num_entries = -1; /* disable bucket */ + } + CCB_UNLOCK(ccb); +} + +static void +tcp_fastopen_ccache_entry_drop(struct tcp_fastopen_ccache_entry *cce, + struct tcp_fastopen_ccache_bucket *ccb) +{ + + CCB_LOCK_ASSERT(ccb); + + TAILQ_REMOVE(&ccb->ccb_entries, cce, cce_link); + ccb->ccb_num_entries--; + uma_zfree(V_tcp_fastopen_ccache.zone, cce); +} + +static int +sysctl_net_inet_tcp_fastopen_ccache_list(SYSCTL_HANDLER_ARGS) +{ + struct sbuf sb; + struct tcp_fastopen_ccache_bucket *ccb; + struct tcp_fastopen_ccache_entry *cce; + sbintime_t now, duration, limit; + const int linesize = 128; + int i, error, num_entries; + unsigned int j; +#ifdef INET6 + char clt_buf[INET6_ADDRSTRLEN], srv_buf[INET6_ADDRSTRLEN]; +#else + char clt_buf[INET_ADDRSTRLEN], srv_buf[INET_ADDRSTRLEN]; +#endif + + if (jailed_without_vnet(curthread->td_ucred) != 0) + return (EPERM); + + /* Only allow root to read the client cookie cache */ + if (curthread->td_ucred->cr_uid != 0) + return (EPERM); + + num_entries = 0; + for (i = 0; i < V_tcp_fastopen_ccache.buckets; i++) { + ccb = &V_tcp_fastopen_ccache.base[i]; + CCB_LOCK(ccb); + if (ccb->ccb_num_entries > 0) + num_entries += ccb->ccb_num_entries; + CCB_UNLOCK(ccb); + } + sbuf_new(&sb, NULL, linesize * (num_entries + 1), SBUF_INCLUDENUL); + + sbuf_printf(&sb, + "\nLocal IP address Remote IP address Port MSS" + " Disabled Cookie\n"); + + now = getsbinuptime(); + limit = (sbintime_t)V_tcp_fastopen_path_disable_time << 32; + for (i = 0; i < V_tcp_fastopen_ccache.buckets; i++) { + ccb = &V_tcp_fastopen_ccache.base[i]; + CCB_LOCK(ccb); + TAILQ_FOREACH(cce, &ccb->ccb_entries, cce_link) { + if (cce->disable_time != 0) { + duration = now - cce->disable_time; + if (limit >= duration) + duration = limit - duration; + else + duration = 0; + } else + duration = 0; + sbuf_printf(&sb, + "%-20s %-20s %5u %5u ", + inet_ntop(cce->af, &cce->cce_client_ip, + clt_buf, sizeof(clt_buf)), + inet_ntop(cce->af, &cce->cce_server_ip, + srv_buf, sizeof(srv_buf)), + ntohs(cce->server_port), + cce->server_mss); + if (duration > 0) + sbuf_printf(&sb, "%7ds ", sbintime_getsec(duration)); + else + sbuf_printf(&sb, "%8s ", "No"); + for (j = 0; j < cce->cookie_len; j++) + sbuf_printf(&sb, "%02x", cce->cookie[j]); + sbuf_putc(&sb, '\n'); + } + CCB_UNLOCK(ccb); + } + error = sbuf_finish(&sb); + if (error == 0) + error = SYSCTL_OUT(req, sbuf_data(&sb), sbuf_len(&sb)); + sbuf_delete(&sb); + return (error); +} + +#endif \ No newline at end of file diff --git a/third_party/tcplp/bsdtcp/tcp_fastopen.h b/third_party/tcplp/bsdtcp/tcp_fastopen.h new file mode 100644 index 000000000..9905cbbd8 --- /dev/null +++ b/third_party/tcplp/bsdtcp/tcp_fastopen.h @@ -0,0 +1,111 @@ +/*- + * Copyright (c) 2015-2017 Patrick Kelsey + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + * + * $FreeBSD$ + */ + +#ifndef TCPLP_TCP_FASTOPEN_H_ +#define TCPLP_TCP_FASTOPEN_H_ + +// #include "opt_inet.h" +#include "tcp_const.h" +#include "tcp_var.h" + +#define TCP_FASTOPEN_COOKIE_LEN 8 /* SipHash24 64-bit output */ + +#if 0 + +#ifdef TCP_RFC7413 +VNET_DECLARE(unsigned int, tcp_fastopen_client_enable); +#define V_tcp_fastopen_client_enable VNET(tcp_fastopen_client_enable) + +VNET_DECLARE(unsigned int, tcp_fastopen_server_enable); +#define V_tcp_fastopen_server_enable VNET(tcp_fastopen_server_enable) +#else +#define V_tcp_fastopen_client_enable 0 +#define V_tcp_fastopen_server_enable 0 +#endif /* TCP_RFC7413 */ + +union tcp_fastopen_ip_addr { + struct in_addr v4; + struct in6_addr v6; +}; + +struct tcp_fastopen_ccache_entry { + TAILQ_ENTRY(tcp_fastopen_ccache_entry) cce_link; + union tcp_fastopen_ip_addr cce_client_ip; /* network byte order */ + union tcp_fastopen_ip_addr cce_server_ip; /* network byte order */ + uint16_t server_port; /* network byte order */ + uint16_t server_mss; /* host byte order */ + uint8_t af; + uint8_t cookie_len; + uint8_t cookie[TCP_FASTOPEN_MAX_COOKIE_LEN]; + sbintime_t disable_time; /* non-zero value means path is disabled */ +}; + +struct tcp_fastopen_ccache; + +struct tcp_fastopen_ccache_bucket { + struct mtx ccb_mtx; + TAILQ_HEAD(bucket_entries, tcp_fastopen_ccache_entry) ccb_entries; + int ccb_num_entries; + struct tcp_fastopen_ccache *ccb_ccache; +}; + +struct tcp_fastopen_ccache { + uma_zone_t zone; + struct tcp_fastopen_ccache_bucket *base; + unsigned int bucket_limit; + unsigned int buckets; + unsigned int mask; + uint32_t secret; +}; + +#endif + +#ifdef TCP_RFC7413 +void tcp_fastopen_init(void); +void tcp_fastopen_destroy(void); +unsigned int *tcp_fastopen_alloc_counter(void); +void tcp_fastopen_decrement_counter(unsigned int *); +/* samkumar: changed type of first argument from "struct in_conninfo *"" to "struct tcpcb*"". */ +int tcp_fastopen_check_cookie(struct tcpcb*, uint8_t *, unsigned int, + uint64_t *); +void tcp_fastopen_connect(struct tcpcb *); +void tcp_fastopen_disable_path(struct tcpcb *); +void tcp_fastopen_update_cache(struct tcpcb *, uint16_t, uint8_t, + uint8_t *); +#else +#define tcp_fastopen_init() ((void)0) +#define tcp_fastopen_destroy() ((void)0) +#define tcp_fastopen_alloc_counter() NULL +#define tcp_fastopen_decrement_counter(c) ((void)0) +#define tcp_fastopen_check_cookie(i, c, l, lc) (-1) +#define tcp_fastopen_connect(t) ((void)0) +#define tcp_fastopen_disable_path(t) ((void)0) +#define tcp_fastopen_update_cache(t, m, l, c) ((void)0) +#endif /* TCP_RFC7413 */ + +#endif /* _TCP_FASTOPEN_H_ */ diff --git a/third_party/tcplp/bsdtcp/tcp_input.c b/third_party/tcplp/bsdtcp/tcp_input.c index d79f2d2f3..e4f2b217f 100644 --- a/third_party/tcplp/bsdtcp/tcp_input.c +++ b/third_party/tcplp/bsdtcp/tcp_input.c @@ -81,6 +81,7 @@ #include "tcp_seq.h" #include "tcp_timer.h" #include "tcp_var.h" +#include "tcp_fastopen.h" #include "../lib/bitmap.h" #include "../lib/cbuf.h" #include "icmp_var.h" @@ -640,6 +641,10 @@ tcp_input(struct ip6_hdr* ip6, struct tcphdr* th, otMessage* msg, struct tcpcb* */ if (/*so->so_options & SO_ACCEPTCONN*/tp == NULL) { + int tfo_cookie_valid = 0; + uint64_t tfo_response_cookie; + // int tfo_response_cookie_valid = 0; + /* samkumar: NULL check isn't needed but prevents a compiler warning */ KASSERT(tpl != NULL && tpl->t_state == TCP6S_LISTEN, ("listen socket must be in listening state!")); @@ -768,8 +773,43 @@ tcp_input(struct ip6_hdr* ip6, struct tcphdr* th, otMessage* msg, struct tcpcb* * syncache, so we initialize the new socket right away. The code to * initialize the socket is taken from the syncache_socket function. */ - + /* + * samkumar: As of FreeBSD 10.3, the syncache_add function returns + * a flag indicating if a "fast open" code path should be taken. + * In that case, there is a "goto" statement to the removed logic + * above that calls tcp_do_segment after expanding a syncache entry. + * Analogous logic is implemented below. + */ tcp_dooptions(&to, optp, optlen, TO_SYN); + + /* + * samkumar: TCP Fast Open logic taken from syncache_add in + * FreeBSD 12.0. + */ + if (V_tcp_fastopen_server_enable && /*IS_FASTOPEN(tp->t_flags) && + (tp->t_tfo_pending != NULL) && */ + (to.to_flags & TOF_FASTOPEN)) { + /* + * Limit the number of pending TFO connections to + * approximately half of the queue limit. This prevents TFO + * SYN floods from starving the service by filling the + * listen queue with bogus TFO connections. + */ + /* + * samkumar: Since we let the application handle the listen + * queue it doesn't make sense to limit the number of pending + * TFO connections as above. Long term, I think the best fix + * is to let applications know if an incoming connection is + * TFO, so that they can handle the case appropriately (e.g., + * by disabling TFO or by declining the connection). + */ + int result = tcp_fastopen_check_cookie(NULL, + to.to_tfo_cookie, to.to_tfo_len, + &tfo_response_cookie); + tfo_cookie_valid = (result > 0); + // tfo_response_cookie_valid = (result >= 0); + } + tp = tcplp_sys_accept_ready(tpl, &ip6->ip6_src, th->th_sport); // Try to allocate an active socket to accept into if (tp == NULL) { /* If we couldn't allocate, just ignore the SYN. */ @@ -780,14 +820,23 @@ tcp_input(struct ip6_hdr* ip6, struct tcphdr* th, otMessage* msg, struct tcpcb* tp = NULL; goto dropwithreset; } + sig->accepted_connection = tp; tcp_state_change(tp, TCPS_SYN_RECEIVED); tpmarkpassiveopen(tp); - tp->t_flags |= TF_ACKNOW; // samkumar: my addition tp->iss = tcp_new_isn(tp); tp->irs = th->th_seq; tcp_rcvseqinit(tp); tcp_sendseqinit(tp); tp->snd_wl1 = th->th_seq; + /* + * samkumar: We remove the "+ 1"s below since we use + * tcp_output to send the appropriate SYN-ACK. For + * example, syncache_tfo_expand eliminates the "+ 1"s + * too. My understanding is that syncache_socket has + * the "+ 1"s because it's normally called once the + * SYN-ACK has already been ACKed, which is not how + * TCPlp operates. + */ tp->snd_max = tp->iss/* + 1*/; tp->snd_nxt = tp->iss/* + 1*/; tp->rcv_up = th->th_seq + 1; @@ -890,6 +939,26 @@ tcp_input(struct ip6_hdr* ip6, struct tcphdr* th, otMessage* msg, struct tcpcb* */ tcp_mss(tp, /*sc->sc_peer_mss*/(to.to_flags & TOF_MSS) ? to.to_mss : 0); + if (tfo_cookie_valid) { + /* + * samkumar: The code below is taken from syncache_tfo_socket. + * It calls syncache_socket (upon which the above code is based) + * so it makes sense for this logic to go here. + */ + tp->t_flags |= TF_FASTOPEN; + tp->t_tfo_cookie.server = tfo_response_cookie; + tp->snd_max = tp->iss; + tp->snd_nxt = tp->iss; + // tp->tfo_pending = pending_counter; + /* This would normally "goto" labeled code that calls tcp_do_segment. */ + tcp_do_segment(ip6, th, msg, tp, drop_hdrlen, tlen, iptos, sig); + + tp->accepted_from = tpl; + return (IPPROTO_DONE); + } else { + tp->t_flags |= TF_ACKNOW; // samkumar: my addition + } + tcp_output(tp); // to send the SYN-ACK tp->accepted_from = tpl; @@ -964,6 +1033,7 @@ tcp_do_segment(struct ip6_hdr* ip6, struct tcphdr *th, otMessage* msg, int rstreason, todrop, win; uint64_t tiwin; struct tcpopt to; + int tfo_syn; uint32_t ticks = tcplp_sys_get_ticks(); otInstance* instance = tp->instance; thflags = th->th_flags; @@ -1093,6 +1163,29 @@ tcp_do_segment(struct ip6_hdr* ip6, struct tcphdr *th, otMessage* msg, if ((tp->t_flags & TF_SACK_PERMIT) && (to.to_flags & TOF_SACKPERM) == 0) tp->t_flags &= ~TF_SACK_PERMIT; + /* + * samkumar: TCP Fast Open logic from FreeBSD 12.0. + */ + if (IS_FASTOPEN(tp->t_flags)) { + if (to.to_flags & TOF_FASTOPEN) { + uint16_t mss; + + if (to.to_flags & TOF_MSS) + mss = to.to_mss; + else + /* + * samkumar: The original code here would set + * mss to either TCP6_MSS or TCP_MSS depending + * on whether the INP_IPV6 flag is present in + * tp->t_inpcb->inp_vflag. In TCPlp, we always + * assume IPv6. + */ + mss = TCP6_MSS; + tcp_fastopen_update_cache(tp, mss, + to.to_tfo_len, to.to_tfo_cookie); + } else + tcp_fastopen_disable_path(tp); + } } /* * Header prediction: check for the two common cases @@ -1403,9 +1496,32 @@ tcp_do_segment(struct ip6_hdr* ip6, struct tcphdr *th, otMessage* msg, SEQ_GT(th->th_ack, tp->snd_max))) { rstreason = BANDLIM_RST_OPENPORT; goto dropwithreset; - } else if ((thflags & TH_SYN) && !(thflags & TH_ACK) && (th->th_seq == tp->irs)) { + } else if (!IS_FASTOPEN(tp->t_flags) && (thflags & TH_SYN) && !(thflags & TH_ACK) && (th->th_seq == tp->irs)) { tp->t_flags |= TF_ACKNOW; } + /* + * samkumar: TCP Fast Open Logic from FreeBSD 12.0. + */ + if (IS_FASTOPEN(tp->t_flags)) { + /* + * When a TFO connection is in SYN_RECEIVED, the + * only valid packets are the initial SYN, a + * retransmit/copy of the initial SYN (possibly with + * a subset of the original data), a valid ACK, a + * FIN, or a RST. + */ + if ((thflags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) { + rstreason = BANDLIM_RST_OPENPORT; + goto dropwithreset; + } else if (thflags & TH_SYN) { + /* non-initial SYN is ignored */ + if ((tcp_timer_active(tp, TT_DELACK) || + tcp_timer_active(tp, TT_REXMT))) + goto drop; + } else if (!(thflags & (TH_ACK|TH_FIN|TH_RST))) { + goto drop; + } + } break; /* @@ -1440,6 +1556,8 @@ tcp_do_segment(struct ip6_hdr* ip6, struct tcphdr *th, otMessage* msg, tp->irs = th->th_seq; tcp_rcvseqinit(tp); if (thflags & TH_ACK) { + int tfo_partial_ack = 0; + /* * samkumar: Removed call to soisconnected(so), since TCPlp has its * own buffering. @@ -1453,11 +1571,20 @@ tcp_do_segment(struct ip6_hdr* ip6, struct tcphdr *th, otMessage* msg, tp->rcv_adv += imin(tp->rcv_wnd, TCP_MAXWIN << tp->rcv_scale); tp->snd_una++; /* SYN is acked */ + /* + * If not all the data that was sent in the TFO SYN + * has been acked, resend the remainder right away. + */ + if (IS_FASTOPEN(tp->t_flags) && + (tp->snd_una != tp->snd_max)) { + tp->snd_nxt = th->th_ack; + tfo_partial_ack = 1; + } /* * If there's data, delay ACK; if there's also a FIN * ACKNOW will be turned on later. */ - if (DELAY_ACK(tp, tlen) && tlen != 0) + if (DELAY_ACK(tp, tlen) && tlen != 0 && !tfo_partial_ack) tcp_timer_activate(tp, TT_DELACK, tcp_delacktime); else @@ -1802,9 +1929,14 @@ tcp_do_segment(struct ip6_hdr* ip6, struct tcphdr *th, otMessage* msg, */ if ((thflags & TH_ACK) == 0) { if (tp->t_state == TCPS_SYN_RECEIVED || - (tp->t_flags & TF_NEEDSYN)) + (tp->t_flags & TF_NEEDSYN)) { + if (tp->t_state == TCPS_SYN_RECEIVED && + IS_FASTOPEN(tp->t_flags)) { + tp->snd_wnd = tiwin; + cc_conn_init(tp); + } goto step6; - else if (tp->t_flags & TF_ACKNOW) + } else if (tp->t_flags & TF_ACKNOW) goto dropafterack; else goto drop; @@ -1839,6 +1971,21 @@ tcp_do_segment(struct ip6_hdr* ip6, struct tcphdr *th, otMessage* msg, * SYN-RECEIVED* -> FIN-WAIT-1 */ tp->t_starttime = ticks; + /* + * samkumar: I'm eliminating the TFO pending counter. + */ + if (IS_FASTOPEN(tp->t_flags)/* && tp->t_tfo_pending */) {\ + /* + tcp_fastopen_decrement_counter(tp->t_tfo_pending); + tp->t_tfo_pending = NULL; + */ + + /* + * Account for the ACK of our SYN prior to + * regular ACK processing below. + */ + tp->snd_una++; + } if (tp->t_flags & TF_NEEDFIN) { tcp_state_change(tp, TCPS_FIN_WAIT_1); tp->t_flags &= ~TF_NEEDFIN; @@ -1846,7 +1993,15 @@ tcp_do_segment(struct ip6_hdr* ip6, struct tcphdr *th, otMessage* msg, tcp_state_change(tp, TCPS_ESTABLISHED); /* samkumar: Set conn_established signal for TCPlp. */ sig->conn_established = true; - cc_conn_init(tp); + /* + * TFO connections call cc_conn_init() during SYN + * processing. Calling it again here for such + * connections is not harmless as it would undo the + * snd_cwnd reduction that occurs when a TFO SYN|ACK + * is retransmitted. + */ + if (!IS_FASTOPEN(tp->t_flags)) + cc_conn_init(tp); tcp_timer_activate(tp, TT_KEEP, TP_KEEPIDLE(tp)); /* * samkumar: I added this check to account for simultaneous open. @@ -2383,7 +2538,9 @@ step6: * case PRU_RCVD). If a FIN has already been received on this * connection then we just ignore the text. */ - if ((tlen || (thflags & TH_FIN)) && + tfo_syn = ((tp->t_state == TCPS_SYN_RECEIVED) && + IS_FASTOPEN(tp->t_flags)); + if ((tlen || (thflags & TH_FIN) || tfo_syn) && TCPS_HAVERCVDFIN(tp->t_state) == 0) { tcp_seq save_start = th->th_seq; /* @@ -2411,8 +2568,9 @@ step6: */ if (th->th_seq == tp->rcv_nxt && (tpiscantrcv(tp) || bmp_isempty(tp->reassbmp, REASSBMP_SIZE(tp))) && - TCPS_HAVEESTABLISHED(tp->t_state)) { - if (DELAY_ACK(tp, tlen)) + (TCPS_HAVEESTABLISHED(tp->t_state) || + tfo_syn)) { + if (DELAY_ACK(tp, tlen) || tfo_syn) tp->t_flags |= TF_DELACK; else tp->t_flags |= TF_ACKNOW; @@ -2694,6 +2852,21 @@ tcp_dooptions(struct tcpopt *to, uint8_t *cp, int cnt, int flags) to->to_nsacks = (optlen - 2) / TCPOLEN_SACK; to->to_sacks = cp + 2; break; + case TCPOPT_FAST_OPEN: + /* + * Cookie length validation is performed by the + * server side cookie checking code or the client + * side cookie cache update code. + */ + if (!(flags & TO_SYN)) + continue; + if (!V_tcp_fastopen_client_enable && + !V_tcp_fastopen_server_enable) + continue; + to->to_flags |= TOF_FASTOPEN; + to->to_tfo_len = optlen - 2; + to->to_tfo_cookie = to->to_tfo_len ? cp + 2 : NULL; + break; default: continue; } diff --git a/third_party/tcplp/bsdtcp/tcp_output.c b/third_party/tcplp/bsdtcp/tcp_output.c index 0e4f2fd77..0343f8cf6 100644 --- a/third_party/tcplp/bsdtcp/tcp_output.c +++ b/third_party/tcplp/bsdtcp/tcp_output.c @@ -34,6 +34,7 @@ #include "../tcplp.h" #include "tcp.h" +#include "tcp_fastopen.h" #include "tcp_fsm.h" #include "tcp_var.h" #include "tcp_seq.h" @@ -118,11 +119,25 @@ tcp_output(struct tcpcb *tp) struct sackhole* p; unsigned ipoptlen, optlen, hdrlen; struct tcpopt to; + unsigned int wanted_cookie = 0; + unsigned int dont_sendalot = 0; uint8_t opt[TCP_MAXOLEN]; uint32_t ticks = tcplp_sys_get_ticks(); /* samkumar: Code for TCP offload has been removed. */ + /* + * For TFO connections in SYN_SENT or SYN_RECEIVED, + * only allow the initial SYN or SYN|ACK and those sent + * by the retransmit timer. + */ + if (IS_FASTOPEN(tp->t_flags) && + ((tp->t_state == TCPS_SYN_SENT) || + (tp->t_state == TCPS_SYN_RECEIVED)) && + SEQ_GT(tp->snd_max, tp->snd_una) && /* initial SYN or SYN|ACK sent */ + (tp->snd_nxt != tp->snd_una)) /* not a retransmit */ + return (0); + /* * Determine length of data that should be transmitted, * and flags that will be used. @@ -323,6 +338,13 @@ after_sack_rexmit: if ((flags & TH_SYN) && SEQ_GT(tp->snd_nxt, tp->snd_una)) { if (tp->t_state != TCPS_SYN_RECEIVED) flags &= ~TH_SYN; + /* + * When sending additional segments following a TFO SYN|ACK, + * do not include the SYN bit. + */ + if (IS_FASTOPEN(tp->t_flags) && + (tp->t_state == TCPS_SYN_RECEIVED)) + flags &= ~TH_SYN; off--, len++; } @@ -336,6 +358,29 @@ after_sack_rexmit: flags &= ~TH_FIN; } + /* + * On TFO sockets, ensure no data is sent in the following cases: + * + * - When retransmitting SYN|ACK on a passively-created socket + * + * - When retransmitting SYN on an actively created socket + * + * - When sending a zero-length cookie (cookie request) on an + * actively created socket + * + * - When the socket is in the CLOSED state (RST is being sent) + */ + /* + * samkumar: I commented out the check to ensure no data is sent + * on a TFO cookie request. As far as I am aware, this is still + * compliant with the RFC. + */ + if (IS_FASTOPEN(tp->t_flags) && + (((flags & TH_SYN) && (tp->t_rxtshift > 0)) || + /*((tp->t_state == TCPS_SYN_SENT) && + (tp->t_tfo_client_cookie_len == 0)) ||*/ + (flags & TH_RST))) + len = 0; if (len <= 0) { /* * If FIN has been sent but not acked, @@ -675,16 +720,52 @@ send: * We only have to care about SYN and established connection * segments. Options for SYN-ACK segments are handled in TCP * syncache. - * Sam: I've done away with the syncache. However, it seems that - * the existing logic works fine for SYN-ACK as well */ + /* + * samkumar: I've done away with the syncache. However, it + * seems that the existing logic works fine for SYN-ACK as + * well. + */ + to.to_flags = 0; if ((tp->t_flags & TF_NOOPT) == 0) { - to.to_flags = 0; /* Maximum segment size. */ if (flags & TH_SYN) { tp->snd_nxt = tp->iss; to.to_mss = tcp_mssopt(tp); to.to_flags |= TOF_MSS; + + /* + * On SYN or SYN|ACK transmits on TFO connections, + * only include the TFO option if it is not a + * retransmit, as the presence of the TFO option may + * have caused the original SYN or SYN|ACK to have + * been dropped by a middlebox. + */ + if (IS_FASTOPEN(tp->t_flags) && + (tp->t_rxtshift == 0)) { + if (tp->t_state == TCPS_SYN_RECEIVED) { + to.to_tfo_len = TCP_FASTOPEN_COOKIE_LEN; + to.to_tfo_cookie = + (u_int8_t *)&tp->t_tfo_cookie.server; + to.to_flags |= TOF_FASTOPEN; + wanted_cookie = 1; + } else if (tp->t_state == TCPS_SYN_SENT) { + to.to_tfo_len = + tp->t_tfo_client_cookie_len; + to.to_tfo_cookie = + tp->t_tfo_cookie.client; + to.to_flags |= TOF_FASTOPEN; + wanted_cookie = 1; + /* + * If we wind up having more data to + * send with the SYN than can fit in + * one segment, don't send any more + * until the SYN|ACK comes back from + * the other end. + */ + dont_sendalot = 1; + } + } } /* Window scaling. */ if ((flags & TH_SYN) && (tp->t_flags & TF_REQ_SCALE)) { @@ -724,6 +805,13 @@ send: /* Processing the options. */ hdrlen += optlen = tcp_addoptions(&to, opt); + /* + * If we wanted a TFO option to be added, but it was unable + * to fit, ensure no data is sent. + */ + if (IS_FASTOPEN(tp->t_flags) && wanted_cookie && + !(to.to_flags & TOF_FASTOPEN)) + len = 0; } /* * samkumar: This used to be set to ip6_optlen(tp->t_inpcb), instead of 0, @@ -746,6 +834,8 @@ send: */ len = tp->t_maxopd - optlen - ipoptlen; sendalot = 1; + if (dont_sendalot) + sendalot = 0; } /* * samkumar: The else case of the above "if" statement would set tso to 0. @@ -1453,6 +1543,25 @@ tcp_addoptions(struct tcpopt *to, uint8_t *optp) /* samkumar: Removed TCPSTAT_INC(tcps_sack_send_blocks); */ break; } + case TOF_FASTOPEN: + { + int total_len; + + /* XXX is there any point to aligning this option? */ + total_len = TCPOLEN_FAST_OPEN_EMPTY + to->to_tfo_len; + if (TCP_MAXOLEN - optlen < total_len) { + to->to_flags &= ~TOF_FASTOPEN; + continue; + } + *optp++ = TCPOPT_FAST_OPEN; + *optp++ = total_len; + if (to->to_tfo_len > 0) { + bcopy(to->to_tfo_cookie, optp, to->to_tfo_len); + optp += to->to_tfo_len; + } + optlen += total_len; + break; + } default: tcplp_sys_panic("PANIC: %s: unknown TCP option type", __func__); break; diff --git a/third_party/tcplp/bsdtcp/tcp_subr.c b/third_party/tcplp/bsdtcp/tcp_subr.c index 4dc58b06c..b89f92e38 100644 --- a/third_party/tcplp/bsdtcp/tcp_subr.c +++ b/third_party/tcplp/bsdtcp/tcp_subr.c @@ -45,6 +45,7 @@ #include "../lib/bitmap.h" #include "../lib/cbuf.h" #include "cc.h" +#include "tcp_fastopen.h" #include "tcp_const.h" @@ -60,6 +61,7 @@ tcp_seq tcp_new_isn(struct tcpcb* tp) { * samkumar: There used to be a function, void tcp_init(void), that would * initialize global state for TCP, including a hash table to store TCBs, * allocating memory zones for sockets, and setting global configurable state. + * In FreeBSD 12.0, it also makes a call to the function tcp_fastopen_init. * None of that is needed for TCPlp: TCB allocation and matching is done by * the host system and global configurable state is removed with hardcoded * values in order to save memory, for example. Thus, I've removed the function @@ -179,6 +181,13 @@ tcp_discardcb(struct tcpcb *tp) struct tcpcb * tcp_close(struct tcpcb *tp) { + /* samkumar: Eliminate the TFO pending counter. */ + /* + if (tp->t_tfo_pending) { + tcp_fastopen_decrement_counter(tp->t_tfo_pending); + tp->t_tfo_pending = NULL; + } + */ tcp_state_change(tp, TCP6S_CLOSED); // for the print statement tcp_discardcb(tp); // Don't reset the TCB by calling initialize_tcb, since that overwrites the buffer contents. @@ -190,7 +199,8 @@ tcp_close(struct tcpcb *tp) * Allocates an mbuf and fills in a skeletal tcp/ip header. The only * use for this function is in keepalives, which use tcp_respond. */ -/* samkumar: I changed the signature of this function. Instead of allocating +/* + * samkumar: I changed the signature of this function. Instead of allocating * the struct tcptemp using malloc, populating it, and then returning it, I * have the caller allocate it. This function merely populates it now. */ diff --git a/third_party/tcplp/bsdtcp/tcp_usrreq.c b/third_party/tcplp/bsdtcp/tcp_usrreq.c index 3afb2013b..7371a6aa8 100644 --- a/third_party/tcplp/bsdtcp/tcp_usrreq.c +++ b/third_party/tcplp/bsdtcp/tcp_usrreq.c @@ -44,7 +44,7 @@ #include "tcp_seq.h" #include "tcp_var.h" #include "tcp_timer.h" -//#include +#include "tcp_fastopen.h" #include "ip6.h" #include "tcp_const.h" @@ -251,14 +251,15 @@ out: * to extend the final linked buffer of the send buffer. Either DATA should be * NULL, or EXTENDBY should be 0. */ -int tcp_usr_send(struct tcpcb* tp, int moretocome, otLinkedBuffer* data, size_t extendby) +int tcp_usr_send(struct tcpcb* tp, int moretocome, otLinkedBuffer* data, size_t extendby, struct sockaddr_in6* nam) { int error = 0; + int do_fastopen_implied_connect = (nam != NULL) && IS_FASTOPEN(tp->t_flags) && tp->t_state < TCPS_SYN_SENT; /* * samkumar: This if statement and the next are checks that I added */ - if (tp->t_state < TCPS_ESTABLISHED) { + if (tp->t_state < TCPS_ESTABLISHED && !IS_FASTOPEN(tp->t_flags)) { error = ENOTCONN; goto out; } @@ -273,9 +274,9 @@ int tcp_usr_send(struct tcpcb* tp, int moretocome, otLinkedBuffer* data, size_t * INP_TIMEWAIT and INP_DROPPED flags on inp->inp_flags, and handled the * control mbuf passed as an argument (which would result in an error since * TCP doesn't support control information). I've deleted that code, but - * left the following if block. + * added the following if block based on those checks. */ - if ((tp->t_state == TCPS_TIME_WAIT) || (tp->t_state == TCPS_CLOSED)) { + if ((tp->t_state == TCPS_TIME_WAIT) || (tp->t_state == TCPS_CLOSED && !do_fastopen_implied_connect)) { error = ECONNRESET; goto out; } @@ -283,13 +284,13 @@ int tcp_usr_send(struct tcpcb* tp, int moretocome, otLinkedBuffer* data, size_t /* * The following code used to be wrapped in an if statement: * "if (!(flags & PRUS_OOB))", that only executed it if the "out of band" - * flag was not set. In TCB, "out of band" data is conveyed via the urgent + * flag was not set. In TCP, "out of band" data is conveyed via the urgent * pointer, and TCPlp does not support the urgent pointer. Therefore, I * removed the "if" check and put its body below. */ /* - * samkumar; The FreeBSD code calls sbappendstream(&so->so_snd, m, flags); + * samkumar: The FreeBSD code calls sbappendstream(&so->so_snd, m, flags); * I've replaced it with the following logic, which appends to the * send buffer according to TCPlp's data structures. */ @@ -308,9 +309,16 @@ int tcp_usr_send(struct tcpcb* tp, int moretocome, otLinkedBuffer* data, size_t /* * samkumar: There used to be code here to handle "implied connect," * which initiates the TCP handshake if sending data on a socket that - * isn't yet connected. TCPlp doesn't support this at the moment, but - * it might be worth revisiting when implementing TCP Fast Open. + * isn't yet connected. For now, I've special-cased this code to work + * only for TCP Fast Open for IPv6 (since implied connect is the only + * way to initiate a connection with TCP Fast Open). */ + if (do_fastopen_implied_connect) { + error = tcp6_connect(tp, nam); + if (error) + goto out; + tcp_fastopen_connect(tp); + } /* * samkumar: There used to be code here handling the PRUS_EOF flag in @@ -362,6 +370,17 @@ tcp_usr_rcvd(struct tcpcb* tp) goto out; } + /* + * For passively-created TFO connections, don't attempt a window + * update while still in SYN_RECEIVED as this may trigger an early + * SYN|ACK. It is preferable to have the SYN|ACK be sent along with + * application response data, or failing that, when the DELACK timer + * expires. + */ + if (IS_FASTOPEN(tp->t_flags) && + (tp->t_state == TCPS_SYN_RECEIVED)) + goto out; + tcp_output(tp); out: diff --git a/third_party/tcplp/bsdtcp/tcp_var.h b/third_party/tcplp/bsdtcp/tcp_var.h index c21b827a7..0aa2d3b3c 100644 --- a/third_party/tcplp/bsdtcp/tcp_var.h +++ b/third_party/tcplp/bsdtcp/tcp_var.h @@ -50,6 +50,8 @@ #include "types.h" #include "ip6.h" +#define TCP_RFC7413 + /* Implement byte-order-specific functions using OpenThread. */ uint16_t tcplp_sys_hostswap16(uint16_t hostport); uint32_t tcplp_sys_hostswap32(uint32_t hostport); @@ -351,6 +353,14 @@ struct tcpcb { // uint32_t t_ispare[8]; /* 5 UTO, 3 TBD */ // void *t_pspare2[4]; /* 1 TCP_SIGNATURE, 3 TBD */ + + /* samkumar: TFO fields from FreeBSD 12.0. */ + uint64_t t_tfo_client_cookie_len; /* TCP Fast Open client cookie length */ +// unsigned int *t_tfo_pending; /* TCP Fast Open server pending counter */ + union { + uint8_t client[TCP_FASTOPEN_MAX_COOKIE_LEN]; + uint64_t server; + } t_tfo_cookie; /* TCP Fast Open cookie to send */ #if 0 #if defined(_KERNEL) && defined(TCPPCAP) struct mbufq t_inpkts; /* List of saved input packets. */ @@ -384,7 +394,7 @@ void cc_cong_signal(struct tcpcb *tp, struct tcphdr *th, uint32_t type); /* Added, since there is no header file for tcp_usrreq.c. */ int tcp6_usr_connect(struct tcpcb* tp, struct sockaddr_in6* sinp6); -int tcp_usr_send(struct tcpcb* tp, int moretocome, struct otLinkedBuffer* data, size_t extendby); +int tcp_usr_send(struct tcpcb* tp, int moretocome, struct otLinkedBuffer* data, size_t extendby, struct sockaddr_in6* nam); int tcp_usr_rcvd(struct tcpcb* tp); int tcp_usr_shutdown(struct tcpcb* tp); void tcp_usr_abort(struct tcpcb* tp); @@ -421,6 +431,7 @@ void tcp_usr_abort(struct tcpcb* tp); #define TF_ECN_SND_ECE 0x10000000 /* ECN ECE in queue */ #define TF_CONGRECOVERY 0x20000000 /* congestion recovery mode */ #define TF_WASCRECOVERY 0x40000000 /* was in congestion recovery */ +#define TF_FASTOPEN 0x80000000 /* TCP Fast Open indication */ #define IN_FASTRECOVERY(t_flags) (t_flags & TF_FASTRECOVERY) #define ENTER_FASTRECOVERY(t_flags) t_flags |= TF_FASTRECOVERY @@ -434,6 +445,12 @@ void tcp_usr_abort(struct tcpcb* tp); #define ENTER_RECOVERY(t_flags) t_flags |= (TF_CONGRECOVERY | TF_FASTRECOVERY) #define EXIT_RECOVERY(t_flags) t_flags &= ~(TF_CONGRECOVERY | TF_FASTRECOVERY) +#ifndef TCP_RFC7413 +#define IS_FASTOPEN(t_flags) (false) +#else +#define IS_FASTOPEN(t_flags) (t_flags & TF_FASTOPEN) +#endif + #define BYTES_THIS_ACK(tp, th) (th->th_ack - tp->snd_una) /* @@ -480,14 +497,17 @@ struct tcpopt { #define TOF_TS 0x0010 /* timestamp */ #define TOF_SIGNATURE 0x0040 /* TCP-MD5 signature option (RFC2385) */ #define TOF_SACK 0x0080 /* Peer sent SACK option */ -#define TOF_MAXOPT 0x0100 +#define TOF_FASTOPEN 0x0100 /* TCP Fast Open (TFO) cookie */ +#define TOF_MAXOPT 0x0200 u_int32_t to_tsval; /* new timestamp */ u_int32_t to_tsecr; /* reflected timestamp */ uint8_t *to_sacks; /* pointer to the first SACK blocks */ uint8_t *to_signature; /* pointer to the TCP-MD5 signature */ + u_int8_t *to_tfo_cookie; /* pointer to the TFO cookie */ u_int16_t to_mss; /* maximum segment size */ u_int8_t to_wscale; /* window scaling */ u_int8_t to_nsacks; /* number of SACK blocks */ + u_int8_t to_tfo_len; /* TFO cookie length */ u_int32_t to_spare; /* UTO */ }; diff --git a/third_party/tcplp/tcplp.h b/third_party/tcplp/tcplp.h index 426963526..d45ee3589 100644 --- a/third_party/tcplp/tcplp.h +++ b/third_party/tcplp/tcplp.h @@ -55,11 +55,12 @@ extern "C" { struct tcplp_signals { - uint32_t links_popped; - uint32_t bytes_acked; - bool conn_established; - bool recvbuf_added; - bool rcvd_fin; + struct tcpcb* accepted_connection; + uint32_t links_popped; + uint32_t bytes_acked; + bool conn_established; + bool recvbuf_added; + bool rcvd_fin; }; /*