1
0
mirror of https://github.com/FreeRTOS/FreeRTOS-Plus-TCP synced 2025-10-21 15:10:39 +08:00
Files
FreeRTOS-Plus-TCP/source/FreeRTOS_TCP_IP.c
Andreas Nordal f5cbeb5238 Let's fix the tests enough to run with AddressSanitizer and UB Sanitizer and enable those in CI (#1151)
* unit-test CMake: Add option to build with sanitizers

These build options affect both the tests and the code under test when
built from the unit-test CMake file.

Example:

    cmake -DSANITIZE=address,undefined

To reset all options:

    cmake --fresh

Meson users will find this familiar:

    meson -Db_sanitize=…
    
(When in doubt in CMake, implement what Meson provides out of the box.)

Motivation:
ASan and UBSan currently finds a lot of crashy problems with the unit-tests,
and makes them visible in plain sight.

* unit-test CMake: Remove compile_options(-O0 -Wno-div-by-zero)

Let's not override optimization options: This is surprising when
the cmake user tries to set CMAKE_BUILD_TYPE=(Debug|Release)'.

The -Wno-div-by-zero warning disabling seems obsolete:
Replacing it with -Werror did not fail, at least with Gcc 13.

* unit-test: Fix missing symbol in a few tests (linker error)

I don't know why I get to resolve these, but in all cases,
it is FreeRTOS_Sockets.c that is dragging in a dependency on
xTCPWindowLoggingLevel, causing a few tests to fail to link:

    FreeRTOS_Sockets.c:5118:(.text+0x18fa2):
    undefined reference to `xTCPWindowLoggingLevel'

Since it's one external variable, let's add it to the necessary unittests.

Also under the headline of extern variables:
The IPv6 address, which was not there for linkage, could be made const.

* unit-test: Fix segfault due to discrepancy between the real and mocked recvfrom

Symptom:
test_vDHCPProcess_eWaitingOffer_CorrectState_ValidBytesInMessage_MatchingEndPoint()
segfaults.

What AddressSanitizer says about that:

    test/unit-test/build/Annexed_TCP_Sources/FreeRTOS_DHCP.c:1139:28: runtime error:
        member access within null pointer of type 'const struct DHCPMessage_IPv4_t'
    AddressSanitizer:DEADLYSIGNAL
    =================================================================
    ==14403==ERROR: AddressSanitizer: SEGV on unknown address 0x0000000000ec
    ==14403==The signal is caused by a READ memory access.
    ==14403==Hint: address points to the zero page.
        #0 0x456eb7 in prvIsValidDHCPResponse test/unit-test/build/Annexed_TCP_Sources/FreeRTOS_DHCP.c:1139
        #1 0x4584c3 in prvProcessDHCPReplies test/unit-test/build/Annexed_TCP_Sources/FreeRTOS_DHCP.c:1280
        #2 0x45038c in xHandleWaitingOffer test/unit-test/build/Annexed_TCP_Sources/FreeRTOS_DHCP.c:334
        #3 0x45366a in vDHCPProcessEndPoint test/unit-test/build/Annexed_TCP_Sources/FreeRTOS_DHCP.c:735
        #4 0x44fe57 in vDHCPProcess test/unit-test/build/Annexed_TCP_Sources/FreeRTOS_DHCP.c:263
        #5 0x418d2c in test_vDHCPProcess_eWaitingOffer_CorrectState_ValidBytesInMessage_MatchingEndPoint test/unit-test/FreeRTOS_DHCP/FreeRTOS_DHCP_utest.c:147

Diagnosis:
pxDHCPMessage in prvProcessDHCPReplies() is the unlucky null pointer.
As commented, it is expected to be set as an out-arg of FreeRTOS_recvfrom()
due to calling it with FREERTOS_ZERO_COPY, but the condition for it
in the mocked FreeRTOS_recvfrom() is that the sum of all flags
is FREERTOS_ZERO_COPY + FREERTOS_MSG_PEEK.

Finding the right fix:
Should we add a null check? Nope.
Set the FREERTOS_MSG_PEEK flag? Nope.
The mocked function did not check the FREERTOS_ZERO_COPY flag properly.
Observe that in the real FreeRTOS_recvfrom(),
specifically inside prvRecvFrom_CopyPacket(),
the condition for setting the zero-copy pointer into the buffer with the data
depends only on one flag - FREERTOS_ZERO_COPY - and ignores the rest.
It is obviously important that the mocked condition is exactly the same.

* FreeRTOS_ND_utest: Fix segfaults caused by no ethernet buffer

* test_prvProcessEthernetPacket_*(): Fix memset(NULL, …) segfaults

The pointer was used before initialized.
If it happened to be NULL, the test would segfault.

* unit-test: Fix pxEthernetBuffer[-ipIP_TYPE_OFFSET] buffer underflows

The tested functions intentionally expect there to be bytes before
the ethernet buffer:

* test_FreeRTOS_GetUDPPayloadBuffer_*():
  The code under test, FreeRTOS_GetUDPPayloadBuffer_Multi,
  writes 6 bytes before the ethernet buffer. This looks
  intentional, as the write is commented as doing that.
* FreeRTOS_IP_utest:
  The code under test, prvProcessIPPacket() intentionally
  writes a byte at offset -ipIP_TYPE_OFFSET into its
  ethernet buffer.

I am thankful for the generous comment about the ipIP_TYPE_OFFSET.

* test_vTCPWindowDestroy_list_length_not_zero(): Fix buffer overflow due to struct interposing

The test was crashing due to what AddressSanitizer calls a buffer overflow,
or really, interposing a TCPSegment_t on top of a TCPWindow_t::xRxSegments
member and accessing an interposed struct member that fell outside the
underlying TCPWindow_t struct.

The naive fix - not doing that - works:

     void test_vTCPWindowDestroy_list_length_not_zero( void )
     {
         TCPWindow_t xWindow = { 0 };
    -    List_t * pxSegments = &( xWindow.xRxSegments );
    +    TCPSegment_t xSegment = { 0 };

         listLIST_IS_INITIALISED_ExpectAnyArgsAndReturn( pdFALSE );
         listLIST_IS_INITIALISED_ExpectAnyArgsAndReturn( pdTRUE );
         listCURRENT_LIST_LENGTH_ExpectAnyArgsAndReturn( 1 );
    -    listGET_OWNER_OF_HEAD_ENTRY_ExpectAnyArgsAndReturn( pxSegments );
    +    listGET_OWNER_OF_HEAD_ENTRY_ExpectAnyArgsAndReturn( &xSegment );
         /* ->vTCPWindowFree */
    -    uxListRemove_ExpectAnyArgsAndReturn( pdTRUE );
    -    uxListRemove_ExpectAnyArgsAndReturn( pdTRUE );
         listCURRENT_LIST_LENGTH_ExpectAnyArgsAndReturn( 0 );

         vTCPWindowDestroy( &xWindow );
     }

However, this became a different test, as evidenced by the less than 100%
line coverage, that two function call expectations had to go, and that it
functionally became an exact copy of the next test.
To reach the holes in the test coverage opened by the naive fix,
the two list items' container pointers also needed and sufficed to be set.

* test_eARPGetCacheEntryByMac_OneMatchingEntry(): Arrest dangling pointer

This test was using the stack of a previously returned function
(probably a previous test). Highlights from AddressSanitizer output:

    ==15832==ERROR: AddressSanitizer: stack-use-after-return
    READ of size 8 at 0x7fdefb013670 thread T0
    #0 0x4325bf in eARPGetCacheEntryByMac source/FreeRTOS_ARP.c:930
    #1 0x421a71 in test_eARPGetCacheEntryByMac_OneMatchingEntry
        (test/unit-test/build/bin/tests/FreeRTOS_ARP_utest+0x421a71)

    Address 0x7fdefb013670 is located in stack of thread T0 at offset 624 in frame
    #0 0x41f941 in test_vARPRefreshCacheEntry_IPAndMACInDifferentLocations1
        (test/unit-test/build/bin/tests/FreeRTOS_ARP_utest+0x41f941)

    This frame has 2 object(s):
    [48, 54) 'xMACAddress' (line 1937)
    [80, 640) 'xEndPoint' (line 1941) <== Memory access at offset 624 is inside this variable

Nulling the dangling pointer is enough to fix the test,
but in order to keep the 100% line coverage,
it must point at somewhere valid.
Therefore doing that.

* FreeRTOS_TCP_Transmission_utest: Fix stack use after return: Point at own endpoint

* FreeRTOS_TCP_IP_utest.c: Fix buffer overflow

* prvTCPNextTimeout(): Fix leftshift by ~0 encountered in unittest

This expression is obviously undefined when ucRepCount is 0 (leftshift by ~0):

    3000U << ( ucRepCount - 1U )

Which is fine if that is impossible. But is it? This case is handled later by
clamping the result from 0 to 1 (which hints at how this accidentally works),
and this is being tested for (in FreeRTOS_TCP_IP_utest.c::
test_prvTCPNextTimeout_ConnSyn_State_Active_Rep0).

I'm also surprised that neither Gcc or Clang optimizes the UB away
(which would make the code behave differently with optimization):

    1500U << ucRepCount

It is very tempting to apply this fix, but 1ms is very different from 1500ms.
That may well speak more for lowering the scale factor than making exceptions,
though. But not now: For the purpose of fixing sanitizer failures,
let's preserve the behaviour for now.

* FreeRTOS_DNS_Callback_utest: Fix buffer overflow caused by mocked malloc

* test_vReceiveRA_vRAProcess(): Fix buffer overflow in test: Don't cast

The struct used as ethernet buffer did not contain the supposed data.
The supposed data, however, seems to be correct based on this resource:

    https://support.huawei.com/enterprise/en/doc/
    EDOC1100174721/8ebcb3c3/icmpv6-router-advertisement-ra-message

AddressSanitizer called it a buffer overflow just because the buffer
happened to be shorter than the supposed data.

To make this evident and let type safety prevent this from compiling
the wrong way, let's define a struct that contains the right data,
and take pointers from the addresses of members instead of casting
and doing manual offset calculations as far as possible.

Also remove unused variables.

I also wonder if the first test is not a subset of the second.
It causes a subset of things to happen in the code under test,
and their names only differ by a typo.

* test_DNS_ParseDNSReply_answer_lmmnr_reply3(): Fix test failure due to use before initialization

* DNS: test_SendRequest_fail(): Fix testing the failure scenario

* FreeRTOS_DNS_utest.c: Fix size to copy when copying pointers instead of target

* unit-test of DHCP option parser: Delete the buffer overflow

Symptom:

    AddressSanitizer: dynamic-stack-buffer-overflow on address 0x7ffc9dfa5c07
    READ of size 1 at 0x7ffc9dfa5c07 thread T0
    #0 0x459a49 in prvProcessDHCPReplies test/unit-test/build/Annexed_TCP_Sources/FreeRTOS_DHCP.c:1310
    #1 0x4526d2 in vHandleWaitingAcknowledge test/unit-test/build/Annexed_TCP_Sources/FreeRTOS_DHCP.c:495
    #2 0x4544ef in vDHCPProcessEndPoint test/unit-test/build/Annexed_TCP_Sources/FreeRTOS_DHCP.c:739
    #3 0x43dbd9 in test_vDHCPProcess_eWaitingAcknowledge_DNSIncorrectLength2
        (test/unit-test/build/bin/tests/FreeRTOS_DHCP_utest+0x43dbd9)

    Address 0x7ffc9dfa5c07 is located in stack of thread T0
    SUMMARY: AddressSanitizer: dynamic-stack-buffer-overflow
        test/unit-test/build/Annexed_TCP_Sources/FreeRTOS_DHCP.c:1310 in prvProcessDHCPReplies
    Shadow bytes around the buggy address:
    0x7ffc9dfa5980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x7ffc9dfa5a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x7ffc9dfa5a80: 00 00 00 00 00 00 00 00 ca ca ca ca 00 00 00 00
    0x7ffc9dfa5b00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x7ffc9dfa5b80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    =>0x7ffc9dfa5c00:[05]cb cb cb cb cb cb cb 00 00 00 00 00 00 00 00
    0x7ffc9dfa5c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x7ffc9dfa5d00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x7ffc9dfa5d80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x7ffc9dfa5e00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x7ffc9dfa5e80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    Shadow byte legend (one shadow byte represents 8 application bytes):
    Addressable:           00
    Partially addressable: 01 02 03 04 05 06 07
    Left alloca redzone:   ca
    Right alloca redzone:  cb

There were two problems that conspired to create this segfault. The first
was allowing the option parser to run off the end of the buffer at all:

    /* ulGenericLength is incremented by 100 to have uxDNSCount > ipconfigENDPOINT_DNS_ADDRESS_COUNT scenario */
    ulGenericLength = sizeof( DHCPMsg ) + 100;

The second problem was letting it overshoot the stop byte (0xFF).
Which is a problem with having manually updated indexes and length fields.
The stop byte was at the end of the buffer, but was of no help, because
the buffer length was off by -3 (missing 2 bytes for the opcode and
length field of the 6 server addresses and 1 byte to account for an
unexplained hole in the serialized stream).

The real fix for this kind of fragility is using some helper funcitons
for serializing the data while keeping indexes and lenghts consistent
(not to mention collapsing repeated lines 8-fold).
Anyway, it is trivial to add a check that the serialized stream ends
at the end of the buffer (done).
Whether to add a functioning stop byte does not matter
and should not be needed anymore with such a check.

I initially fixed it the wrong way, by keeping it within the same buffer,
which hurt line coverage. But what the test wants to test is as commented:
At least 6 server addresses, because that's the value of
ipconfigENDPOINT_DNS_ADDRESS_COUNT + 1. No "invalid" length required,
just an overabundance of DNS servers. As such, let's rename the test.

Btw, the test was using a VLA (fixed), and most of the uint32_t writes
are still unaligned (I replaced one of them with memcpy).

* test_eHandleIPv6ExtensionHeaders_TCPHappyPath: Fix buffer overflow

* FreeRTOS_DNS.c: Fix NULL deref encountered in test_FreeRTOS_gethostbyname_SuccessAddressInCache

* FreeRTOS_DNS_Parser_utest: Don't return dangling pointers

UBSan happens to catch this as a misaligned pointer deref within the null page
one moment before segfault:

    source/FreeRTOS_DNS_Parser.c:761:49: runtime error:
    member access within misaligned address 0x00000000012c for type
    'struct freertos_addrinfo', which requires 8 byte alignment

This was traced back to the test test_parseDNSAnswer_dns_nocallback_false(),
which creates an uninitialized pointer and passes it on.
Other tests were also found doing the same, though did not lead to segfault
on GCC 12 and 13, except did on GCC 11 in CI with AddressSanitizer.

This is a class of error that a higher warning level could easily forbid
(reading an uninitialized variable, pointer or not):

    -Werror=maybe-uninitialized

* FreeRTOS_DHCP_utest: Fix a read out of bounds

xProcessCheckOption() reads the length from the second byte.
So for the purpose of testing reading the second byte,
but no more, the buffer length was correctly given as 2,
except that the buffer length must then be at least 2.

* FreeRTOS_{DNS,Routing}_utest.c: Fix off-by-one buffer overflows

FreeRTOS_DNS_utest.c::test_FreeRTOS_gethostbyname_FailLongAddress:
Array length vs index of last element:
I was about to add one more byte to the buffer,
but it looked like that had been attempted before
without remembering to initialize them.
Therefore, remove those bytes instead.

FreeRTOS_Routing_utest.c::test_pcEndpointName_IPv{4,6}_HappyPath:
Can be summed up as sizeof() != strlen(). These tests
were copying one byte too many from their test input strings.

Non-functional cleanups:
* Let input strings have static storage duration (avoid copy to stack).
* I found it confusing to take the address of the string constants,
  as it performs the same pointer decay as doing nothing.

* unit-test FreeRTOS_DHCP_stubs.c: Fix NetworkBufferDescriptor_t alignment

* FreeRTOS_{DHCPv6,DNS}_utest: Fix memory leaks

Consider undoing this and see if the code under test needs fixing.
LeakSanitizer finds these.

* FreeRTOS_DNS_Parser_utest: Fix misaligned writes in the test

* FreeRTOS_TCP_WIN_utest.c: Fix memory leaks of type free(NULL)

The pointer to the allocated memory was reset. ⏚

* FreeRTOS_DHCP_utest: Fix misaligned writing of DHCP option fields

Symptom (UB Sanitizer):

    Store to misaligned address 0x7ffe* for type 'uint32_t',
    which requires 4 byte alignment

When repeated, these 4-byte fields are 2 bytes apart
(because of the option and length bytes).
The padding byte added to each test does not solve
this problem (consider removing).
Should have used memcpy (done).

Actually, one thing that makes memcpy tedious is that
it takes an address, not a value.
I got tired of memcpy halfway through;
this is what I mean by helper functions
(see the commit about deleting a buffer overflow):

The ultimate solution is not memcpy, but helpers that
remove those manual indexes and length fields,
and with that, the possibility for inconsistencies
that can lead to such a buffer overflow.

* FreeRTOS_ND_utest.c: Remove failing but redundant test

test_prvProcessICMPMessage_IPv6_NeighborSolicitationCorrectLen()
required these fixes when compiled with -fsanitize=address,memory

    +usGenerateProtocolChecksum_IgnoreAndReturn( ipCORRECT_CRC );
    +vReturnEthernetFrame_ExpectAnyArgs();

… but only this fix when compiled regularly:

    +usGenerateProtocolChecksum_IgnoreAndReturn( ipCORRECT_CRC );

Thankfully, the intention is clear from the comment.
It fails extra with sanitization because the two compared IP addresses
actually do compare equal.

Which is fixable. But removing the test did not impact coverage.

* unit-test: Fix differences between with and without sanitization due to lack of initialization

* unit-test: Add FIXME for behavioural difference with sanitizers

* FreeRTOS_DNS_Parser_utest.c: Fix buffer alignment

* CI: Add SANITIZE=address,undefined build

As commented, it had to be a separate build because branch coverage
(currently) doesn't ignore artificial branches added by sanitizers.

On reusing the same build directory:
It's totally possible to use separate build directories in build/,
but there is no correctness benefit (CMake rebuilds the object files
whose recipe has changed anyway). Rather, CMake saves (130) jobs
that don't need to run again when reusing the same build directory.

On which builds to build and run first (aubsan before coverage):
When it matters, which is when a test is crashing, that's generally
when you want to see the AddressSanitizer output.

* FreeRTOS_ND_utest: Don't use an uninitialized ip address

… it's not fun when it only fails in CI.
The lookup happened to fail to fail with AddressSanitizer,
but only on GCC 11 (not 12 of 13).

* FreeRTOS_DHCP_utest: Fix attempt at making recvfrom return a NULL buffer

With Gcc 11 + AddressSanitizer, the mocked recvfrom would not return
a NULL buffer (unlike Gcc 12 and 13 with and without sanitization).
A custom stub function gave enough control to do that.

The existing FreeRTOS_recvfrom_Generic_NullBuffer() stub did almost the same,
but was unused and meaningless (failed to set its out-argument),
so it could be replaced.

* FreeRTOS_ND_utest: Fix test failures due to missing initialization

test_SendPingRequestIPv6_SendToIP_Pass():
This test segfaulted without AddressSanitizer:

    'build/normal/bin/tests/FreeRTOS…' terminated by signal SIGSEGV

test_SendPingRequestIPv6_Assert():

    ==7143==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_descriptions.cpp:80 "((0 && "Address is not in memory and not in shadow?")) != (0)" (0x0, 0x0)
    #0 0x7ff6c812f9a8 in AsanCheckFailed ../../../../src/libsanitizer/asan/asan_rtl.cpp:74
    #1 0x7ff6c815032e in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) ../../../../src/libsanitizer/sanitizer_common/sanitizer_termination.cpp:78
    #2 0x7ff6c809fa77 in GetShadowKind ../../../../src/libsanitizer/asan/asan_descriptions.cpp:80
    #3 0x7ff6c809fa77 in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cpp:96
    #4 0x7ff6c809fa77 in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cpp:93
    #5 0x7ff6c80a1296 in __asan::AddressDescription::AddressDescription(unsigned long, unsigned long, bool) ../../../../src/libsanitizer/asan/asan_descriptions.cpp:441
    #6 0x7ff6c80a3a84 in __asan::ErrorGeneric::ErrorGeneric(unsigned int, unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long) ../../../../src/libsanitizer/asan/asan_errors.cpp:389
    #7 0x7ff6c812efc5 in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) ../../../../src/libsanitizer/asan/asan_report.cpp:476
    #8 0x7ff6c80abc44 in __interceptor_memset ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:799
    #9 0x55f2e38a3620 in FreeRTOS_SendPingRequestIPv6 build/u22/Annexed_TCP_Sources/FreeRTOS_ND.c:768
    #10 0x55f2e3893053 in test_SendPingRequestIPv6_Assert test/unit-test/FreeRTOS_ND/FreeRTOS_ND_utest.c:1065
    #11 0x55f2e389c5dd in run_test build/u22/FreeRTOS_ND_utest_runner.c:201
    #12 0x55f2e389ca84 in main build/u22/FreeRTOS_ND_utest_runner.c:252
    #13 0x7ff6c6bcbd8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f)
    #14 0x7ff6c6bcbe3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f)
    #15 0x55f2e38873d4 in _start (build/u22/bin/tests/FreeRTOS_ND_utest+0x233d4

test_prvProcessICMPMessage_IPv6_NeighborSolicitationNullEP()
behaved different with and without ASan on Gcc 11.
Without AddressSanitizer on Gcc 11:

    FreeRTOS_ND_utest.c:1427:test_prvProcessICMPMessage_IPv6_NeighborSolicitationNullEP:
    FAIL:Function usGenerateProtocolChecksum.  Called more times than expected.

* test_lTCPWindowTxAdd_nothing_to_do(): Fix TCP window initialization

Under Gcc 11, this expression in the tested function lTCPWindowTxAdd()
was always true, leading to imperfect coverage:

    pxSegment->lDataLength < pxSegment->lMaxLength

With Gcc 13, they were both 0.
Let's add zero-initialization to make this what's tested for.

---------

Co-authored-by: Tony Josi <tonyjosi@amazon.com>
2024-06-14 16:11:40 +05:30

1049 lines
46 KiB
C

/*
* FreeRTOS+TCP <DEVELOPMENT BRANCH>
* Copyright (C) 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* http://aws.amazon.com/freertos
* http://www.FreeRTOS.org
*/
/**
* @file FreeRTOS_TCP_IP.c
* @brief Module which handles the TCP connections for FreeRTOS+TCP.
* It depends on FreeRTOS_TCP_WIN.c, which handles the TCP windowing
* schemes.
*
* Endianness: in this module all ports and IP addresses are stored in
* host byte-order, except fields in the IP-packets
*/
/* Standard includes. */
#include <stdint.h>
#include <stdio.h>
/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* FreeRTOS+TCP includes. */
#include "FreeRTOS_IP.h"
#include "FreeRTOS_Sockets.h"
#include "FreeRTOS_IP_Private.h"
#include "FreeRTOS_IP_Utils.h"
#include "FreeRTOS_UDP_IP.h"
#include "FreeRTOS_DHCP.h"
#include "NetworkInterface.h"
#include "NetworkBufferManagement.h"
#include "FreeRTOS_ARP.h"
#include "FreeRTOS_TCP_Reception.h"
#include "FreeRTOS_TCP_Transmission.h"
#include "FreeRTOS_TCP_State_Handling.h"
#include "FreeRTOS_TCP_Utils.h"
/* Just make sure the contents doesn't get compiled if TCP is not enabled. */
#if ipconfigUSE_TCP == 1
/** @brief When closing a socket an event is posted to the Network Event Queue.
* If the queue is full, then the event is not posted and the socket
* can be orphaned. To prevent this, the below variable is used to keep
* track of any socket which needs to be closed. This variable can be
* accessed by the IP task only. Thus, preventing any race condition.
*/
/* MISRA Ref 8.9.1 [File scoped variables] */
/* More details at: https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/blob/main/MISRA.md#rule-89 */
/* coverity[misra_c_2012_rule_8_9_violation] */
static FreeRTOS_Socket_t * xSocketToClose = NULL;
/** @brief When a connection is coming in on a reusable socket, and the
* SYN phase times out, the socket must be put back into eTCP_LISTEN
* mode, so it can accept a new connection again.
* This variable can be accessed by the IP task only. Thus, preventing any
* race condition.
*/
/* MISRA Ref 8.9.1 [File scoped variables] */
/* More details at: https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/blob/main/MISRA.md#rule-89 */
/* coverity[misra_c_2012_rule_8_9_violation] */
_static FreeRTOS_Socket_t * xSocketToListen = NULL;
#if ( ipconfigHAS_DEBUG_PRINTF != 0 )
/*
* For logging and debugging: make a string showing the TCP flags.
*/
const char * prvTCPFlagMeaning( UBaseType_t xFlags );
#endif /* ipconfigHAS_DEBUG_PRINTF != 0 */
static IPv46_Address_t xGetSourceAddrFromBuffer( const uint8_t * const pucEthernetBuffer );
/*-----------------------------------------------------------*/
/** @brief Close the socket another time.
*
* @param[in] pxSocket The socket to be checked.
*/
/* coverity[single_use] */
void vSocketCloseNextTime( FreeRTOS_Socket_t * pxSocket )
{
if( ( xSocketToClose != NULL ) && ( xSocketToClose != pxSocket ) )
{
( void ) vSocketClose( xSocketToClose );
}
xSocketToClose = pxSocket;
}
/*-----------------------------------------------------------*/
/** @brief Postpone a call to FreeRTOS_listen() to avoid recursive calls.
*
* @param[in] pxSocket The socket to be checked.
*/
/* coverity[single_use] */
void vSocketListenNextTime( FreeRTOS_Socket_t * pxSocket )
{
if( ( xSocketToListen != NULL ) && ( xSocketToListen != pxSocket ) )
{
( void ) FreeRTOS_listen( ( Socket_t ) xSocketToListen, ( BaseType_t ) ( xSocketToListen->u.xTCP.usBacklog ) );
}
xSocketToListen = pxSocket;
}
/*-----------------------------------------------------------*/
/**
* @brief As soon as a TCP socket timer expires, this function will be called
* (from xTCPTimerCheck). It can send a delayed ACK or new data.
*
* @param[in] pxSocket socket to be checked.
*
* @return 0 on success, a negative error code on failure. A negative value will be
* returned in case the hang-protection has put the socket in a wait-close state.
*
* @note Sequence of calling (normally) :
* IP-Task:
* xTCPTimerCheck() // Check all sockets ( declared in FreeRTOS_Sockets.c )
* xTCPSocketCheck() // Either send a delayed ACK or call prvTCPSendPacket()
* prvTCPSendPacket() // Either send a SYN or call prvTCPSendRepeated ( regular messages )
* prvTCPSendRepeated() // Send at most 8 messages on a row
* prvTCPReturnPacket() // Prepare for returning
* xNetworkInterfaceOutput() // Sends data to the NIC ( declared in portable/NetworkInterface/xxx )
*/
BaseType_t xTCPSocketCheck( FreeRTOS_Socket_t * pxSocket )
{
BaseType_t xResult = 0;
BaseType_t xReady = pdFALSE;
if( ( pxSocket->u.xTCP.eTCPState >= eESTABLISHED ) && ( pxSocket->u.xTCP.txStream != NULL ) )
{
/* The API FreeRTOS_send() might have added data to the TX stream. Add
* this data to the windowing system so it can be transmitted. */
prvTCPAddTxData( pxSocket );
}
#if ( ipconfigUSE_TCP_WIN == 1 )
{
if( pxSocket->u.xTCP.pxAckMessage != NULL )
{
/* The first task of this regular socket check is to send-out delayed
* ACK's. */
if( pxSocket->u.xTCP.bits.bUserShutdown == pdFALSE_UNSIGNED )
{
/* Earlier data was received but not yet acknowledged. This
* function is called when the TCP timer for the socket expires, the
* ACK may be sent now. */
if( pxSocket->u.xTCP.eTCPState != eCLOSED )
{
if( ( xTCPWindowLoggingLevel > 1 ) && ipconfigTCP_MAY_LOG_PORT( pxSocket->usLocalPort ) )
{
FreeRTOS_debug_printf( ( "Send[%u->%u] del ACK %u SEQ %u (len %u)\n",
pxSocket->usLocalPort,
pxSocket->u.xTCP.usRemotePort,
( unsigned ) ( pxSocket->u.xTCP.xTCPWindow.rx.ulCurrentSequenceNumber - pxSocket->u.xTCP.xTCPWindow.rx.ulFirstSequenceNumber ),
( unsigned ) ( pxSocket->u.xTCP.xTCPWindow.ulOurSequenceNumber - pxSocket->u.xTCP.xTCPWindow.tx.ulFirstSequenceNumber ),
( unsigned ) ( uxIPHeaderSizeSocket( pxSocket ) + ipSIZE_OF_TCP_HEADER ) ) );
}
prvTCPReturnPacket( pxSocket, pxSocket->u.xTCP.pxAckMessage, ( uint32_t ) ( uxIPHeaderSizeSocket( pxSocket ) + ipSIZE_OF_TCP_HEADER ), ipconfigZERO_COPY_TX_DRIVER );
#if ( ipconfigZERO_COPY_TX_DRIVER != 0 )
{
/* The ownership has been passed to the SEND routine,
* clear the pointer to it. */
pxSocket->u.xTCP.pxAckMessage = NULL;
}
#endif /* ipconfigZERO_COPY_TX_DRIVER */
}
if( prvTCPNextTimeout( pxSocket ) > 1U )
{
/* Tell the code below that this function is ready. */
xReady = pdTRUE;
}
}
else
{
/* The user wants to perform an active shutdown(), skip sending
* the delayed ACK. The function prvTCPSendPacket() will send the
* FIN along with the ACK's. */
}
if( pxSocket->u.xTCP.pxAckMessage != NULL )
{
vReleaseNetworkBufferAndDescriptor( pxSocket->u.xTCP.pxAckMessage );
pxSocket->u.xTCP.pxAckMessage = NULL;
}
}
}
#endif /* ipconfigUSE_TCP_WIN */
if( xReady == pdFALSE )
{
/* The second task of this regular socket check is sending out data. */
if( ( pxSocket->u.xTCP.eTCPState >= eESTABLISHED ) ||
( pxSocket->u.xTCP.eTCPState == eCONNECT_SYN ) )
{
( void ) prvTCPSendPacket( pxSocket );
}
/* Set the time-out for the next wakeup for this socket. */
( void ) prvTCPNextTimeout( pxSocket );
#if ( ipconfigTCP_HANG_PROTECTION == 1 )
{
/* In all (non-connected) states in which keep-alive messages can not be sent
* the anti-hang protocol will close sockets that are 'hanging'. */
xResult = prvTCPStatusAgeCheck( pxSocket );
}
#endif
}
return xResult;
}
/*-----------------------------------------------------------*/
/**
* @brief 'Touch' the socket to keep it alive/updated.
*
* @param[in] pxSocket The socket to be updated.
*
* @note This is used for anti-hanging protection and TCP keep-alive messages.
* Called in two places: after receiving a packet and after a state change.
* The socket's alive timer may be reset.
*/
void prvTCPTouchSocket( struct xSOCKET * pxSocket )
{
#if ( ipconfigTCP_HANG_PROTECTION == 1 )
{
pxSocket->u.xTCP.xLastActTime = xTaskGetTickCount();
}
#endif
#if ( ipconfigTCP_KEEP_ALIVE == 1 )
{
pxSocket->u.xTCP.bits.bWaitKeepAlive = pdFALSE_UNSIGNED;
pxSocket->u.xTCP.bits.bSendKeepAlive = pdFALSE_UNSIGNED;
pxSocket->u.xTCP.ucKeepRepCount = 0U;
pxSocket->u.xTCP.xLastAliveTime = xTaskGetTickCount();
}
#endif
( void ) pxSocket;
}
/*-----------------------------------------------------------*/
static BaseType_t vTCPRemoveTCPChild( const FreeRTOS_Socket_t * pxChildSocket )
{
BaseType_t xReturn = pdFALSE;
const ListItem_t * pxEnd = ( ( const ListItem_t * ) &( xBoundTCPSocketsList.xListEnd ) );
/* MISRA Ref 11.3.1 [Misaligned access] */
/* More details at: https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/blob/main/MISRA.md#rule-113 */
/* coverity[misra_c_2012_rule_11_3_violation] */
const ListItem_t * pxIterator = ( const ListItem_t * ) listGET_HEAD_ENTRY( &xBoundTCPSocketsList );
while( pxIterator != pxEnd )
{
FreeRTOS_Socket_t * pxSocket;
pxSocket = ( ( FreeRTOS_Socket_t * ) listGET_LIST_ITEM_OWNER( pxIterator ) );
pxIterator = ( ListItem_t * ) listGET_NEXT( pxIterator );
if( ( pxSocket != pxChildSocket ) && ( pxSocket->usLocalPort == pxChildSocket->usLocalPort ) )
{
if( pxSocket->u.xTCP.pxPeerSocket == pxChildSocket ) /**< for server socket: child, for child socket: parent */
{
pxSocket->u.xTCP.pxPeerSocket = NULL;
xReturn = pdTRUE;
break;
}
}
}
return xReturn;
}
/**
* @brief Changing to a new state. Centralised here to do specific actions such as
* resetting the alive timer, calling the user's OnConnect handler to notify
* that a socket has got (dis)connected, and setting bit to unblock a call to
* FreeRTOS_select().
*
* @param[in] pxSocket The socket whose state we are trying to change.
* @param[in] eTCPState The state to which we want to change to.
*/
void vTCPStateChange( FreeRTOS_Socket_t * pxSocket,
enum eTCP_STATE eTCPState )
{
FreeRTOS_Socket_t * xParent = pxSocket;
BaseType_t bBefore = tcpNOW_CONNECTED( ( BaseType_t ) pxSocket->u.xTCP.eTCPState ); /* Was it connected ? */
BaseType_t bAfter = tcpNOW_CONNECTED( ( BaseType_t ) eTCPState ); /* Is it connected now ? */
eIPTCPState_t xPreviousState = pxSocket->u.xTCP.eTCPState;
#if ( ipconfigUSE_CALLBACKS == 1 )
FreeRTOS_Socket_t * xConnected = NULL;
#endif
if( ( ( xPreviousState == eCONNECT_SYN ) ||
( xPreviousState == eSYN_FIRST ) ||
( xPreviousState == eSYN_RECEIVED ) ) &&
( eTCPState == eCLOSE_WAIT ) )
{
/* A socket was in the connecting phase but something
* went wrong and it should be closed. */
#if ( ipconfigHAS_DEBUG_PRINTF != 0 )
FreeRTOS_debug_printf( ( "Move from %s to %s\n",
FreeRTOS_GetTCPStateName( ( UBaseType_t ) xPreviousState ),
FreeRTOS_GetTCPStateName( eTCPState ) ) );
#endif
/* Set the flag to show that it was connected before and that the
* status has changed now. This will cause the control flow to go
* in the below if condition.*/
bBefore = pdTRUE;
}
/* Has the connected status changed? */
if( bBefore != bAfter )
{
/* if bPassQueued is true, this socket is an orphan until it gets connected. */
if( pxSocket->u.xTCP.bits.bPassQueued != pdFALSE_UNSIGNED )
{
/* Find it's parent if the reuse bit is not set. */
if( pxSocket->u.xTCP.bits.bReuseSocket == pdFALSE_UNSIGNED )
{
xParent = pxSocket->u.xTCP.pxPeerSocket;
configASSERT( xParent != NULL );
}
}
/* Is the socket connected now ? */
if( bAfter != pdFALSE )
{
/* if bPassQueued is true, this socket is an orphan until it gets connected. */
if( pxSocket->u.xTCP.bits.bPassQueued != pdFALSE_UNSIGNED )
{
if( xParent != NULL )
{
/* The child socket has got connected. See if the parent
* ( the listening socket ) should be signalled, or if a
* call-back must be made, in which case 'xConnected' will
* be set to the parent socket. */
if( xParent->u.xTCP.pxPeerSocket == NULL )
{
xParent->u.xTCP.pxPeerSocket = pxSocket;
}
xParent->xEventBits |= ( EventBits_t ) eSOCKET_ACCEPT;
#if ( ipconfigSUPPORT_SELECT_FUNCTION == 1 )
{
/* Library support FreeRTOS_select(). Receiving a new
* connection is being translated as a READ event. */
if( ( xParent->xSelectBits & ( ( EventBits_t ) eSELECT_READ ) ) != 0U )
{
xParent->xEventBits |= ( ( EventBits_t ) eSELECT_READ ) << SOCKET_EVENT_BIT_COUNT;
}
}
#endif
#if ( ipconfigUSE_CALLBACKS == 1 )
{
if( ( ipconfigIS_VALID_PROG_ADDRESS( xParent->u.xTCP.pxHandleConnected ) ) &&
( xParent->u.xTCP.bits.bReuseSocket == pdFALSE_UNSIGNED ) )
{
/* The listening socket does not become connected itself, in stead
* a child socket is created.
* Postpone a call the OnConnect event until the end of this function. */
xConnected = xParent;
}
}
#endif
}
/* Don't need to access the parent socket anymore, so the
* reference 'pxPeerSocket' may be cleared. */
pxSocket->u.xTCP.pxPeerSocket = NULL;
pxSocket->u.xTCP.bits.bPassQueued = pdFALSE_UNSIGNED;
/* When true, this socket may be returned in a call to accept(). */
pxSocket->u.xTCP.bits.bPassAccept = pdTRUE_UNSIGNED;
}
else
{
/* An active connect() has succeeded. In this case there is no
* ( listening ) parent socket. Signal the now connected socket. */
pxSocket->xEventBits |= ( EventBits_t ) eSOCKET_CONNECT;
#if ( ipconfigSUPPORT_SELECT_FUNCTION == 1 )
{
if( ( pxSocket->xSelectBits & ( ( EventBits_t ) eSELECT_WRITE ) ) != 0U )
{
pxSocket->xEventBits |= ( ( EventBits_t ) eSELECT_WRITE ) << SOCKET_EVENT_BIT_COUNT;
}
}
#endif
}
}
else /* bAfter == pdFALSE, connection is closed. */
{
/* Notify/wake-up the socket-owner by setting the event bits. */
xParent->xEventBits |= ( EventBits_t ) eSOCKET_CLOSED;
#if ( ipconfigSUPPORT_SELECT_FUNCTION == 1 )
{
if( ( xParent->xSelectBits & ( EventBits_t ) eSELECT_EXCEPT ) != 0U )
{
xParent->xEventBits |= ( ( EventBits_t ) eSELECT_EXCEPT ) << SOCKET_EVENT_BIT_COUNT;
}
}
#endif
}
#if ( ipconfigUSE_CALLBACKS == 1 )
{
if( ( ipconfigIS_VALID_PROG_ADDRESS( pxSocket->u.xTCP.pxHandleConnected ) ) && ( xConnected == NULL ) )
{
/* The 'connected' state has changed, call the user handler. */
xConnected = pxSocket;
}
}
#endif /* ipconfigUSE_CALLBACKS */
if( prvTCPSocketIsActive( pxSocket->u.xTCP.eTCPState ) == 0 )
{
/* Now the socket isn't in an active state anymore so it
* won't need further attention of the IP-task.
* Setting time-out to zero means that the socket won't get checked during
* timer events. */
pxSocket->u.xTCP.usTimeout = 0U;
}
}
/* Fill in the new state. */
pxSocket->u.xTCP.eTCPState = eTCPState;
if( ( eTCPState == eCLOSED ) ||
( eTCPState == eCLOSE_WAIT ) )
{
BaseType_t xMustClear = pdFALSE;
BaseType_t xHasCleared = pdFALSE;
if( ( xParent == pxSocket ) && ( pxSocket->u.xTCP.pxPeerSocket != NULL ) )
{
xParent = pxSocket->u.xTCP.pxPeerSocket;
}
if( ( xParent->u.xTCP.pxPeerSocket != NULL ) &&
( xParent->u.xTCP.pxPeerSocket == pxSocket ) )
{
xMustClear = pdTRUE;
( void ) xMustClear;
}
/* Socket goes to status eCLOSED because of a RST.
* When nobody owns the socket yet, delete it. */
FreeRTOS_printf( ( "vTCPStateChange: Closing (Queued %d, Accept %d Reuse %d)\n",
pxSocket->u.xTCP.bits.bPassQueued,
pxSocket->u.xTCP.bits.bPassAccept,
pxSocket->u.xTCP.bits.bReuseSocket ) );
FreeRTOS_printf( ( "vTCPStateChange: me %p parent %p peer %p clear %d\n",
( void * ) pxSocket,
( void * ) xParent,
xParent ? ( void * ) xParent->u.xTCP.pxPeerSocket : NULL,
( int ) xMustClear ) );
vTaskSuspendAll();
{
if( ( pxSocket->u.xTCP.bits.bPassQueued != pdFALSE_UNSIGNED ) ||
( pxSocket->u.xTCP.bits.bPassAccept != pdFALSE_UNSIGNED ) )
{
if( pxSocket->u.xTCP.bits.bReuseSocket == pdFALSE_UNSIGNED )
{
xHasCleared = vTCPRemoveTCPChild( pxSocket );
( void ) xHasCleared;
pxSocket->u.xTCP.bits.bPassQueued = pdFALSE_UNSIGNED;
pxSocket->u.xTCP.bits.bPassAccept = pdFALSE_UNSIGNED;
configASSERT( xIsCallingFromIPTask() != pdFALSE );
vSocketCloseNextTime( pxSocket );
}
}
}
( void ) xTaskResumeAll();
FreeRTOS_printf( ( "vTCPStateChange: xHasCleared = %d\n",
( int ) xHasCleared ) );
}
if( ( eTCPState == eCLOSE_WAIT ) && ( pxSocket->u.xTCP.bits.bReuseSocket == pdTRUE_UNSIGNED ) )
{
switch( xPreviousState )
{
case eSYN_FIRST: /* 3 (server) Just created, must ACK the SYN request */
case eSYN_RECEIVED: /* 4 (server) waiting for a confirming connection request */
FreeRTOS_debug_printf( ( "Restoring a reuse socket port %u\n", pxSocket->usLocalPort ) );
/* Go back into listening mode. Set the TCP status to 'eCLOSED',
* otherwise FreeRTOS_listen() will refuse the action. */
pxSocket->u.xTCP.eTCPState = eCLOSED;
/* vSocketListenNextTime() makes sure that FreeRTOS_listen() will be called
* before the IP-task handles any new message. */
vSocketListenNextTime( pxSocket );
break;
default:
/* Nothing to do. */
break;
}
}
/* Touch the alive timers because moving to another state. */
prvTCPTouchSocket( pxSocket );
#if ( ipconfigHAS_DEBUG_PRINTF == 1 )
{
if( ( xTCPWindowLoggingLevel >= 0 ) && ( ipconfigTCP_MAY_LOG_PORT( pxSocket->usLocalPort ) ) )
{
char pcBuffer[ 40 ];
switch( pxSocket->bits.bIsIPv6 ) /* LCOV_EXCL_BR_LINE */
{
#if ( ipconfigUSE_IPv4 != 0 )
case pdFALSE_UNSIGNED:
{
uint32_t ulIPAddress = FreeRTOS_ntohl( pxSocket->u.xTCP.xRemoteIP.ulIP_IPv4 );
FreeRTOS_inet_ntop( FREERTOS_AF_INET4,
( const uint8_t * ) &ulIPAddress,
pcBuffer,
sizeof( pcBuffer ) );
}
break;
#endif /* ( ipconfigUSE_IPv4 != 0 ) */
#if ( ipconfigUSE_IPv6 != 0 )
case pdTRUE_UNSIGNED:
FreeRTOS_inet_ntop( FREERTOS_AF_INET6,
pxSocket->u.xTCP.xRemoteIP.xIP_IPv6.ucBytes,
pcBuffer,
sizeof( pcBuffer ) );
break;
#endif /* ( ipconfigUSE_IPv6 != 0 ) */
default: /* LCOV_EXCL_LINE */
/* MISRA 16.4 Compliance */
break; /* LCOV_EXCL_LINE */
}
FreeRTOS_debug_printf( ( "Socket %u -> [%s]:%u State %s->%s\n",
pxSocket->usLocalPort,
pcBuffer,
pxSocket->u.xTCP.usRemotePort,
FreeRTOS_GetTCPStateName( ( UBaseType_t ) xPreviousState ),
FreeRTOS_GetTCPStateName( ( UBaseType_t ) eTCPState ) ) );
}
}
#endif /* ipconfigHAS_DEBUG_PRINTF */
#if ( ipconfigUSE_CALLBACKS == 1 )
{
if( xConnected != NULL )
{
/* The 'connected' state has changed, call the OnConnect handler of the parent. */
xConnected->u.xTCP.pxHandleConnected( ( Socket_t ) xConnected, bAfter );
}
}
#endif
if( xParent != NULL )
{
vSocketWakeUpUser( xParent );
}
}
/*-----------------------------------------------------------*/
/**
* @brief Calculate after how much time this socket needs to be checked again.
*
* @param[in] pxSocket The socket to be checked.
*
* @return The number of clock ticks before the timer expires.
*/
TickType_t prvTCPNextTimeout( struct xSOCKET * pxSocket )
{
TickType_t ulDelayMs = ( TickType_t ) tcpMAXIMUM_TCP_WAKEUP_TIME_MS;
if( pxSocket->u.xTCP.eTCPState == eCONNECT_SYN )
{
/* The socket is actively connecting to a peer. */
if( pxSocket->u.xTCP.bits.bConnPrepared != pdFALSE_UNSIGNED )
{
/* Ethernet address has been found, use progressive timeout for
* active connect(). */
if( pxSocket->u.xTCP.ucRepCount < 3U )
{
if( pxSocket->u.xTCP.ucRepCount == 0U )
{
ulDelayMs = 0U;
}
else
{
ulDelayMs = ( ( uint32_t ) 3000U ) << ( pxSocket->u.xTCP.ucRepCount - 1U );
}
}
else
{
ulDelayMs = 11000U;
}
}
else
{
/* Still in the ARP phase: check every half second. */
ulDelayMs = 500U;
}
FreeRTOS_debug_printf( ( "Connect[%xip:%u]: next timeout %u: %u ms\n",
( unsigned ) pxSocket->u.xTCP.xRemoteIP.ulIP_IPv4, pxSocket->u.xTCP.usRemotePort,
pxSocket->u.xTCP.ucRepCount, ( unsigned ) ulDelayMs ) );
pxSocket->u.xTCP.usTimeout = ( uint16_t ) ipMS_TO_MIN_TICKS( ulDelayMs );
}
else if( pxSocket->u.xTCP.usTimeout == 0U )
{
/* Let the sliding window mechanism decide what time-out is appropriate. */
BaseType_t xResult = xTCPWindowTxHasData( &pxSocket->u.xTCP.xTCPWindow, pxSocket->u.xTCP.ulWindowSize, &ulDelayMs );
if( ulDelayMs == 0U )
{
if( xResult != ( BaseType_t ) 0 )
{
ulDelayMs = 1U;
}
else
{
ulDelayMs = tcpMAXIMUM_TCP_WAKEUP_TIME_MS;
}
}
else
{
/* ulDelayMs contains the time to wait before a re-transmission. */
}
pxSocket->u.xTCP.usTimeout = ( uint16_t ) ipMS_TO_MIN_TICKS( ulDelayMs ); /* LCOV_EXCL_BR_LINE ulDelayMs will not be smaller than 1 */
}
else
{
/* field '.usTimeout' has already been set (by the
* keep-alive/delayed-ACK mechanism). */
}
/* Return the number of clock ticks before the timer expires. */
return ( TickType_t ) pxSocket->u.xTCP.usTimeout;
}
/*-----------------------------------------------------------*/
/**
* @brief IP frame agnostic helper to obtain the source IP Address from a buffer.
*
* @param[in] pucEthernetBuffer The Ethernet buffer from which the source address will be retrieved.
*
* @return IPv46_Address_t struct containing the source IP address.
*/
static IPv46_Address_t xGetSourceAddrFromBuffer( const uint8_t * const pucEthernetBuffer )
{
IPv46_Address_t xSourceAddr;
/* Map the buffer onto Ethernet Header struct for easy access to fields. */
/* MISRA Ref 11.3.1 [Misaligned access] */
/* More details at: https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/blob/main/MISRA.md#rule-113 */
/* coverity[misra_c_2012_rule_11_3_violation] */
const EthernetHeader_t * pxHeader = ( ( const EthernetHeader_t * ) pucEthernetBuffer );
if( pxHeader->usFrameType == ( uint16_t ) ipIPv6_FRAME_TYPE )
{
/* Map the ethernet buffer onto the IPHeader_t struct for easy access to the fields. */
/* MISRA Ref 11.3.1 [Misaligned access] */
/* More details at: https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/blob/main/MISRA.md#rule-113 */
/* coverity[misra_c_2012_rule_11_3_violation] */
const IPHeader_IPv6_t * const pxIPHeader_IPv6 = ( ( const IPHeader_IPv6_t * ) &( pucEthernetBuffer[ ipSIZE_OF_ETH_HEADER ] ) );
xSourceAddr.xIs_IPv6 = pdTRUE;
( void ) memcpy( xSourceAddr.xIPAddress.xIP_IPv6.ucBytes, pxIPHeader_IPv6->xSourceAddress.ucBytes, sizeof( IPv6_Address_t ) );
}
else
{
/* Map the ethernet buffer onto the IPHeader_t struct for easy access to the fields. */
/* MISRA Ref 11.3.1 [Misaligned access] */
/* More details at: https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/blob/main/MISRA.md#rule-113 */
/* coverity[misra_c_2012_rule_11_3_violation] */
const IPHeader_t * const pxIPHeader = ( ( const IPHeader_t * ) &( pucEthernetBuffer[ ipSIZE_OF_ETH_HEADER ] ) );
xSourceAddr.xIs_IPv6 = pdFALSE;
xSourceAddr.xIPAddress.ulIP_IPv4 = FreeRTOS_htonl( pxIPHeader->ulSourceIPAddress );
}
return xSourceAddr;
}
/**
* @brief Process the received TCP packet.
*
* @param[in] pxDescriptor The descriptor in which the TCP packet is held.
*
* @return If the processing of the packet was successful, then pdPASS is returned
* or else pdFAIL.
*
* @note FreeRTOS_TCP_IP has only 2 public functions, this is the second one:
* xProcessReceivedTCPPacket()
* prvTCPHandleState()
* prvTCPPrepareSend()
* prvTCPReturnPacket()
* xNetworkInterfaceOutput() // Sends data to the NIC
* prvTCPSendRepeated()
* prvTCPReturnPacket() // Prepare for returning
* xNetworkInterfaceOutput() // Sends data to the NIC
*/
BaseType_t xProcessReceivedTCPPacket( NetworkBufferDescriptor_t * pxDescriptor )
{
BaseType_t xResult = pdPASS;
/* Function might modify the parameter. */
NetworkBufferDescriptor_t * pxNetworkBuffer;
size_t uxIPHeaderOffset;
configASSERT( pxDescriptor != NULL );
configASSERT( pxDescriptor->pucEthernetBuffer != NULL );
pxNetworkBuffer = pxDescriptor;
uxIPHeaderOffset = ipSIZE_OF_ETH_HEADER + uxIPHeaderSizePacket( pxNetworkBuffer );
/* Check for a minimum packet size. */
if( pxNetworkBuffer->xDataLength < ( uxIPHeaderOffset + ipSIZE_OF_TCP_HEADER ) )
{
xResult = pdFAIL;
}
else
{
/* MISRA Ref 11.3.1 [Misaligned access] */
/* More details at: https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/blob/main/MISRA.md#rule-113 */
/* coverity[misra_c_2012_rule_11_3_violation] */
const TCPHeader_t * pxTCPHeader = ( ( const TCPHeader_t * )
&( pxNetworkBuffer->pucEthernetBuffer[ uxIPHeaderOffset ] ) );
const uint16_t ucTCPFlags = pxTCPHeader->ucTCPFlags;
const uint16_t usLocalPort = FreeRTOS_htons( pxTCPHeader->usDestinationPort );
const uint16_t usRemotePort = FreeRTOS_htons( pxTCPHeader->usSourcePort );
const IPv46_Address_t xRemoteIP = xGetSourceAddrFromBuffer( pxNetworkBuffer->pucEthernetBuffer );
/* Find the destination socket, and if not found: return a socket listening to
* the destination PORT. */
FreeRTOS_Socket_t * pxSocket = pxTCPSocketLookup( 0U, usLocalPort, xRemoteIP, usRemotePort );
if( ( pxSocket == NULL ) || ( prvTCPSocketIsActive( pxSocket->u.xTCP.eTCPState ) == pdFALSE ) )
{
/* A TCP messages is received but either there is no socket with the
* given port number or the there is a socket, but it is in one of these
* non-active states: eCLOSED, eCLOSE_WAIT, eFIN_WAIT_2, eCLOSING, or
* eTIME_WAIT. */
FreeRTOS_debug_printf( ( "TCP: No active socket on port %d (%d)\n", usLocalPort, usRemotePort ) );
/* Send a RST to all packets that can not be handled. As a result
* the other party will get a ECONN error. There are two exceptions:
* 1) A packet that already has the RST flag set.
* 2) A packet that only has the ACK flag set.
* A packet with only the ACK flag set might be the last ACK in
* a three-way hand-shake that closes a connection. */
if( ( ( ucTCPFlags & tcpTCP_FLAG_CTRL ) != tcpTCP_FLAG_ACK ) &&
( ( ucTCPFlags & tcpTCP_FLAG_RST ) == 0U ) )
{
( void ) prvTCPSendReset( pxNetworkBuffer );
}
/* The packet can't be handled. */
xResult = pdFAIL;
}
else
{
pxSocket->u.xTCP.ucRepCount = 0U;
if( pxSocket->u.xTCP.eTCPState == eTCP_LISTEN )
{
/* The matching socket is in a listening state. Test if the peer
* has set the SYN flag. */
if( ( ucTCPFlags & tcpTCP_FLAG_CTRL ) != tcpTCP_FLAG_SYN )
{
/* What happens: maybe after a reboot, a client doesn't know the
* connection had gone. Send a RST in order to get a new connect
* request. */
#if ( ipconfigHAS_DEBUG_PRINTF == 1 )
{
FreeRTOS_debug_printf( ( "TCP: Server can't handle flags: %s from %u to port %u\n",
prvTCPFlagMeaning( ( UBaseType_t ) ucTCPFlags ), usRemotePort, usLocalPort ) );
}
#endif /* ipconfigHAS_DEBUG_PRINTF */
if( ( ucTCPFlags & tcpTCP_FLAG_RST ) == 0U )
{
( void ) prvTCPSendReset( pxNetworkBuffer );
}
xResult = pdFAIL;
}
else
{
/* prvHandleListen() will either return a newly created socket
* (if bReuseSocket is false), otherwise it returns the current
* socket which will later get connected. */
pxSocket = prvHandleListen( pxSocket, pxNetworkBuffer );
if( pxSocket == NULL )
{
xResult = pdFAIL;
}
}
} /* if( pxSocket->u.xTCP.eTCPState == eTCP_LISTEN ). */
else
{
/* This is not a socket in listening mode. Check for the RST
* flag. */
if( ( ucTCPFlags & tcpTCP_FLAG_RST ) != 0U )
{
FreeRTOS_debug_printf( ( "TCP: RST received from %u for %u\n", usRemotePort, usLocalPort ) );
/* Implement https://tools.ietf.org/html/rfc5961#section-3.2. */
if( pxSocket->u.xTCP.eTCPState == eCONNECT_SYN )
{
const uint32_t ulAckNumber = FreeRTOS_ntohl( pxTCPHeader->ulAckNr );
/* Per the above RFC, "In the SYN-SENT state ... the RST is
* acceptable if the ACK field acknowledges the SYN." */
if( ulAckNumber == ( pxSocket->u.xTCP.xTCPWindow.ulOurSequenceNumber + 1U ) )
{
vTCPStateChange( pxSocket, eCLOSED );
}
}
else
{
const uint32_t ulSequenceNumber = FreeRTOS_ntohl( pxTCPHeader->ulSequenceNumber );
/* Check whether the packet matches the next expected sequence number. */
if( ulSequenceNumber == pxSocket->u.xTCP.xTCPWindow.rx.ulCurrentSequenceNumber )
{
vTCPStateChange( pxSocket, eCLOSED );
}
/* Otherwise, check whether the packet is within the receive window. */
else if( ( xSequenceGreaterThan( ulSequenceNumber, pxSocket->u.xTCP.xTCPWindow.rx.ulCurrentSequenceNumber ) != pdFALSE ) &&
( xSequenceLessThan( ulSequenceNumber, pxSocket->u.xTCP.xTCPWindow.rx.ulCurrentSequenceNumber +
pxSocket->u.xTCP.xTCPWindow.xSize.ulRxWindowLength ) != pdFALSE ) )
{
/* Send a challenge ACK. */
( void ) prvTCPSendChallengeAck( pxNetworkBuffer );
}
else
{
/* Nothing. */
}
}
/* Otherwise, do nothing. In any case, the packet cannot be handled. */
xResult = pdFAIL;
}
/* Check whether there is a pure SYN amongst the TCP flags while the connection is established. */
else if( ( ( ucTCPFlags & tcpTCP_FLAG_CTRL ) == tcpTCP_FLAG_SYN ) && ( pxSocket->u.xTCP.eTCPState >= eESTABLISHED ) )
{
/* SYN flag while this socket is already connected. */
FreeRTOS_debug_printf( ( "TCP: SYN unexpected from %u\n", usRemotePort ) );
/* The packet cannot be handled. */
xResult = pdFAIL;
}
else
{
/* Update the copy of the TCP header only (skipping eth and IP
* headers). It might be used later on, whenever data must be sent
* to the peer. */
const size_t uxOffset = ipSIZE_OF_ETH_HEADER + uxIPHeaderSizeSocket( pxSocket );
( void ) memcpy( ( void * ) ( &( pxSocket->u.xTCP.xPacket.u.ucLastPacket[ uxOffset ] ) ),
( const void * ) ( &( pxNetworkBuffer->pucEthernetBuffer[ uxOffset ] ) ),
ipSIZE_OF_TCP_HEADER );
/* Clear flags that are set by the peer, and set the ACK flag. */
pxSocket->u.xTCP.xPacket.u.ucLastPacket[ uxOffset + ipTCP_FLAGS_OFFSET ] = tcpTCP_FLAG_ACK;
}
}
}
if( xResult != pdFAIL )
{
uint16_t usWindow;
/* pxSocket is not NULL when xResult != pdFAIL. */
configASSERT( pxSocket != NULL ); /* LCOV_EXCL_LINE ,this branch will not be hit*/
/* Touch the alive timers because we received a message for this
* socket. */
prvTCPTouchSocket( pxSocket );
/* Parse the TCP option(s), if present. */
/* _HT_ : if we're in the SYN phase, and peer does not send a MSS option,
* then we MUST assume an MSS size of 536 bytes for backward compatibility. */
/* When there are no TCP options, the TCP offset equals 20 bytes, which is stored as
* the number 5 (words) in the higher nibble of the TCP-offset byte. */
if( ( pxTCPHeader->ucTCPOffset & tcpTCP_OFFSET_LENGTH_BITS ) > tcpTCP_OFFSET_STANDARD_LENGTH )
{
xResult = prvCheckOptions( pxSocket, pxNetworkBuffer );
}
if( xResult != pdFAIL )
{
usWindow = FreeRTOS_ntohs( pxTCPHeader->usWindow );
pxSocket->u.xTCP.ulWindowSize = ( uint32_t ) usWindow;
#if ( ipconfigUSE_TCP_WIN == 1 )
{
/* rfc1323 : The Window field in a SYN (i.e., a <SYN> or <SYN,ACK>)
* segment itself is never scaled. */
if( ( ucTCPFlags & ( uint8_t ) tcpTCP_FLAG_SYN ) == 0U )
{
pxSocket->u.xTCP.ulWindowSize =
( pxSocket->u.xTCP.ulWindowSize << pxSocket->u.xTCP.ucPeerWinScaleFactor );
}
}
#endif /* ipconfigUSE_TCP_WIN */
/* In prvTCPHandleState() the incoming messages will be handled
* depending on the current state of the connection. */
if( prvTCPHandleState( pxSocket, &pxNetworkBuffer ) > 0 )
{
/* prvTCPHandleState() has sent a message, see if there are more to
* be transmitted. */
#if ( ipconfigUSE_TCP_WIN == 1 )
{
( void ) prvTCPSendRepeated( pxSocket, &pxNetworkBuffer );
}
#endif /* ipconfigUSE_TCP_WIN */
}
if( pxNetworkBuffer != NULL )
{
/* We must check if the buffer is unequal to NULL, because the
* socket might keep a reference to it in case a delayed ACK must be
* sent. */
vReleaseNetworkBufferAndDescriptor( pxNetworkBuffer );
#ifndef _lint
/* Clear pointers that are freed. */
pxNetworkBuffer = NULL;
#endif
}
/* And finally, calculate when this socket wants to be woken up. */
( void ) prvTCPNextTimeout( pxSocket );
}
}
}
/* pdPASS being returned means the buffer has been consumed. */
return xResult;
}
/*-----------------------------------------------------------*/
/**
* @brief In the API accept(), the user asks is there is a new client? As API's can
* not walk through the xBoundTCPSocketsList the IP-task will do this.
*
* @param[in] pxSocket The socket for which the bound socket list will be iterated.
*
* @return if there is a new client, then pdTRUE is returned or else, pdFALSE.
*/
BaseType_t xTCPCheckNewClient( FreeRTOS_Socket_t * pxSocket )
{
TickType_t uxLocalPort = ( TickType_t ) FreeRTOS_htons( pxSocket->usLocalPort );
const ListItem_t * pxIterator;
FreeRTOS_Socket_t * pxFound;
BaseType_t xResult = pdFALSE;
/* MISRA Ref 11.3.1 [Misaligned access] */
/* More details at: https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/blob/main/MISRA.md#rule-113 */
/* coverity[misra_c_2012_rule_11_3_violation] */
const ListItem_t * pxEndTCP = ( ( const ListItem_t * ) &( xBoundTCPSocketsList.xListEnd ) );
/* Here xBoundTCPSocketsList can be accessed safely IP-task is the only one
* who has access. */
for( pxIterator = ( const ListItem_t * ) listGET_HEAD_ENTRY( &xBoundTCPSocketsList );
pxIterator != pxEndTCP;
pxIterator = ( const ListItem_t * ) listGET_NEXT( pxIterator ) )
{
if( listGET_LIST_ITEM_VALUE( pxIterator ) == ( configLIST_VOLATILE TickType_t ) uxLocalPort )
{
pxFound = ( ( FreeRTOS_Socket_t * ) listGET_LIST_ITEM_OWNER( pxIterator ) );
if( ( pxFound->ucProtocol == ( uint8_t ) FREERTOS_IPPROTO_TCP ) && ( pxFound->u.xTCP.bits.bPassAccept != pdFALSE_UNSIGNED ) )
{
pxSocket->u.xTCP.pxPeerSocket = pxFound;
FreeRTOS_debug_printf( ( "xTCPCheckNewClient[0]: client on port %u\n", pxSocket->usLocalPort ) );
xResult = pdTRUE;
break;
}
}
}
return xResult;
}
/*-----------------------------------------------------------*/
#endif /* ipconfigUSE_TCP == 1 */
/* Provide access to private members for testing. */
#ifdef FREERTOS_ENABLE_UNIT_TESTS
#include "freertos_tcp_test_access_tcp_define.h"
#endif
/* Provide access to private members for verification. */
#ifdef FREERTOS_TCP_ENABLE_VERIFICATION
#include "aws_freertos_tcp_verification_access_tcp_define.h"
#endif