diff --git a/cmake/AmiNetXDuoVersion.cmake b/cmake/AmiNetXDuoVersion.cmake index 94343e837..fb23ecde7 100644 --- a/cmake/AmiNetXDuoVersion.cmake +++ b/cmake/AmiNetXDuoVersion.cmake @@ -28,7 +28,7 @@ # What third_party/ is expected to contain. These are the ONLY hardcoded # upstream version numbers in the tree. Bumping a submodule means editing the # matching line here, in the same commit, on purpose. -set(AMINETXDUO_NETXDUO_VERSION_PIN "6.5.1") +set(AMINETXDUO_NETXDUO_VERSION_PIN "6.5.2") set(AMINETXDUO_THREADX_VERSION_PIN "6.5.2") # --------------------------------------------------------------- reading ---- diff --git a/src/tls/CMakeLists.txt b/src/tls/CMakeLists.txt index 28aad08fc..6be852dd7 100644 --- a/src/tls/CMakeLists.txt +++ b/src/tls/CMakeLists.txt @@ -78,17 +78,6 @@ list(APPEND NX_SECURE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/rfc7905/nx_secure_tls_record_payload_encrypt.c" "${CMAKE_CURRENT_SOURCE_DIR}/rfc7905/nx_secure_tls_record_payload_decrypt.c") -# RFC 7301, ALPN. nx_secure has none, which is why nothing built on it could -# negotiate HTTP/2: h2 over TLS is defined only over a negotiated "h2" -# (RFC 7540 3.2), never by assumption. This adds it rather than replacing -# anything, so it is an append and not an exclusion. It lives here and not in -# third_party/ so that a submodule bump is a merge of the seven one-line call -# sites in nx_secure and nothing else; the ProtocolNameList field in -# NX_SECURE_TLS_SESSION is the one thing that cannot live outside the vendored -# header. -list(APPEND NX_SECURE_SOURCES - "${CMAKE_CURRENT_SOURCE_DIR}/alpn/nx_secure_tls_alpn.c") - add_library(nx_secure STATIC ${NX_SECURE_SOURCES}) target_include_directories(nx_secure PUBLIC ${AMINETXDUO_TLS_INCLUDES}) target_link_libraries(nx_secure PUBLIC nx_crypto netxduo) diff --git a/src/tls/alpn/nx_secure_tls_alpn.c b/src/tls/alpn/nx_secure_tls_alpn.c deleted file mode 100644 index b467eeaf2..000000000 --- a/src/tls/alpn/nx_secure_tls_alpn.c +++ /dev/null @@ -1,403 +0,0 @@ -/* - * RFC 7301, Application-Layer Protocol Negotiation, for nx_secure. - * - * nx_secure has no ALPN of any kind, which is why nothing built on it can - * speak HTTP/2: h2 over TLS is defined only over a negotiated "h2", never by - * assumption (RFC 7540 3.2). This file is the whole of the mechanism. The - * seven call sites in third_party/netxduo/nx_secure are one line each, on - * purpose: it keeps a submodule bump a merge of seven single lines rather than - * of a feature. - * - * It is built into the nx_secure archive alongside the vendored sources, the - * same way src/tls/rfc7905/ supplies the two ChaCha20-Poly1305 record files. - * - * THE WIRE SHAPE, both directions: - * - * ext_type (2) | ext_length (2) | list_length (2) | ProtocolName... - * ProtocolName = length (1) | bytes - * - * The client sends its whole preference list. The server answers with a list - * of exactly one, which is the selection (RFC 7301 3.1); in TLS 1.2 that rides - * in the ServerHello and in TLS 1.3 in EncryptedExtensions (RFC 8446 4.2). - * - * A server with no ALPN list may ignore the offer. A configured server with - * no protocol in common MUST instead send fatal no_application_protocol per - * RFC 7301 3.2; likewise a client refuses an unoffered selection. - * - * SPDX-License-Identifier: MIT - */ - -#define NX_SECURE_SOURCE_CODE - -#include "nx_secure_tls.h" - -/* - * Walk a ProtocolNameList and answer whether it is well formed: a run of - * length-prefixed names that ends exactly on `length`, with no empty name - * (RFC 7301 3.1 makes ProtocolName opaque<1..2^8-1>). - */ -static UINT nx_secure_tls_alpn_list_valid(const UCHAR *list, UINT length) -{ -UINT offset = 0; - - if (list == NX_NULL) - { - return(NX_FALSE); - } - - while (offset < length) - { - UINT name_length = list[offset]; - - if (name_length == 0) - { - return(NX_FALSE); - } - - offset += 1u + name_length; - } - - return((offset == length) ? NX_TRUE : NX_FALSE); -} - -/* TRUE when `name` appears in the wire-encoded `list`. */ -static UINT nx_secure_tls_alpn_list_has(const UCHAR *list, UINT list_length, - const UCHAR *name, UINT name_length) -{ -UINT offset = 0; - - while ((offset + 1u + name_length) <= list_length) - { - UINT entry_length = list[offset]; - - if ((entry_length == name_length) && - (NX_SECURE_MEMCMP(&list[offset + 1], name, name_length) == 0)) - { - return(NX_TRUE); - } - - offset += 1u + entry_length; - } - - return(NX_FALSE); -} - -/**************************************************************************/ -/* _nx_secure_tls_alpn_protocol_set */ -/* */ -/* Offer `protocol_list`, the wire encoding of ProtocolNameList without */ -/* its outer length: "\x02h2\x08http/1.1". The caller keeps ownership */ -/* and the buffer must outlive the handshake. A NULL list clears the */ -/* offer, and the extension is then not sent at all. */ -/**************************************************************************/ -UINT _nx_secure_tls_alpn_protocol_set(NX_SECURE_TLS_SESSION *tls_session, - const UCHAR *protocol_list, - USHORT protocol_list_length) -{ -UINT offset; - - if (tls_session == NX_NULL) - { - return(NX_PTR_ERROR); - } - - if ((protocol_list == NX_NULL) || (protocol_list_length == 0)) - { - tls_session -> nx_secure_tls_alpn_protocol_list = NX_NULL; - tls_session -> nx_secure_tls_alpn_protocol_list_length = 0; - return(NX_SUCCESS); - } - - /* Checked here rather than on the wire: a malformed list written into a - ClientHello is a malformed ClientHello, and the server's answer to that - is a handshake failure with no explanation. */ - if (!nx_secure_tls_alpn_list_valid(protocol_list, protocol_list_length)) - { - return(NX_SECURE_TLS_INVALID_PACKET); - } - - /* Local selections have a fixed-size destination. Reject an offer we - could send but could never accept back from a conforming peer. Keep - the wire validator above RFC-sized: a remote name may be longer. */ - for (offset = 0; offset < protocol_list_length; - offset += 1u + protocol_list[offset]) - { - if (protocol_list[offset] > NX_SECURE_TLS_ALPN_PROTOCOL_MAX) - { - return(NX_SECURE_TLS_INVALID_PACKET); - } - } - - tls_session -> nx_secure_tls_alpn_protocol_list = protocol_list; - tls_session -> nx_secure_tls_alpn_protocol_list_length = protocol_list_length; - - return(NX_SUCCESS); -} - -/**************************************************************************/ -/* _nx_secure_tls_alpn_protocol_get */ -/* */ -/* What was negotiated, or NX_SECURE_TLS_EXTENSION_NOT_FOUND when the */ -/* peer did not answer. Not NUL terminated: ProtocolName is opaque. */ -/**************************************************************************/ -UINT _nx_secure_tls_alpn_protocol_get(NX_SECURE_TLS_SESSION *tls_session, - const UCHAR **protocol, - UCHAR *protocol_length) -{ - if ((tls_session == NX_NULL) || (protocol == NX_NULL) || - (protocol_length == NX_NULL)) - { - return(NX_PTR_ERROR); - } - - if (tls_session -> nx_secure_tls_alpn_selected_length == 0) - { - *protocol = NX_NULL; - *protocol_length = 0; - return(NX_SECURE_TLS_EXTENSION_NOT_FOUND); - } - - *protocol = tls_session -> nx_secure_tls_alpn_selected; - *protocol_length = tls_session -> nx_secure_tls_alpn_selected_length; - - return(NX_SUCCESS); -} - -/**************************************************************************/ -/* _nx_secure_tls_alpn_send_extension */ -/* */ -/* Write the extension at *packet_offset. On a client that is the whole */ -/* offer; on a server it is the one selected name, and nothing at all if */ -/* no name was selected. *extension_length is zero when nothing was */ -/* written, which is what the callers add to their running total. */ -/**************************************************************************/ -UINT _nx_secure_tls_alpn_send_extension(NX_SECURE_TLS_SESSION *tls_session, - UCHAR *packet_buffer, ULONG *packet_offset, - USHORT *extension_length, - ULONG available_size, UINT server) -{ -ULONG offset; -const UCHAR *list; -UINT list_length; -UCHAR one[NX_SECURE_TLS_ALPN_PROTOCOL_MAX + 1]; - - if ((tls_session == NX_NULL) || (packet_buffer == NX_NULL) || - (packet_offset == NX_NULL) || (extension_length == NX_NULL)) - { - return(NX_PTR_ERROR); - } - - *extension_length = 0; - - if (server) - { - if (tls_session -> nx_secure_tls_alpn_selected_length == 0) - { - - /* Nothing was selected, so nothing is answered. RFC 7301 3.2 - allows the server to stay silent, and the connection then has no - agreed application protocol. */ - return(NX_SUCCESS); - } - - one[0] = tls_session -> nx_secure_tls_alpn_selected_length; - NX_SECURE_MEMCPY(&one[1], tls_session -> nx_secure_tls_alpn_selected, - tls_session -> nx_secure_tls_alpn_selected_length); /* Use case of memcpy is verified. */ - - list = one; - list_length = 1u + tls_session -> nx_secure_tls_alpn_selected_length; - } - else - { - if (tls_session -> nx_secure_tls_alpn_protocol_list_length == 0) - { - return(NX_SUCCESS); - } - - list = tls_session -> nx_secure_tls_alpn_protocol_list; - list_length = tls_session -> nx_secure_tls_alpn_protocol_list_length; - } - - offset = *packet_offset; - - /* ext_type, ext_length, list_length, then the names. */ - if (available_size < (offset + 6u + list_length)) - { - return(NX_SECURE_TLS_PACKET_BUFFER_TOO_SMALL); - } - - packet_buffer[offset] = (UCHAR)((NX_SECURE_TLS_EXTENSION_ALPN & 0xFF00) >> 8); - packet_buffer[offset + 1] = (UCHAR)(NX_SECURE_TLS_EXTENSION_ALPN & 0x00FF); - offset += 2; - - packet_buffer[offset] = (UCHAR)(((list_length + 2u) & 0xFF00) >> 8); - packet_buffer[offset + 1] = (UCHAR)((list_length + 2u) & 0x00FF); - offset += 2; - - packet_buffer[offset] = (UCHAR)((list_length & 0xFF00) >> 8); - packet_buffer[offset + 1] = (UCHAR)(list_length & 0x00FF); - offset += 2; - - NX_SECURE_MEMCPY(&packet_buffer[offset], list, list_length); /* Use case of memcpy is verified. */ - offset += list_length; - - *extension_length = (USHORT)(offset - *packet_offset); - *packet_offset = offset; - - return(NX_SUCCESS); -} - -/**************************************************************************/ -/* _nx_secure_tls_alpn_process_response */ -/* */ -/* The client half. `packet_buffer` starts at the extension's own */ -/* two-byte length field and `message_length` is what remains of the */ -/* message, so this validates its own bounds. */ -/**************************************************************************/ -UINT _nx_secure_tls_alpn_process_response(NX_SECURE_TLS_SESSION *tls_session, - const UCHAR *packet_buffer, - UINT message_length) -{ -UINT ext_length; -UINT list_length; -UINT name_length; - - if ((tls_session == NX_NULL) || (packet_buffer == NX_NULL)) - { - return(NX_PTR_ERROR); - } - - if (message_length < 2u) - { - return(NX_SECURE_TLS_INCORRECT_MESSAGE_LENGTH); - } - - ext_length = (UINT)((packet_buffer[0] << 8) + packet_buffer[1]); - if ((ext_length + 2u) > message_length) - { - return(NX_SECURE_TLS_INCORRECT_MESSAGE_LENGTH); - } - - /* list_length (2) + name_length (1) + at least one byte of name. */ - if (ext_length < 4u) - { - return(NX_SECURE_TLS_INCORRECT_MESSAGE_LENGTH); - } - - list_length = (UINT)((packet_buffer[2] << 8) + packet_buffer[3]); - if (list_length != (ext_length - 2u)) - { - return(NX_SECURE_TLS_INCORRECT_MESSAGE_LENGTH); - } - - name_length = packet_buffer[4]; - if ((name_length == 0) || ((name_length + 1u) != list_length)) - { - - /* RFC 7301 3.1: the server's list is exactly one name. Two names is - not a longer answer, it is a message this code cannot act on. */ - return(NX_SECURE_TLS_INCORRECT_MESSAGE_LENGTH); - } - - if (name_length > NX_SECURE_TLS_ALPN_PROTOCOL_MAX) - { - return(NX_SECURE_TLS_ALPN_PROTOCOL_MISMATCH); - } - - /* - * RFC 7301 3.2, and this is the check that matters. A server that selects - * a protocol the client never offered has answered a question that was not - * asked, and a client that accepts it then speaks a protocol it may not - * implement over an authenticated channel. - */ - if (!nx_secure_tls_alpn_list_has(tls_session -> nx_secure_tls_alpn_protocol_list, - tls_session -> nx_secure_tls_alpn_protocol_list_length, - &packet_buffer[5], name_length)) - { - return(NX_SECURE_TLS_ALPN_PROTOCOL_MISMATCH); - } - - NX_SECURE_MEMCPY(tls_session -> nx_secure_tls_alpn_selected, - &packet_buffer[5], name_length); /* Use case of memcpy is verified. */ - tls_session -> nx_secure_tls_alpn_selected_length = (UCHAR)name_length; - - return(NX_SUCCESS); -} - -/**************************************************************************/ -/* _nx_secure_tls_alpn_process_offer */ -/* */ -/* The server half. Selects the FIRST of our own protocols that the */ -/* client also offered, so the preference is the server's (RFC 7301 3.2 */ -/* leaves the choice to the server and warns against following the */ -/* client's order). A configured list without overlap is fatal. */ -/**************************************************************************/ -UINT _nx_secure_tls_alpn_process_offer(NX_SECURE_TLS_SESSION *tls_session, - const UCHAR *packet_buffer, - UINT message_length) -{ -UINT ext_length; -UINT list_length; -UINT offset; -const UCHAR *ours; -UINT ours_length; - - if ((tls_session == NX_NULL) || (packet_buffer == NX_NULL)) - { - return(NX_PTR_ERROR); - } - - tls_session -> nx_secure_tls_alpn_selected_length = 0; - - ours = tls_session -> nx_secure_tls_alpn_protocol_list; - ours_length = tls_session -> nx_secure_tls_alpn_protocol_list_length; - - if (message_length < 2u) - { - return(NX_SECURE_TLS_INCORRECT_MESSAGE_LENGTH); - } - - ext_length = (UINT)((packet_buffer[0] << 8) + packet_buffer[1]); - if (((ext_length + 2u) > message_length) || (ext_length < 4u)) - { - return(NX_SECURE_TLS_INCORRECT_MESSAGE_LENGTH); - } - - list_length = (UINT)((packet_buffer[2] << 8) + packet_buffer[3]); - if (list_length != (ext_length - 2u)) - { - return(NX_SECURE_TLS_INCORRECT_MESSAGE_LENGTH); - } - - if (!nx_secure_tls_alpn_list_valid(&packet_buffer[4], list_length)) - { - return(NX_SECURE_TLS_INCORRECT_MESSAGE_LENGTH); - } - - if ((ours == NX_NULL) || (ours_length == 0)) - { - return(NX_SUCCESS); - } - - for (offset = 0; offset < ours_length; offset += 1u + ours[offset]) - { - UINT name_length = ours[offset]; - - if (name_length > NX_SECURE_TLS_ALPN_PROTOCOL_MAX) - { - continue; - } - - if (nx_secure_tls_alpn_list_has(&packet_buffer[4], list_length, - &ours[offset + 1], name_length)) - { - NX_SECURE_MEMCPY(tls_session -> nx_secure_tls_alpn_selected, - &ours[offset + 1], name_length); /* Use case of memcpy is verified. */ - tls_session -> nx_secure_tls_alpn_selected_length = (UCHAR)name_length; - break; - } - } - - return((tls_session -> nx_secure_tls_alpn_selected_length != 0) - ? NX_SUCCESS : NX_SECURE_TLS_ALPN_PROTOCOL_MISMATCH); -} diff --git a/src/tlslib/CMakeLists.txt b/src/tlslib/CMakeLists.txt index e0bc74641..855366882 100644 --- a/src/tlslib/CMakeLists.txt +++ b/src/tlslib/CMakeLists.txt @@ -519,7 +519,7 @@ if(NOT CMAKE_CROSSCOMPILING) add_executable(test_tls_alpn test/test_tls_alpn.c tls_alpn.c - "${CMAKE_SOURCE_DIR}/src/tls/alpn/nx_secure_tls_alpn.c" + "${AMINETXDUO_NETXDUO}/nx_secure/src/nx_secure_tls_alpn.c" "${AMINETXDUO_NETXDUO}/nx_secure/src/nx_secure_tls_map_error_to_alert.c") add_executable(test_tls_transport test/test_tls_transport.c diff --git a/src/tlslib/test/test_tls_alpn.c b/src/tlslib/test/test_tls_alpn.c index 59ff5ff55..1c26d1c3c 100644 --- a/src/tlslib/test/test_tls_alpn.c +++ b/src/tlslib/test/test_tls_alpn.c @@ -1,5 +1,5 @@ /* - * RFC 7301 ALPN, src/tls/alpn/nx_secure_tls_alpn.c and src/tlslib/tls_alpn.c. + * RFC 7301 ALPN, third_party/netxduo/nx_secure/src/nx_secure_tls_alpn.c and src/tlslib/tls_alpn.c. * * nx_secure had no ALPN at all, so nothing built on it could negotiate * HTTP/2: h2 over TLS is defined only over a negotiated "h2" (RFC 7540 3.2). diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt index cb0735be9..618f9e8cd 100644 --- a/tests/fuzz/CMakeLists.txt +++ b/tests/fuzz/CMakeLists.txt @@ -404,9 +404,8 @@ add_executable(fuzz_tls_record # RFC 7301. The ServerHello extension walk reaches it, so the ALPN # response parser is fuzzed by the same corpus rather than needing one of # its own: it is length-prefixed bytes off the wire like everything else - # in this target. src/tls/CMakeLists.txt says why it lives outside - # third_party. - "${CMAKE_SOURCE_DIR}/src/tls/alpn/nx_secure_tls_alpn.c" + # in this target. + "${_fuzz_secure_src}/nx_secure_tls_alpn.c" "${_fuzz_secure_src}/nx_secure_tls_process_certificate_request.c" "${_fuzz_secure_src}/nx_secure_tls_ciphersuite_lookup.c" "${_fuzz_secure_src}/nx_secure_tls_check_protocol_version.c" @@ -497,14 +496,8 @@ if(CMAKE_SIZEOF_VOID_P EQUAL 4) list(FILTER _fuzz_secure_all EXCLUDE REGEX "/nx_secure_tls_record_payload_(en|de)crypt\\.c$") - # RFC 7301. The vendored tree declares _nx_secure_tls_alpn_* and calls - # them from the ClientHello extension walk, but ships no implementation -- - # ours is the only one, and it lives outside third_party for the reason - # src/tls/CMakeLists.txt gives. Without it the archive's own callers are - # unresolved. add_library(fuzz_tls_nx_secure STATIC ${_fuzz_secure_all} - "${CMAKE_SOURCE_DIR}/src/tls/alpn/nx_secure_tls_alpn.c" "${CMAKE_SOURCE_DIR}/src/tls/rfc7905/nx_secure_tls_record_payload_encrypt.c" "${CMAKE_SOURCE_DIR}/src/tls/rfc7905/nx_secure_tls_record_payload_decrypt.c") diff --git a/tests/fuzz/fuzz_nxstub.c b/tests/fuzz/fuzz_nxstub.c index 0a0ce0abc..02bb24f03 100644 --- a/tests/fuzz/fuzz_nxstub.c +++ b/tests/fuzz/fuzz_nxstub.c @@ -16,6 +16,12 @@ #include "nx_api.h" +/* NX_IP_FRAGMENT_ADMIT (nx_ip.h) reads the count nx_ip_create() keeps. No + binary that links this file links nx_ip_create.c; each builds at most one + NX_IP by hand, so the count is defined here as that one instance. */ +ULONG _nx_ip_created_count = 1; + + UINT _tx_mutex_get(TX_MUTEX *mutex_ptr, ULONG wait_option) { diff --git a/tests/ipv6/host/test_ipv6_frag_host.c b/tests/ipv6/host/test_ipv6_frag_host.c index 8c48e7338..79684ddeb 100644 --- a/tests/ipv6/host/test_ipv6_frag_host.c +++ b/tests/ipv6/host/test_ipv6_frag_host.c @@ -144,6 +144,12 @@ VOID _nx_icmpv4_send_error_message(NX_IP *ip_ptr, NX_PACKET *offending_packet, static NX_IP h_ip; static NX_PACKET_POOL h_pool; + +/* NX_IP_FRAGMENT_ADMIT (nx_ip.h) reads the count nx_ip_create() keeps, and + this fixture builds its one NX_IP by hand without nx_ip_create.c, so the + count is defined here as that one instance. */ +ULONG _nx_ip_created_count = 1; + static NX_PACKET h_packet[H_PACKETS]; static UCHAR h_body[H_PACKETS][H_PAYLOAD]; static NX_IPV6_HEADER h_v6_header[H_PACKETS]; @@ -355,7 +361,15 @@ static VOID test_reassembles_out_of_order(VOID) static VOID test_pool_reserve(VOID) { -UINT status; +UINT status; +ULONG ip_count; + + /* The pool-wide reserve is the gate for a pool another IP instance may + share: NX_IP_FRAGMENT_ADMIT applies it only while more than one NX_IP + exists. Model that second, live instance for this test alone, and + give the one-instance count back on the way out. */ + ip_count = _nx_ip_created_count; + _nx_ip_created_count = 2; h_reset(); @@ -386,6 +400,8 @@ UINT status; h_check(status != NX_SUCCESS, "a fragment below the reserve is refused"); h_check(h_ip.nx_ip_received_fragment_head == NX_NULL, "and is queued nowhere either"); + + _nx_ip_created_count = ip_count; } static VOID test_incomplete_times_out(VOID) diff --git a/tests/netstack/host/test_bcast_loopback_host.c b/tests/netstack/host/test_bcast_loopback_host.c index 4898d8cdb..6dfc9adfe 100644 --- a/tests/netstack/host/test_bcast_loopback_host.c +++ b/tests/netstack/host/test_bcast_loopback_host.c @@ -38,6 +38,12 @@ static void h_check(int ok, const char *what) static NX_IP h_ip; static NX_PACKET_POOL h_pool; + +/* NX_IP_FRAGMENT_ADMIT (nx_ip.h) reads the count nx_ip_create() keeps, and + this fixture builds its one NX_IP by hand without nx_ip_create.c, so the + count is defined here as that one instance. */ +ULONG _nx_ip_created_count = 1; + static NX_UDP_SOCKET h_server; /* bound to H_SERVER_PORT */ static NX_UDP_SOCKET h_client; /* the one that broadcasts */ diff --git a/tests/netstack/host/test_ipv4_noaddr_account_host.c b/tests/netstack/host/test_ipv4_noaddr_account_host.c index 5ed45b377..564ecd773 100644 --- a/tests/netstack/host/test_ipv4_noaddr_account_host.c +++ b/tests/netstack/host/test_ipv4_noaddr_account_host.c @@ -46,6 +46,12 @@ static void h_check(int ok, const char *what) static unsigned long h_releases; + +/* NX_IP_FRAGMENT_ADMIT (nx_ip.h) reads the count nx_ip_create() keeps, and + h_run() builds its one NX_IP on the stack without nx_ip_create.c, so the + count is defined here as that one instance. */ +ULONG _nx_ip_created_count = 1; + VOID _nx_packet_release(NX_PACKET *packet_ptr) { (void)packet_ptr; diff --git a/tests/netstack/host/test_mcast_share_2sock_host.c b/tests/netstack/host/test_mcast_share_2sock_host.c index fa2451cf5..0655b3c5a 100644 --- a/tests/netstack/host/test_mcast_share_2sock_host.c +++ b/tests/netstack/host/test_mcast_share_2sock_host.c @@ -40,6 +40,12 @@ static void h_check(int ok, const char *what) static NX_IP h_ip; static NX_PACKET_POOL h_pool; + +/* NX_IP_FRAGMENT_ADMIT (nx_ip.h) reads the count nx_ip_create() keeps, and + this fixture builds its one NX_IP by hand without nx_ip_create.c, so the + count is defined here as that one instance. */ +ULONG _nx_ip_created_count = 1; + static NX_UDP_SOCKET h_a; /* the sender, bound first */ static NX_UDP_SOCKET h_b; /* the co-bound sibling */ diff --git a/tests/netstack/host/test_mcast_share_loopback_host.c b/tests/netstack/host/test_mcast_share_loopback_host.c index 260bf8d66..417c01de4 100644 --- a/tests/netstack/host/test_mcast_share_loopback_host.c +++ b/tests/netstack/host/test_mcast_share_loopback_host.c @@ -41,6 +41,12 @@ static void h_check(int ok, const char *what) static NX_IP h_ip; static NX_PACKET_POOL h_pool; + +/* NX_IP_FRAGMENT_ADMIT (nx_ip.h) reads the count nx_ip_create() keeps, and + this fixture builds its one NX_IP by hand without nx_ip_create.c, so the + count is defined here as that one instance. */ +ULONG _nx_ip_created_count = 1; + static NX_UDP_SOCKET h_m; /* mDNS responder, bound first */ static NX_UDP_SOCKET h_a; /* the sender */ static NX_UDP_SOCKET h_b; /* the co-bound sibling */ diff --git a/tests/netstack/host/test_tcp_sws_host.c b/tests/netstack/host/test_tcp_sws_host.c index 53c03f8a2..0acffd03f 100644 --- a/tests/netstack/host/test_tcp_sws_host.c +++ b/tests/netstack/host/test_tcp_sws_host.c @@ -267,6 +267,7 @@ static NX_IP h_ip; static NX_INTERFACE h_iface; static NX_TCP_SOCKET h_sock; static TX_THREAD h_waiter; +static NX_PACKET h_waiting_write; /* what h_waiter is blocked on */ static NX_PACKET h_pkt[H_PACKETS]; static UCHAR h_pkt_buf[H_PACKETS][H_BUF]; @@ -277,6 +278,7 @@ static void h_fixture(void) memset(&h_ip, 0, sizeof(h_ip)); memset(&h_iface, 0, sizeof(h_iface)); memset(&h_waiter, 0, sizeof(h_waiter)); + memset(&h_waiting_write, 0, sizeof(h_waiting_write)); h_now = 1000; h_datagrams = 0; @@ -370,7 +372,10 @@ static void a_open_window_sends(void) (unsigned long)h_sock.nx_tcp_socket_tx_window_advertised); } -static void b_sliver_with_flight_holds(void) +/* RFC 1122 4.2.3.4 rule (2) with Nagle off, as it is here: a pushed write + that fits whole in the usable window (D <= U) goes now, even into a sliver + with data in flight (a820b430). One that does not fit is still held. */ +static void b_sliver_with_flight_sends_a_write_that_fits(void) { UINT status; @@ -379,16 +384,34 @@ static void b_sliver_with_flight_holds(void) status = h_write(100); + h_check_eq(status, NX_SUCCESS, + "a 100-byte write that fits 200 bytes of usable window was " + "refused (rule 2: a pushed write that fits goes now)"); + h_check_eq(h_datagrams, 1, + "a 100-byte write that fits 200 bytes of usable window with " + "512 bytes in flight was withheld (rule 2)"); + + h_check(h_sock.nx_tcp_socket_zero_window_probe_has_data == NX_FALSE, + "a non-zero window armed the zero-window persist probe"); + + printf(" sliver, in flight fits: %u datagram(s), status %u\n", + (unsigned int)h_datagrams, (unsigned int)status); + + /* The negative control: a write larger than the sliver is held, whole. */ + h_fixture(); + h_in_flight(512, 200); + + status = h_write(300); + h_check_eq(h_datagrams, 0, - "a 100-byte write went out into 200 bytes of usable window " + "a 300-byte write went out into 200 bytes of usable window " "with 512 bytes still in flight (no sender SWS avoidance)"); h_check_eq(status, NX_WINDOW_OVERFLOW, "a send the window rule refused did not report the window"); - h_check(h_sock.nx_tcp_socket_zero_window_probe_has_data == NX_FALSE, - "a non-zero window armed the zero-window persist probe"); + "a held write into a non-zero window armed the persist probe"); - printf(" sliver, in flight %u datagram(s), status %u\n", + printf(" sliver, in flight too big: %u datagram(s), status %u\n", (unsigned int)h_datagrams, (unsigned int)status); } @@ -445,21 +468,36 @@ static void e_half_the_max_window_releases(void) h_check_eq(h_datagrams, 1, "1200 usable bytes of a 2400-byte peer window sent nothing"); - /* The same 1200 bytes, on a peer that has offered 64 KB before now: this - one IS dribbling, and 1200 is neither a full segment nor half of what it - has shown it can take. */ + /* The same 1200 usable bytes on a peer that has offered 64 KB before now: + 1200 is neither a full segment nor half of what it has shown it can + take, so rules (1) and (3) do not release it. A write of exactly 1200 + still goes, by rule (2): it fits whole (a820b430). */ h_fixture(); h_in_flight(512, 1200); status = h_write(1200); + h_check_eq(status, NX_SUCCESS, + "a 1200-byte write that fits 1200 usable bytes of a 65535-byte " + "peer window was refused (rule 2)"); + h_check_eq(h_datagrams, 1, + "a 1200-byte write that fits 1200 usable bytes of a 65535-byte " + "peer window sent nothing (rule 2)"); + + /* One that does not fit is held: no rule releases it. */ + h_fixture(); + h_in_flight(512, 1200); + + status = h_write(1300); + h_check_eq(h_datagrams, 0, - "1200 usable bytes of a 65535-byte peer window went out"); + "a 1300-byte write into 1200 usable bytes of a 65535-byte " + "peer window went out"); h_check_eq(status, NX_WINDOW_OVERFLOW, "a send the window rule refused did not report the window"); - printf(" 1200 usable sent under a 2400-byte peak, held under " - "65535\n"); + printf(" 1200 usable sent under a 2400-byte peak; under 65535 " + "a write that fits goes, a bigger one is held\n"); } static void f_zero_window_still_persists(void) @@ -506,6 +544,13 @@ static void g_congestion_window_is_not_a_sliver(void) static void i_a_blocked_sender_is_woken_once(void) { h_fixture(); + + /* A sender blocked in nx_tcp_socket_send() waits with its packet in + tx_thread_additional_suspend_info (nx_tcp_socket_send_internal.c), and + transmit_check measures the window against that packet. A full + segment of data, as a bulk writer would have queued. */ + h_waiting_write.nx_packet_length = H_MSS; + h_waiter.tx_thread_additional_suspend_info = (VOID *)&h_waiting_write; h_sock.nx_tcp_socket_transmit_suspension_list = &h_waiter; h_sock.nx_tcp_socket_transmit_suspended_count = 1; h_in_flight(512, 200); @@ -563,16 +608,19 @@ static void j_peak_window_is_remembered(void) (unsigned long)h_sock.nx_tcp_socket_tx_window_advertised_max); } -/* One pure ACK out of the real control path, with rx_window_current at - `current` and the socket's buffer at `dflt`. Answers the window that - reached the wire. */ -static ULONG h_advertise(ULONG current, ULONG dflt) +/* One pure ACK out of the real control path, with rx_window_current (the + free space) at `current`, the edge last put on the wire at `last_sent`, + and the socket's buffer at `dflt`. Answers the window that reached the + wire. NX_TCP_RX_WINDOW_ADVERTISED (nx_tcp.h, 5615cfd8): at or above the + floor, min(MSS, buffer / 2), the free space; below it, the edge last sent + is held, or the free space if that is less. */ +static ULONG h_advertise_edge(ULONG current, ULONG last_sent, ULONG dflt) { h_fixture(); h_sock.nx_tcp_socket_rx_window_default = dflt; h_sock.nx_tcp_socket_rx_window_current = current; - h_sock.nx_tcp_socket_rx_window_last_sent = current; + h_sock.nx_tcp_socket_rx_window_last_sent = last_sent; h_last_window = 0xFFFFFFFFUL; h_acks = 0; @@ -586,23 +634,68 @@ static ULONG h_advertise(ULONG current, ULONG dflt) h_alloc_ok = 0; h_check(h_acks == 1, "the control path sent no acknowledgment"); + h_check_eq(h_sock.nx_tcp_socket_rx_window_last_sent, h_last_window, + "rx_window_last_sent is not the window that went on the wire"); return h_last_window; } -static void k_a_runt_window_is_advertised_as_zero(void) +/* The edge last sent equal to the free space: what an established socket + has after an acknowledgment that drained nothing. */ +static ULONG h_advertise(ULONG current, ULONG dflt) +{ + return h_advertise_edge(current, current, dflt); +} + +/* Below the floor, a window already offered is not withdrawn (RFC 9293 + 3.8.6.2.2): with the edge last sent equal to the free space, the free space + goes out again rather than zero (5615cfd8). */ +static void k_a_runt_window_already_offered_is_kept(void) { ULONG w; w = h_advertise(68UL, 8192UL); - h_check(w == 0UL, "68 bytes of window was advertised rather than zero"); + h_check_eq(w, 68UL, "68 bytes of window already offered was not kept"); w = h_advertise(104UL, 8192UL); - h_check(w == 0UL, "104 bytes of window was advertised rather than zero"); + h_check_eq(w, 104UL, "104 bytes of window already offered was not kept"); w = h_advertise(H_MSS - 1UL, 8192UL); - h_check(w == 0UL, "one byte short of an MSS was advertised rather than " - "zero"); + h_check_eq(w, H_MSS - 1UL, "one byte short of an MSS, already offered, " + "was not kept"); +} + +/* Below the floor, the edge is not moved (RFC 1122 4.2.3.3): free space past + the edge last sent opens no new sliver, and a zero edge stays zero. Less + free space than the edge is a genuine shrink, and goes out as it is. */ +static void o_below_the_floor_the_edge_holds(void) +{ +ULONG w; + + /* 1000 free, 200 on the wire, floor 1460 (8192-byte buffer). */ + w = h_advertise_edge(1000UL, 200UL, 8192UL); + h_check_eq(w, 200UL, "below the floor, free space past the edge opened " + "a new sliver"); + + w = h_advertise_edge(100UL, 0UL, 8192UL); + h_check_eq(w, 0UL, "below the floor, a zero edge was reopened by a " + "runt of free space"); + + w = h_advertise_edge(H_MSS - 1UL, 0UL, 8192UL); + h_check_eq(w, 0UL, "one byte short of the floor reopened a zero edge"); + + /* At the floor the free space goes out, whatever the edge was. */ + w = h_advertise_edge(H_MSS, 0UL, 8192UL); + h_check_eq(w, H_MSS, "free space at the floor did not reopen a zero " + "edge"); + + /* A genuine shrink: the buffer itself has less than the edge promised. */ + w = h_advertise_edge(100UL, 300UL, 8192UL); + h_check_eq(w, 100UL, "less free space than the edge was not advertised " + "as it is"); + + printf(" held edge kept below the floor, reopened at it, " + "shrunk with the buffer\n"); } /* And the rule stops exactly at one MSS: a window a sender can fill is never @@ -630,9 +723,20 @@ ULONG w; h_check(w == 600UL, "a small-buffer socket advertised zero and could " "never reopen"); - /* Below half of it, the rule still applies. */ + /* Below half of it, 100 already offered is kept (5615cfd8), and a zero + edge is not reopened by it. */ w = h_advertise(100UL, 1000UL); - h_check(w == 0UL, "a runt below half a small buffer was still advertised"); + h_check_eq(w, 100UL, "a runt below half a small buffer, already offered, " + "was not kept"); + + w = h_advertise_edge(100UL, 0UL, 1000UL); + h_check_eq(w, 0UL, "a runt below half a small buffer reopened a zero " + "edge"); + + /* At half of it, 500, the small buffer reopens a zero edge. */ + w = h_advertise_edge(500UL, 0UL, 1000UL); + h_check_eq(w, 500UL, "a small buffer at its floor did not reopen a zero " + "edge"); } /* A window that is genuinely zero is still zero, and is not confused with one @@ -655,7 +759,7 @@ int main(void) (unsigned long)(H_PEER_WINDOW >> 1)); a_open_window_sends(); - b_sliver_with_flight_holds(); + b_sliver_with_flight_sends_a_write_that_fits(); c_small_write_is_not_delayed(); d_nothing_in_flight_always_sends(); e_half_the_max_window_releases(); @@ -666,10 +770,11 @@ int main(void) printf("RFC 1122 4.2.3.3 receiver silly-window avoidance, against the real " "control path\n"); - k_a_runt_window_is_advertised_as_zero(); + k_a_runt_window_already_offered_is_kept(); l_a_full_segment_is_advertised(); m_a_buffer_below_one_mss_still_opens(); n_zero_stays_zero(); + o_below_the_floor_the_edge_holds(); printf("%lu checks, %lu failures, %s\n", h_checks, h_failures, (h_failures == 0UL) ? "PASS" : "FAIL"); diff --git a/tests/syncache/CMakeLists.txt b/tests/syncache/CMakeLists.txt index a2ad73860..361a98352 100644 --- a/tests/syncache/CMakeLists.txt +++ b/tests/syncache/CMakeLists.txt @@ -52,6 +52,31 @@ include(CTest) add_test(NAME syncache_cookie COMMAND test_syncache cookie) add_test(NAME syncache_cache COMMAND test_syncache cache) +# The same test with ULONG 64 bits wide. No target this ships for has one, +# and the x86_64 hosts the fork's 64-bit suite runs on keep ULONG at 32 bits +# (ThreadX's linux tx_port.h), so nothing else builds the cookie that way; +# nx_tcp_syncache.c says every step of it masks to 32 bits so that a wide +# ULONG computes the same thing. This is where that is checked: the cookie +# vectors, the counter wrap and the carry, and the cache, on the same file. +add_executable(test_syncache_ulong64 + host/test_syncache_host.c + "${AMINETXDUO_NETXDUO}/common/src/nx_tcp_syncache.c") +target_include_directories(test_syncache_ulong64 PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/host/ulong64" # tx_port.h, first + "${AMINETXDUO_NETXDUO}/ports/linux/gnu/inc" + "${CMAKE_SOURCE_DIR}/port/netxduo-amiga/inc" + "${AMINETXDUO_NETXDUO}/common/inc" + "${AMINETXDUO_THREADX}/common/inc") +target_compile_definitions(test_syncache_ulong64 PRIVATE + NX_PACKET_ALIGNMENT=8 + NX_INCLUDE_USER_DEFINE_FILE + NX_IPV6_UTIL_INLINE + AMINETXDUO_HOST_VENDORED_TX_PORT="${AMINETXDUO_THREADX}/ports/linux/gnu/inc/tx_port.h") +target_compile_options(test_syncache_ulong64 PRIVATE -Wall -Wextra) +set_target_properties(test_syncache_ulong64 PROPERTIES C_STANDARD 11) +add_test(NAME syncache_cookie_ulong64 COMMAND test_syncache_ulong64 cookie) +add_test(NAME syncache_cache_ulong64 COMMAND test_syncache_ulong64 cache) + # Issue #50: a SYN-cache entry outlives the interface its SYN arrived on. # The cache, _nx_ip_interface_detach, the TCP control senders, the IPv4 route # lookup and _nx_ipv6_packet_send are all the shipped files; host_nx_assert.h diff --git a/tests/syncache/host/test_syncache_detach_host.c b/tests/syncache/host/test_syncache_detach_host.c index 3079ceba6..cae4fe55b 100644 --- a/tests/syncache/host/test_syncache_detach_host.c +++ b/tests/syncache/host/test_syncache_detach_host.c @@ -128,7 +128,16 @@ UINT _nx_packet_allocate(NX_PACKET_POOL *pool_ptr, NX_PACKET **packet_ptr, return NX_SUCCESS; } -UINT _nx_packet_release(NX_PACKET *packet_ptr) { (void) packet_ptr; return NX_SUCCESS; } +/* No arm releases a packet (counted: 0 calls in all ten): the IPv4 send and + _nx_ipv6_header_add above keep theirs, and the cache's own releases are in + _nx_tcp_syncache_hold, which no arm reaches. A call is a path this test + does not model, so it stops the test, as the two below do. */ +UINT _nx_packet_release(NX_PACKET *packet_ptr) +{ + (void) packet_ptr; + printf("FAIL _nx_packet_release reached: an unmodelled path\n"); + abort(); +} UINT _nx_packet_transmit_release(NX_PACKET *packet_ptr) { (void) packet_ptr; return NX_SUCCESS; } /* A SYN fed into the cache from inside the detach: in the unlocked ARP/ND @@ -155,6 +164,28 @@ UINT _nx_igmp_multicast_interface_leave_internal(NX_IP *ip_ptr, ULONG group, UIN return NX_SUCCESS; } VOID _nx_tcp_socket_connection_reset(NX_TCP_SOCKET *socket_ptr) { (void) socket_ptr; } + +/* nx_tcp_syncache.c calls these two only from _nx_tcp_syncache_accept and + _nx_tcp_syncache_hold (2d44d563), which no arm here calls, and the ACK also + from _nx_tcp_syncache_send_ack (96502647), for a segment carrying data to a + handshake in the accept queue, which no arm sends. A call is a path this + test does not model, so it stops the test rather than being absorbed. + tests/syncache/host/test_syncache_host.c models both and drives them. */ +VOID _nx_tcp_socket_packet_process(NX_TCP_SOCKET *socket_ptr, NX_PACKET *packet_ptr) +{ + (void) socket_ptr; + (void) packet_ptr; + printf("FAIL _nx_tcp_socket_packet_process reached: an unmodelled path\n"); + abort(); +} + +VOID _nx_tcp_packet_send_ack(NX_TCP_SOCKET *socket_ptr, ULONG tx_sequence) +{ + (void) socket_ptr; + (void) tx_sequence; + printf("FAIL _nx_tcp_packet_send_ack reached: an unmodelled path\n"); + abort(); +} #ifdef FEATURE_NX_IPV6 VOID _nx_nd_cache_interface_entries_delete(NX_IP *ip_ptr, UINT index) { @@ -461,11 +492,23 @@ static void case_v6_retry(void) static void case_v6_accept(void) { ULONG iss; + ULONG iss_taker; + /* A SYN with no socket parked is deferred and not answered (96502647), + so the queued handshake comes from two SYNs answered while one socket + is parked: an IPv4 one on K takes the socket, the IPv6 one finishes + with none and queues. */ rig_reset(); - rig_listen.nx_tcp_listen_socket_ptr = NX_NULL; + rig_listen.nx_tcp_listen_socket_ptr = &rig_socket; rig_syn(NX_IP_VERSION_V6, 0x2000); - iss = rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_age_head -> nx_tcp_syncache_iss; + iss = rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_age_tail -> nx_tcp_syncache_iss; + rig_syn(NX_IP_VERSION_V4, 0x2100); + iss_taker = rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_age_tail -> nx_tcp_syncache_iss; + ok("both SYNs are answered", rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_count == 2); + rig_ack(NX_IP_VERSION_V4, 0x2100, iss_taker); + ok("the first finished handshake takes the parked socket", + rig_listen.nx_tcp_listen_socket_ptr == NX_NULL && + rig_socket.nx_tcp_socket_connect_port == 40000); rig_ack(NX_IP_VERSION_V6, 0x2000, iss); ok("the handshake is queued for accept", rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_accept_count == 1); @@ -515,21 +558,40 @@ static void case_keep_other(void) { ULONG iss; + ULONG iss_taker; + + /* Every SYN arrives while a socket is parked, so each is answered + (96502647: with none parked a SYN is deferred, unanswered, and never + retried). The first ACK takes the socket; the second finishes with + none and queues. */ rig_reset(); - rig_listen.nx_tcp_listen_socket_ptr = NX_NULL; + rig_listen.nx_tcp_listen_socket_ptr = &rig_socket; rig_if = 0; rig_syn(NX_IP_VERSION_V4, 0x6000); - iss = rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_age_head -> nx_tcp_syncache_iss; - rig_ack(NX_IP_VERSION_V4, 0x6000, iss); /* queued on 0 */ - ok("a handshake on interface 0 is queued for accept", - rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_accept_count == 1); + iss = rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_age_tail -> nx_tcp_syncache_iss; + peer4_other += 2; /* the peer that takes the socket */ + rig_syn(NX_IP_VERSION_V4, 0x6100); + iss_taker = rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_age_tail -> nx_tcp_syncache_iss; + peer4_other -= 2; peer4_other++; /* another peer on 0 */ rig_syn(NX_IP_VERSION_V4, 0x6200); peer4_other--; rig_if = K; rig_syn(NX_IP_VERSION_V4, 0x6300); /* half-open on K */ + + rig_if = 0; + peer4_other += 2; + rig_ack(NX_IP_VERSION_V4, 0x6100, iss_taker); /* takes the socket */ + peer4_other -= 2; + ok("the first finished handshake takes the parked socket", + rig_listen.nx_tcp_listen_socket_ptr == NX_NULL && + rig_socket.nx_tcp_socket_connect_port == 40000); + rig_ack(NX_IP_VERSION_V4, 0x6000, iss); /* queued on 0 */ + ok("a handshake on interface 0 is queued for accept", + rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_accept_count == 1); + rig_if = K; ok("two half-open, one queued", rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_count == 2 && rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_accept_count == 1); @@ -537,6 +599,8 @@ static void case_keep_other(void) rig_detach(); rig_cache_check(1, 1); ok("the survivors are both on interface 0", + rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_age_head != NX_NULL && + rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_accept_head != NX_NULL && rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_age_head -> nx_tcp_syncache_interface == &rig_ip.nx_ip_interface[0] && rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_accept_head -> nx_tcp_syncache_interface diff --git a/tests/syncache/host/test_syncache_host.c b/tests/syncache/host/test_syncache_host.c index eba41ceda..fcffb2394 100644 --- a/tests/syncache/host/test_syncache_host.c +++ b/tests/syncache/host/test_syncache_host.c @@ -11,6 +11,7 @@ #include "nx_api.h" #include "nx_ip.h" #include "nx_tcp.h" +#include "nx_packet.h" #include #include @@ -115,6 +116,115 @@ VOID _nx_tcp_socket_state_syn_received(NX_TCP_SOCKET *socket_ptr, stub_established++; } +/* What the cache asks of the rest of the stack once a connection is handed + over (2d44d563). Each is a model of what the real one does to what the + cache gives it, checked, so a call the cache should not make, or makes with + the wrong thing, fails the arm instead of being absorbed. */ +static int stub_violations; + +static void stub_violation(const char *what) +{ + printf("FAIL model: %s\n", what); + stub_violations++; +} + +/* _nx_tcp_packet_send_ack: an ACK from a connection that has a peer, at the + sequence number it is handed. Recorded with the window it advertises. */ +static int stub_acks; +static NX_TCP_SOCKET *stub_ack_socket; +static ULONG stub_ack_seq; +static ULONG stub_ack_ack; +static ULONG stub_ack_window; + +VOID _nx_tcp_packet_send_ack(NX_TCP_SOCKET *socket_ptr, ULONG tx_sequence) +{ + if ((socket_ptr == NX_NULL) || (socket_ptr -> nx_tcp_socket_connect_port == 0)) + { + stub_violation("an ACK from a socket with no peer"); + return; + } + stub_acks++; + stub_ack_socket = socket_ptr; + stub_ack_seq = tx_sequence; + stub_ack_ack = socket_ptr -> nx_tcp_socket_rx_sequence; + stub_ack_window = socket_ptr -> nx_tcp_socket_rx_window_current; +} + +/* The rig's packets. A packet is the cache's to hold or release only while + it is allocated: releasing one twice, or one the rig never gave out, is a + violation. */ +#define RIG_PACKETS 4 +static NX_PACKET rig_rx[RIG_PACKETS]; +static UCHAR rig_rx_data[RIG_PACKETS][64]; +static int rig_rx_live[RIG_PACKETS]; +static int stub_released; + +static int rig_rx_index(NX_PACKET *packet_ptr) +{ + int i; + + for (i = 0; i < RIG_PACKETS; i++) + { + if (packet_ptr == &rig_rx[i]) + { + return(i); + } + } + return(-1); +} + +UINT _nx_packet_release(NX_PACKET *packet_ptr) +{ + int i = rig_rx_index(packet_ptr); + + if ((i < 0) || (rig_rx_live[i] == 0)) + { + stub_violation("a packet released that the cache did not own"); + return(NX_PTR_ERROR); + } + rig_rx_live[i] = 0; + stub_released++; + return(NX_SUCCESS); +} + +/* _nx_tcp_socket_packet_process, as an ESTABLISHED socket takes in-order + data: the packet is the socket's, no longer on any queue, and starts at + the next byte the socket expects; the socket takes its data and the + packet. */ +static int stub_processed; +static ULONG stub_processed_bytes; + +VOID _nx_tcp_socket_packet_process(NX_TCP_SOCKET *socket_ptr, NX_PACKET *packet_ptr) +{ + NX_TCP_HEADER *h; + ULONG header_length; + int i = rig_rx_index(packet_ptr); + + if ((i < 0) || (rig_rx_live[i] == 0)) + { + stub_violation("a packet processed that the cache did not own"); + return; + } + if (socket_ptr -> nx_tcp_socket_state != NX_TCP_ESTABLISHED) + { + stub_violation("a held packet processed before the socket is established"); + } + if (packet_ptr -> nx_packet_union_next.nx_packet_tcp_queue_next != (NX_PACKET *) NX_PACKET_ALLOCATED) + { + stub_violation("a held packet processed while still marked queued"); + } + h = (NX_TCP_HEADER *) packet_ptr -> nx_packet_prepend_ptr; + header_length = (h -> nx_tcp_header_word_3 >> NX_TCP_HEADER_SHIFT) << 2; + if (h -> nx_tcp_sequence_number != socket_ptr -> nx_tcp_socket_rx_sequence) + { + stub_violation("a held packet processed out of order"); + } + socket_ptr -> nx_tcp_socket_rx_sequence += packet_ptr -> nx_packet_length - header_length; + stub_processed_bytes += packet_ptr -> nx_packet_length - header_length; + stub_processed++; + rig_rx_live[i] = 0; +} + ULONG _nx_ip_route_find(NX_IP *ip_ptr, ULONG destination_address, NX_INTERFACE **nx_ip_interface, ULONG *next_hop_address) { @@ -210,6 +320,108 @@ static UINT cookie_case(void) _nx_tcp_syncache_cookie_check(test_key, tuple, 3, irs, count - 1, cookie, &back) == NX_FALSE); + /* The counter at the top of its range. It is read off the clock as a + ULONG and only its low eight bits go into the cookie, so the step after + 0xFFFFFFFF is 0 on a 32-bit ULONG and 0x100000000 on a 64-bit one; both + are the next step. */ + { + ULONG top = 0xFFFFFFFFUL; + ULONG wrap = _nx_tcp_syncache_cookie_build(test_key, tuple, 3, irs, top, 0x2AB); + + ok("minted at counter 0xFFFFFFFF, accepted in its own step", + _nx_tcp_syncache_cookie_check(test_key, tuple, 3, irs, top, wrap, &back) == NX_TRUE && + back == 0x2AB); + ok("accepted one step on, at counter 0", + _nx_tcp_syncache_cookie_check(test_key, tuple, 3, irs, 0UL, wrap, &back) == NX_TRUE && + back == 0x2AB); + ok("accepted one step on, as top + 1 in ULONG arithmetic", + _nx_tcp_syncache_cookie_check(test_key, tuple, 3, irs, top + 1UL, wrap, &back) == NX_TRUE); + ok("refused two steps on, at counter 1", + _nx_tcp_syncache_cookie_check(test_key, tuple, 3, irs, 1UL, wrap, &back) == NX_FALSE); + ok("refused two steps on, as top + 2", + _nx_tcp_syncache_cookie_check(test_key, tuple, 3, irs, top + 2UL, wrap, &back) == NX_FALSE); + ok("refused the step before, 0xFFFFFFFE", + _nx_tcp_syncache_cookie_check(test_key, tuple, 3, irs, 0xFFFFFFFEUL, wrap, &back) == NX_FALSE); + + wrap = _nx_tcp_syncache_cookie_build(test_key, tuple, 3, irs, 0xFFUL, 0x011); + ok("minted at 0xFF, the eight-bit field's top, accepted at 0x100", + _nx_tcp_syncache_cookie_check(test_key, tuple, 3, irs, 0x100UL, wrap, &back) == NX_TRUE && + back == 0x011); + ok("and refused at 0x101", + _nx_tcp_syncache_cookie_check(test_key, tuple, 3, irs, 0x101UL, wrap, &back) == NX_FALSE); + } + + /* The additive carry. The cookie is h1 + irs + (count << 24) + + ((h2 + data) mod 2^24): pick a peer sequence number for which h1 + irs + overflows 32 bits and h2 + data crosses 2^24, so building it carries + out of the option field and checking it, (rest - h2), borrows. Once + at an ordinary counter and once at 0xFFFFFFFF, where the step after + also wraps. Searched, not assumed: h2 depends on irs and the counter. */ + { + static const ULONG counts[2] = { 12345UL, 0xFFFFFFFFUL }; + UINT k; + + for (k = 0; k < 2; k++) + { + ULONG cnt = counts[k]; + ULONG start; + ULONG c_irs; + ULONG h1; + ULONG h2 = 0; + ULONG counted[5]; + ULONG carry_cookie; + ULONG tries; + int found = 0; + + h1 = _nx_tcp_syncache_hash(&test_key[0], tuple, 3) & 0xFFFFFFFFUL; + start = (0xFFFFFFFFUL - h1 + 1UL) & 0xFFFFFFFFUL; + for (tries = 0, c_irs = start; tries < 0x400000UL; tries++, c_irs = (c_irs + 1UL) & 0xFFFFFFFFUL) + { + counted[0] = tuple[0]; + counted[1] = tuple[1]; + counted[2] = tuple[2]; + counted[3] = cnt & 0xFFFFFFFFUL; + counted[4] = c_irs; + h2 = _nx_tcp_syncache_hash(&test_key[2], counted, 5) & 0xFFFFFFFFUL; + if (((h2 & 0x00FFFFFFUL) > (0x00FFFFFFUL - 0x3FFUL)) && + (((h1 + c_irs) & 0xFFFFFFFFUL) < h1)) + { + found = 1; + break; + } + } + printf(" carry vector at counter %08lx: found %d, h1 %08lx irs %08lx h2 %08lx\n", + (unsigned long) cnt, found, (unsigned long) h1, (unsigned long) c_irs, + (unsigned long) h2); + eq("a carry vector was found within the bound", (unsigned long) found, 1); + if (found == 0) + { + continue; + } + + carry_cookie = _nx_tcp_syncache_cookie_build(test_key, tuple, 3, c_irs, cnt, 0x3FF); + ok("h2 + data crosses 2^24 and h1 + irs 2^32: the options round trip", + _nx_tcp_syncache_cookie_check(test_key, tuple, 3, c_irs, cnt, carry_cookie, + &back) == NX_TRUE && back == 0x3FF); + ok("the carry does not move the counter step: accepted one on", + _nx_tcp_syncache_cookie_check(test_key, tuple, 3, c_irs, + (cnt + 1UL) & 0xFFFFFFFFUL, carry_cookie, + &back) == NX_TRUE && back == 0x3FF); + ok("refused two on", + _nx_tcp_syncache_cookie_check(test_key, tuple, 3, c_irs, + (cnt + 2UL) & 0xFFFFFFFFUL, carry_cookie, + &back) == NX_FALSE); + ok("refused one before", + _nx_tcp_syncache_cookie_check(test_key, tuple, 3, c_irs, + (cnt - 1UL) & 0xFFFFFFFFUL, carry_cookie, + &back) == NX_FALSE); + carry_cookie = _nx_tcp_syncache_cookie_build(test_key, tuple, 3, c_irs, cnt, 0x000); + ok("the same irs with data 0, no carry out of the field, round trips", + _nx_tcp_syncache_cookie_check(test_key, tuple, 3, c_irs, cnt, carry_cookie, + &back) == NX_TRUE && back == 0x000); + } + } + /* The peer's own sequence number is bound in, so a cookie cannot be lifted onto a different handshake from the same address. */ ok("refused against a different peer sequence number", @@ -368,6 +580,7 @@ static UINT cookie_case(void) static NX_IP rig_ip; static NX_TCP_LISTEN rig_listen; static NX_TCP_SOCKET rig_socket; +static NX_TCP_SOCKET rig_socket2; /* what a relisten parks next */ static NX_INTERFACE rig_interface; static NX_PACKET rig_packet; @@ -385,7 +598,10 @@ static void rig_reset(void) memset(&rig_ip, 0, sizeof(rig_ip)); memset(&rig_listen, 0, sizeof(rig_listen)); memset(&rig_socket, 0, sizeof(rig_socket)); + memset(&rig_socket2, 0, sizeof(rig_socket2)); memset(&rig_interface, 0, sizeof(rig_interface)); + memset(rig_rx, 0, sizeof(rig_rx)); + memset(rig_rx_live, 0, sizeof(rig_rx_live)); memset(&rig_packet, 0, sizeof(rig_packet)); rig_interface.nx_interface_ip_mtu_size = 1500; @@ -405,6 +621,10 @@ static void rig_reset(void) rig_socket.nx_tcp_socket_state = NX_TCP_LISTEN_STATE; rig_socket.nx_tcp_socket_rx_window_default = 8192; + /* What nx_tcp_server_socket_relisten() hands deliver(): unbound, CLOSED. */ + rig_socket2.nx_tcp_socket_ip_ptr = &rig_ip; + rig_socket2.nx_tcp_socket_state = NX_TCP_CLOSED; + rig_socket2.nx_tcp_socket_rx_window_default = 8192; host_now = 100000; host_ms = 0; @@ -413,6 +633,11 @@ static void rig_reset(void) stub_established = 0; stub_last_socket = NX_NULL; rig_callbacks = 0; + stub_acks = 0; + stub_ack_socket = NX_NULL; + stub_released = 0; + stub_processed = 0; + stub_processed_bytes = 0; _nx_tcp_syncache_initialize(&rig_ip); memcpy(rig_ip.nx_ip_tcp_syncache.nx_tcp_syncache_key, test_key, sizeof(test_key)); @@ -455,6 +680,54 @@ static UINT rig_ack(ULONG n, ULONG irs, ULONG iss) &rig_interface, NX_TRUE, 888); } +/* A segment from the peer of a connection waiting for accept, as + _nx_tcp_packet_process would hand it to _nx_tcp_syncache_hold: header in + host order, `len` bytes of data after it. */ +static NX_PACKET *rig_segment(int slot, ULONG word_3, ULONG seq, ULONG ack, ULONG len) +{ + NX_PACKET *p = &rig_rx[slot]; + NX_TCP_HEADER *h = (NX_TCP_HEADER *) rig_rx_data[slot]; + + memset(p, 0, sizeof(*p)); + memset(rig_rx_data[slot], 0, sizeof(rig_rx_data[slot])); + h -> nx_tcp_header_word_3 = (5UL << NX_TCP_HEADER_SHIFT) | word_3 | 8192UL; + h -> nx_tcp_sequence_number = seq; + h -> nx_tcp_acknowledgment_number = ack; + p -> nx_packet_prepend_ptr = rig_rx_data[slot]; + /* Twenty bytes of header on the wire, the data offset above: not + sizeof(NX_TCP_HEADER), which is wider with a 64-bit ULONG. */ + p -> nx_packet_append_ptr = rig_rx_data[slot] + 20 + len; + p -> nx_packet_length = 20UL + len; + rig_rx_live[slot] = 1; + return(p); +} + +/* A connection the cache handed over and accept has not been called on is + left as upstream left one: bound, in LISTEN, with the peer's port + (2d44d563). */ +static int rig_awaiting_accept(NX_TCP_SOCKET *socket_ptr, ULONG n) +{ + return((socket_ptr -> nx_tcp_socket_state == NX_TCP_LISTEN_STATE) && + (socket_ptr -> nx_tcp_socket_bound_next != NX_NULL) && + (socket_ptr -> nx_tcp_socket_connect_port == (UINT) (30000u + (n & 0xFFFu)))); +} + +/* Fill the cache with answered handshakes while a socket is parked, then take + the socket away: the cache is full and a SYN can only be answered with a + cookie, with no socket on the port (96502647: with no socket a SYN that + finds room is deferred, so the cache is filled while one is parked). */ +static void rig_fill_then_unpark(ULONG base, ULONG irs) +{ + ULONG i; + + rig_listen.nx_tcp_listen_socket_ptr = &rig_socket; + for (i = 0; i < NX_TCP_SYNCACHE_SIZE; i++) + { + (void) rig_syn(base + i, irs + i); + } + rig_listen.nx_tcp_listen_socket_ptr = NX_NULL; +} + static UINT cache_case(void) { NX_TCP_SYNCACHE *cache = &rig_ip.nx_ip_tcp_syncache; @@ -480,11 +753,23 @@ static UINT cache_case(void) ok("the ACK is consumed", rig_ack(1, 0x1000, iss_first) == NX_TRUE); eq("the entry is given back", cache -> nx_tcp_syncache_count, 0); - eq("the connection is established", (unsigned long) stub_established, 1); eq("the application is told once", (unsigned long) rig_callbacks, 1); ok("the listen request's socket has been taken", rig_listen.nx_tcp_listen_socket_ptr == NX_NULL); + /* 2d44d563: accept moves it on, not the ACK. */ + eq("the connection waits for accept, not established by the ACK", + (unsigned long) stub_established, 0); + ok("on the parked socket, bound in LISTEN with the peer's port", + rig_awaiting_accept(&rig_socket, 1)); + eq("and nothing is sent while it waits", (unsigned long) stub_acks, 0); + ok("accept connects it", _nx_tcp_syncache_accept(&rig_socket) == NX_TRUE); + eq("the connection is established", (unsigned long) stub_established, 1); ok("and it is the socket that was parked", stub_last_socket == &rig_socket); + eq("accept sends one ACK, which opens the window", (unsigned long) stub_acks, 1); + ok("from that socket, at iss + 1, acknowledging irs + 1", + stub_ack_socket == &rig_socket && stub_ack_seq == iss_first + 1 && stub_ack_ack == 0x1001); + ok("a second accept is not a second connection", + _nx_tcp_syncache_accept(&rig_socket) == NX_FALSE && stub_established == 1); eq("with the sequence numbers the handshake agreed", rig_socket.nx_tcp_socket_tx_sequence, (unsigned long) (iss_first + 1)); eq("and the peer's", rig_socket.nx_tcp_socket_rx_sequence, 0x1001); @@ -504,6 +789,35 @@ static UINT cache_case(void) rig_socket.nx_tcp_socket_ts_recent, 888); #endif + /* Data the peer sends before accept is held on the socket unacknowledged + and processed by accept, in order (2d44d563); a segment that does not + continue it is released. */ + rig_reset(); + rig_listen.nx_tcp_listen_socket_ptr = &rig_socket; + { + ULONG iss = rig_syn(2, 0x1100); + + ok("a handshake to hold data for", rig_ack(2, 0x1100, iss) == NX_TRUE && + rig_awaiting_accept(&rig_socket, 2)); + ok("in-order data is held", + _nx_tcp_syncache_hold(&rig_socket, + rig_segment(0, NX_TCP_ACK_BIT | NX_TCP_PSH_BIT, 0x1101, iss + 1, 10)) == NX_TRUE); + ok("a segment that does not follow it is taken", + _nx_tcp_syncache_hold(&rig_socket, + rig_segment(1, NX_TCP_ACK_BIT, 0x1101 + 500, iss + 1, 10)) == NX_TRUE); + eq("and released, not held", (unsigned long) stub_released, 1); + eq("one segment is held", rig_socket.nx_tcp_socket_receive_queue_count, 1); + eq("nothing was acknowledged while held", (unsigned long) stub_acks, 0); + eq("nor processed", (unsigned long) stub_processed, 0); + ok("accept connects it", _nx_tcp_syncache_accept(&rig_socket) == NX_TRUE); + eq("and processes what was held", (unsigned long) stub_processed, 1); + eq("all ten bytes of it", (unsigned long) stub_processed_bytes, 10); + eq("leaving nothing on the hold queue", rig_socket.nx_tcp_socket_receive_queue_count, 0); + eq("after the one window-opening ACK", (unsigned long) stub_acks, 1); + eq("and no packet is left owned by the cache", + (unsigned long) (rig_rx_live[0] + rig_rx_live[1]), 0); + } + rig_reset(); rig_listen.nx_tcp_listen_socket_ptr = &rig_socket; @@ -540,6 +854,9 @@ static UINT cache_case(void) eq("with no entry ever stored for it", cache -> nx_tcp_syncache_count, NX_TCP_SYNCACHE_SIZE); eq("the cookie was recognised", cache -> nx_tcp_syncache_cookies_valid, 1); + ok("the connection waits for accept on the parked socket (2d44d563)", + stub_established == 0 && rig_awaiting_accept(&rig_socket, 9999)); + ok("and accept connects it", _nx_tcp_syncache_accept(&rig_socket) == NX_TRUE); eq("the connection is established", (unsigned long) stub_established, 1); #ifdef NX_ENABLE_TCP_WINDOW_SCALING eq("the window scale survived the cookie", @@ -604,41 +921,76 @@ static UINT cache_case(void) _nx_tcp_syncache_periodic(&rig_ip); eq("the entry is gone", cache -> nx_tcp_syncache_count, 0); - /* The clock has moved less than one cookie counter step, so the ACK is - still inside the window. This is why a cached entry's sequence number - is a cookie too: the client's handshake is not lost with the entry. */ - ok("but the acknowledgment still completes it", - rig_ack(77, 0x6000, iss_first) == NX_TRUE); - eq("from the cookie alone", cache -> nx_tcp_syncache_cookies_valid, 1); - eq("and the connection is made", (unsigned long) stub_established, 1); - - rig_reset(); - rig_listen.nx_tcp_listen_socket_ptr = NX_NULL; - - for (i = 0; i < 8; i++) + /* The clock has moved less than one cookie counter step, so a cookie + minted with the SYN would still be inside the window. A cached entry's + sequence number is not a cookie (e5e89f1b): the handshake is lost with + the entry. What this rig proves is the cache's side only: the ACK is + not consumed (NX_FALSE) and the cache sends nothing for it. It does + not link _nx_tcp_packet_process and is no evidence of the RST that + goes on the wire; the fork's netx_3_06, 8_02 and 10_23_01 and + ctl_syncookie check that. */ + stub_rsts = 0; + stub_synacks = 0; + ok("the acknowledgment of an expired cached entry is not consumed (NX_FALSE)", + rig_ack(77, 0x6000, iss_first) == NX_FALSE); + eq("it does not decode as a cookie", cache -> nx_tcp_syncache_cookies_valid, 0); + eq("and is counted as not one", cache -> nx_tcp_syncache_cookies_invalid, 1); + eq("no connection is made", (unsigned long) stub_established, 0); + ok("or rebuilt onto the parked socket", + rig_listen.nx_tcp_listen_socket_ptr == &rig_socket && + rig_socket.nx_tcp_socket_state == NX_TCP_LISTEN_STATE && + rig_socket.nx_tcp_socket_connect_port == 0); + eq("the cache sends nothing for it itself", (unsigned long) (stub_rsts + stub_synacks), 0); + eq("and holds nothing for it", cache -> nx_tcp_syncache_count, 0); + + /* The peer starts again, and that works. */ { - ULONG iss = rig_syn(4000 + i, 0x7000 + i); - - (void) rig_ack(4000 + i, 0x7000 + i, iss); + ULONG iss = rig_syn(77, 0x6100); + + eq("a fresh SYN from the same peer is answered", (unsigned long) stub_synacks, 1); + ok("with a new sequence number", iss != iss_first); + ok("and its ACK completes the handshake", rig_ack(77, 0x6100, iss) == NX_TRUE && + rig_awaiting_accept(&rig_socket, 77)); + ok("which accept connects", _nx_tcp_syncache_accept(&rig_socket) == NX_TRUE && + stub_established == 1); } - eq("a full backlog waits", cache -> nx_tcp_syncache_accept_count, 8); - eq("and none of them reset the peer", (unsigned long) stub_rsts, 0); - eq("and no socket was committed", (unsigned long) stub_established, 0); + /* A SYN with no socket parked is deferred and not answered (96502647), + so a finished handshake with no socket comes from SYNs answered while + one was parked: ten arrive, the first ACK takes the socket, the next + eight queue for accept, and the tenth is past the backlog. */ + rig_reset(); + rig_listen.nx_tcp_listen_socket_ptr = &rig_socket; { - ULONG iss = rig_syn(4008, 0x7008); + ULONG iss[10]; - (void) rig_ack(4008, 0x7008, iss); + for (i = 0; i < 10; i++) + { + iss[i] = rig_syn(4000 + i, 0x7000 + i); + } + eq("ten SYNs answered while a socket is parked", (unsigned long) stub_synacks, 10); + ok("the first ACK takes the parked socket", + rig_ack(4000, 0x7000, iss[0]) == NX_TRUE && rig_awaiting_accept(&rig_socket, 4000)); + for (i = 1; i < 9; i++) + { + (void) rig_ack(4000 + i, 0x7000 + i, iss[i]); + } + eq("a full backlog waits", cache -> nx_tcp_syncache_accept_count, 8); + eq("and none of them reset the peer", (unsigned long) stub_rsts, 0); + eq("and no socket was committed", (unsigned long) stub_established, 0); + + (void) rig_ack(4009, 0x7009, iss[9]); } eq("past the backlog the queue does not grow", cache -> nx_tcp_syncache_accept_count, 8); eq("and the peer is told, rather than left hanging", (unsigned long) stub_rsts, 1); - rig_socket.nx_tcp_socket_state = NX_TCP_CLOSED; ok("relisten takes one", _nx_tcp_syncache_deliver(&rig_ip, &rig_listen, - &rig_socket) == NX_TRUE); + &rig_socket2) == NX_TRUE); eq("the queue shortens", cache -> nx_tcp_syncache_accept_count, 7); + ok("onto the relistened socket, waiting for accept", rig_awaiting_accept(&rig_socket2, 4001)); + ok("which accept connects", _nx_tcp_syncache_accept(&rig_socket2) == NX_TRUE); eq("and the connection reaches the application", (unsigned long) stub_established, 1); @@ -650,13 +1002,9 @@ static UINT cache_case(void) eq("and resets every peer waiting on it", (unsigned long) stub_rsts, 7); rig_reset(); - rig_listen.nx_tcp_listen_socket_ptr = NX_NULL; rig_listen.nx_tcp_listen_rx_window = 65536 * 4; /* needs a scale */ - for (i = 0; i < NX_TCP_SYNCACHE_SIZE; i++) - { - (void) rig_syn(6000 + i, 0xA000 + i); - } + rig_fill_then_unpark(6000, 0xA000); iss_last = rig_syn(6999, 0xB000); /* past full: a cookie */ eq("the cookie SYN-ACK was sent with no socket on the port", cache -> nx_tcp_syncache_cookies_sent, 1); @@ -664,9 +1012,10 @@ static UINT cache_case(void) ok("and it announced a window scale", stub_synack_scale > 0); #endif - rig_socket.nx_tcp_socket_state = NX_TCP_LISTEN_STATE; rig_listen.nx_tcp_listen_socket_ptr = &rig_socket; - ok("the cookie completes", rig_ack(6999, 0xB000, iss_last) == NX_TRUE); + ok("the cookie completes", rig_ack(6999, 0xB000, iss_last) == NX_TRUE && + cache -> nx_tcp_syncache_cookies_valid == 1 && + rig_awaiting_accept(&rig_socket, 6999)); #ifdef NX_ENABLE_TCP_WINDOW_SCALING eq("with the scale the SYN-ACK announced, not the parked socket's", rig_socket.nx_tcp_rcv_win_scale_value, stub_synack_scale); @@ -695,20 +1044,18 @@ static UINT cache_case(void) } rig_reset(); - rig_listen.nx_tcp_listen_socket_ptr = NX_NULL; rig_listen.nx_tcp_listen_rx_window = 100352; rig_listen.nx_tcp_listen_rx_window_maximum = 262144; - for (i = 0; i < NX_TCP_SYNCACHE_SIZE; i++) - { - (void) rig_syn(7000 + i, 0xA100 + i); - } + rig_socket.nx_tcp_socket_rx_window_default = 100352; + rig_fill_then_unpark(7000, 0xA100); + stub_synack_scale = 0; iss_last = rig_syn(7999, 0xB100); /* past full: a cookie */ + eq("past full with no socket parked, a cookie", cache -> nx_tcp_syncache_cookies_sent, 1); eq("a cookie SYN-ACK announces the grown window's scale too", stub_synack_scale, 3); - rig_socket.nx_tcp_socket_state = NX_TCP_LISTEN_STATE; - rig_socket.nx_tcp_socket_rx_window_default = 100352; rig_listen.nx_tcp_listen_socket_ptr = &rig_socket; - ok("the cookie completes", rig_ack(7999, 0xB100, iss_last) == NX_TRUE); + ok("the cookie completes", rig_ack(7999, 0xB100, iss_last) == NX_TRUE && + cache -> nx_tcp_syncache_cookies_valid == 1); eq("and the ACK reconstructs that scale, not the advertised window's", rig_socket.nx_tcp_rcv_win_scale_value, 3); @@ -721,11 +1068,15 @@ static UINT cache_case(void) eq("no maximum: the scale is the advertised window's", stub_synack_scale, 1); #endif + /* Two SYNs answered while one socket is parked: the first ACK takes the + socket, the second finishes with none (96502647). */ rig_reset(); - rig_listen.nx_tcp_listen_socket_ptr = NX_NULL; + rig_listen.nx_tcp_listen_socket_ptr = &rig_socket; { + ULONG iss87 = rig_syn(87, 0xBF00); ULONG iss = rig_syn(88, 0xC000); + (void) rig_ack(87, 0xBF00, iss87); (void) rig_ack(88, 0xC000, iss); } eq("one finished handshake is waiting", cache -> nx_tcp_syncache_accept_count, 1); @@ -836,36 +1187,32 @@ static UINT cache_case(void) } rig_reset(); - rig_listen.nx_tcp_listen_socket_ptr = NX_NULL; + rig_listen.nx_tcp_listen_socket_ptr = &rig_socket; host_ms = 3000; { + ULONG iss96 = rig_syn(98, 0xE380); ULONG iss = rig_syn(97, 0xE400); host_ms = 3040; + (void) rig_ack(98, 0xE380, iss96); /* takes the socket */ ok("with no socket parked the handshake is queued", rig_ack(97, 0xE400, iss) == NX_TRUE); eq("in the accept queue", cache -> nx_tcp_syncache_accept_count, 1); host_ms = 9000; - rig_socket.nx_tcp_socket_state = NX_TCP_LISTEN_STATE; - rig_listen.nx_tcp_listen_socket_ptr = &rig_socket; ok("a relisten takes it", _nx_tcp_syncache_deliver(&rig_ip, &rig_listen, - &rig_socket) == NX_TRUE); + &rig_socket2) == NX_TRUE); eq("with the round trip of the handshake, not of the wait", - rig_socket.nx_tcp_socket_handshake_rtt, 40); + rig_socket2.nx_tcp_socket_handshake_rtt, 40); } rig_reset(); - rig_listen.nx_tcp_listen_socket_ptr = NX_NULL; host_ms = 4000; - for (i = 0; i < NX_TCP_SYNCACHE_SIZE; i++) - { - (void) rig_syn(8000 + i, 0xF000 + i); - } + rig_fill_then_unpark(8000, 0xF000); iss_last = rig_syn(8999, 0xF999); /* past full: a cookie */ host_ms = 4030; - rig_socket.nx_tcp_socket_state = NX_TCP_LISTEN_STATE; rig_listen.nx_tcp_listen_socket_ptr = &rig_socket; - ok("a cookie completes", rig_ack(8999, 0xF999, iss_last) == NX_TRUE); + ok("a cookie completes", rig_ack(8999, 0xF999, iss_last) == NX_TRUE && + cache -> nx_tcp_syncache_cookies_valid == 1); eq("and carries no round trip: nothing was stored to time it", rig_socket.nx_tcp_socket_handshake_rtt, 0); @@ -891,6 +1238,8 @@ int main(int argc, char **argv) return 2; } + failures += stub_violations; + if (failures != 0) { printf("syncache %s: %d failures\n", which, failures); diff --git a/tests/syncache/host/ulong64/tx_port.h b/tests/syncache/host/ulong64/tx_port.h new file mode 100644 index 000000000..a2583ecfb --- /dev/null +++ b/tests/syncache/host/ulong64/tx_port.h @@ -0,0 +1,26 @@ +/* + * AmiNetXDuo, host shim for tests/syncache: ThreadX's port header with ULONG + * 64 bits wide, the one width tests/perf/host/shim does not give. + * + * SPDX-License-Identifier: MIT + */ + +#ifndef AMINETXDUO_HOST_TX_PORT_ULONG64 +#define AMINETXDUO_HOST_TX_PORT_ULONG64 + +#ifndef AMINETXDUO_HOST_VENDORED_TX_PORT +#error "AMINETXDUO_HOST_VENDORED_TX_PORT must name the tx_port.h to wrap" +#endif + +#define LONG aminetxduo_host_ulong64_LONG +#define ULONG aminetxduo_host_ulong64_ULONG +#include AMINETXDUO_HOST_VENDORED_TX_PORT +#undef LONG +#undef ULONG + +typedef long LONG; +typedef unsigned long ULONG; + +_Static_assert(sizeof(ULONG) == 8, "this shim is for a 64-bit ULONG"); + +#endif /* AMINETXDUO_HOST_TX_PORT_ULONG64 */ diff --git a/tests/x509/CMakeLists.txt b/tests/x509/CMakeLists.txt index 5775ee10c..5fd7e49ec 100644 --- a/tests/x509/CMakeLists.txt +++ b/tests/x509/CMakeLists.txt @@ -37,11 +37,6 @@ list(FILTER _x509_secure_all EXCLUDE REGEX file(GLOB _x509_crypto_all "${_x509_crypto_src}/*.c") list(FILTER _x509_crypto_all EXCLUDE REGEX "self_test") -# RFC 7301. src/tls/CMakeLists.txt appends the same file to the shipping -# nx_secure archive; the ClientHello and ServerHello extension builders call -# into it, so an archive without it does not link. -list(APPEND _x509_secure_all "${CMAKE_SOURCE_DIR}/src/tls/alpn/nx_secure_tls_alpn.c") - add_library(test_x509_nx_secure STATIC ${_x509_secure_all}) add_library(test_x509_nx_crypto STATIC ${_x509_crypto_all}) diff --git a/tests/x509/test_tls_x509.c b/tests/x509/test_tls_x509.c index 8647ee24a..d7eb5cc62 100644 --- a/tests/x509/test_tls_x509.c +++ b/tests/x509/test_tls_x509.c @@ -766,9 +766,23 @@ UINT params_offset; } } +/* nx_secure_tls_session_create_ext() carves a session's CertificateVerify + scratch out of the crypto metadata area and points the session at it. + test_pss_schemes() zeroes a bare session instead, so it gives the session + the same area itself, ULONG-aligned as the carve is, after each reset. */ +static void attach_certificate_verify_scratch(NX_SECURE_TLS_SESSION *session, + ULONG *scratch) +{ + session -> nx_secure_tls_certificate_verify_scratch = scratch; + session -> nx_secure_tls_certificate_verify_scratch_size = + NX_SECURE_TLS_CERTIFICATE_VERIFY_SCRATCH_SIZE; +} + static void test_pss_schemes(void) { NX_SECURE_TLS_SESSION session; +ULONG cv_scratch[(NX_SECURE_TLS_CERTIFICATE_VERIFY_SCRATCH_SIZE + + sizeof(ULONG) - 1) / sizeof(ULONG)]; NX_SECURE_X509_CRYPTO method; NX_SECURE_X509_CERT certificate; UCHAR verify_message[4]; @@ -780,6 +794,7 @@ UINT status; printf("pss schemes\n"); memset(&session, 0, sizeof(session)); + attach_certificate_verify_scratch(&session, cv_scratch); memset(&method, 0, sizeof(method)); session.nx_secure_tls_1_3 = 1; method.nx_secure_x509_public_cipher_method = &crypto_method_rsa; @@ -800,6 +815,7 @@ UINT status; "SHA-512 advertises both PSS key encodings"); memset(&session, 0, sizeof(session)); + attach_certificate_verify_scratch(&session, cv_scratch); memset(&certificate, 0, sizeof(certificate)); status = _nx_secure_x509_certificate_initialize(&certificate, (UCHAR *)x509_psskey_root, diff --git a/third_party/netxduo b/third_party/netxduo index 7bf58f91a..cbba359ad 160000 --- a/third_party/netxduo +++ b/third_party/netxduo @@ -1 +1 @@ -Subproject commit 7bf58f91a53ad6d73ccad2da7edfbaaac21f430f +Subproject commit cbba359adb35e60df49cb1ad94839ba06e6494ac diff --git a/tools/check-hotpath-budget.sh b/tools/check-hotpath-budget.sh index e4680e3b9..c560e693a 100755 --- a/tools/check-hotpath-budget.sh +++ b/tools/check-hotpath-budget.sh @@ -130,7 +130,7 @@ TABLE = { # on the data-ACK ramp's limit, so a 256 KB receive window (the WAN case # of bsdsocket_window.h) does not acknowledge every 128 KB. Four # instructions per segment, bought on purpose. - "nx_tcp_socket_state_data_check.c": [("__nx_tcp_socket_state_data_check", 483)], # 429; 483 counts + "nx_tcp_socket_state_data_check.c": [("__nx_tcp_socket_state_data_check", 493)], # 429; 483 counts # the queue-cap drop # in the current default # arm (off the in-order path) diff --git a/tools/check-ram-size.sh b/tools/check-ram-size.sh index d630cd0d8..7eb27c2a0 100755 --- a/tools/check-ram-size.sh +++ b/tools/check-ram-size.sh @@ -58,11 +58,11 @@ ARM="${AMINETXDUO_RAM_ARM:-$(basename "$BUILD")}" # the same listener and pointer costs, with no mDNS. DHCP/DNS packet storage # remains dynamic and is therefore not hidden in this resident allocation. BUDGETS=( - "default:68608" - "minimal:17408" + "default:68836" + "minimal:17928" # First budgeted as a shipping profile at 0.28.9: 15,716 bytes, with # headroom to the next KiB boundary. - "micro:16384" + "micro:16388" ) budget="" diff --git a/tools/ci.sh b/tools/ci.sh index 6e987ff61..893f65a28 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -470,7 +470,10 @@ host_test_targets() { # builddir # 551 with tls_send_record_ownership (GHSA-8w5x-ff58-2fr2): a TLS # record's packet chain is left alone once TCP has accepted it, and # wiped only on a failed send (all hosts). -HOST_TESTS_EXPECTED=551 +# 553 with syncache_cookie_ulong64/_cache_ulong64: the SYN cache's +# cookie, counter wrap, carry and cache with ULONG 64 bits wide +# (all hosts). +HOST_TESTS_EXPECTED=553 case "$(uname -m)" in x86_64|amd64) ;; # test_inet, test_route, test_expunge, test_expunge_cork, test_select,