FreeRTOS+TCP How to bind a socket to a specific endpoint in a multi-endpoint setup?

Hello,
I am using FreeRTOS v11.1.0 kernel and FreeRTOS+TCP v4.2.2 (both from the FreeRTOSv202406.01-LTS package) and I am trying to get sockets on multiple endpoints to work.

This is how I initialize the two endpoints that should be set to different IP adresses:

        #define IP_ENDPOINT_CLIENT_ACCESS                   0
        #define IP_ENDPOINT_PRIVATE_ACCESS                  1
	// Add the interface
	pxZynq_FillInterfaceDescriptor(XPAR_PS7_ETHERNET_0_DEVICE_ID, &networkInterface);

	//Add the endpoint for the client address
    FreeRTOS_FillEndPoint( &(networkInterface),
                           &(ipEndpoints[IP_ENDPOINT_CLIENT_ACCESS]),
                           ipConfig.ipAdr4,
                           ipConfig.ipSubNetMask4,
                           ipConfig.ipStdGateway4,
                           ucDNSServerAddress,
                           ucMACAddress );

    //Add the endpoint for the default IP address
    FreeRTOS_FillEndPoint( &(networkInterface),
                           &(ipEndpoints[IP_ENDPOINT_PRIVATE_ACCESS]),
                           privateIPAddress,
                           privateNetMask,
                           privateGatewayAddress,
                           ucDNSServerAddress,
                           ucMACAddress );

When setting up a TCP socket, how do I select which endpoint it should use to bind to?
I cannot find any official documentation on how to do that, specifically mentioning the new concept of multiple endpoints.

Any links to such documentation or some hints on how to do this would be greatly appreciated.

Thank you.

@Stonebull

Sockets cannot be bound to endpoints in FreeRTOS+TCP.
While using a socket, for example, sending a UDP packet, the right endpoint is chosen during the ARP lookup. The FreeRTOS_FindEndPointOnNetMask [eARPGetCacheEntryGateWay] is used to find which endpoint has the same network mask as the given IP-address and that endpoint is used.

@tony-josi-aws Thanks for your reply.

Okay, I am fine with FreeRTOS choosing the correct endpoint automatically, but how do I select the IP adress to use?

Is this done through the bind address parameter like for the port?
This example from here sets the IP address field to all zeros. Should the desired IP be set here?

    /* Set the listening port to 10000. */
    memset( &xBindAddress, 0, sizeof(xBindAddress) );  
    xBindAddress.sin_port = ( uint16_t ) 10000;
    xBindAddress.sin_port = FreeRTOS_htons( xBindAddress.sin_port );
    xBindAddress.sin_family = FREERTOS_AF_INET4;

    /* Bind the socket to the port that the client RTOS task will send to. */
    FreeRTOS_bind( xListeningSocket, &xBindAddress, sizeof( xBindAddress ) );

During the bind operation, if you have specified the IP address [xBindAddress.sin_address] to be the address of an endpoint, the socket’s pxEndPoint field will be set to that endpoint (using FreeRTOS_FindEndPointOnIP_IPv4).

Which, if not NULL, will be used to send packets; if it’s NULL, it uses ARP or other methods.

Okay thanks. It’s strange, I keep getting error -22 when trying to bind to the IP address of a specific endpoint that I want to use. I need to look into that.

Please be a little bit more careful about your usage of the term “end point.” An end point is not an IP address, but a combination of an IP address and a port (any machine that supports IP netwotking may and often will listen on several ports at any time, so a client must establish a connection to a specific port on the target device).

Thanks for the clarification, I’ll keep that in mind.

I did some more tests and found out something that seems odd:
First I create two listening TCP server sockets on these endpoints:

  1. 192.168.9.5, Port: 40000
  2. 192.168.168.5, Port: 40001

By using different ports I avoid getting error -22 mentioned above, when I tried creating two sockets with different IP addresses but the same port (40000).
I do not fully understand why I got this error in the first place as the two endpoints still differ from the IP address - so there should not be multiple attempts to bind on the same port :thinking:

The initialization goes smoothly and both endpoint’s IPs can be pinged. When trying to connect to the enpoints via TCP client, I noticed that I can reach both ports on both IPs, which puzzles me.

If as @RAc said, endpoints should are specified by the combination of IP address and port, how comes that I can connect to both ports on both IP addresses?

If you bind the two listenings sockets to INADDR_ANY (IP address 0.0.0.0), both sockets will accept connections on any local interface.

Thanks for your quick reply. Although, I specifically avoid setting the IP address to all 0.0.0.0 … rather I am binding the socket to a specific IP address.

These are the relevant parts of the code I am using:

ip_config_t ipConfigClient =
{
	.ipAdr4 = { 192, 168, 9, 5 },				//Client port: 40000
	.ipSubNetMask4 = { 255, 255, 255, 0 },
	.ipStdGateway4 = { 192, 168, 9, 10}
};

ip_config_t ipConfigPrivate =
{
	.ipAdr4 = { 192, 168, 168, 5 },			    //Client port 40001
	.ipSubNetMask4 = { 255, 255, 255, 0 },
	.ipStdGateway4 = { 192, 168, 168, 10}
};

...

//Add the endpoint for the client address
FreeRTOS_FillEndPoint(  &(networkInterface),
                        &(ipEndpoints[IP_ENDPOINT_CLIENT_ACCESS]),
						ipConfigClient.ipAdr4,
						ipConfigClient.ipSubNetMask4,
						ipConfigClient.ipStdGateway4,
                        ucDNSServerAddress,
						macCfg.macArray );

//Add the endpoint for the default IP address
FreeRTOS_FillEndPoint( &(networkInterface),
					   &(ipEndpoints[IP_ENDPOINT_PRIVATE_ACCESS]),
					   ipConfigPrivate.ipAdr4,
					   ipConfigPrivate.ipSubNetMask4,
					   ipConfigPrivate.ipStdGateway4,
					   ucDNSServerAddress,
					   macCfg.macArray );

/* Initialize the interface descriptor. */
FreeRTOS_IPInit_Multi();

...

//Set the protocol
pCtrl->sockAdrListener.sin_family = FREERTOS_AF_INET4;

//Set the port & IP address of the endpoint: 192.168.9.5:40000
pCtrl->sockAdrListener.sin_port = FreeRTOS_htons( 40000 );
sockAdrListener.sin_address.ulIP_IPv4 = networkEndpoint[0].ipv4_settings.ulIPAddress;

// Bind the socket to the port that the client RTOS task will send to.
retVal = FreeRTOS_bind(socketListener, &sockAdrListener, sizeof(freertos_sockaddr));

...

//Set the protocol
pCtrl->sockAdrListener.sin_family = FREERTOS_AF_INET4;

//Set the port & IP address of the endpoint: 192.168.168.5:40001
pCtrl->sockAdrListener.sin_port = FreeRTOS_htons( 40001 );
sockAdrListener.sin_address.ulIP_IPv4 = networkEndpoint[1].ipv4_settings.ulIPAddress;

retVal = FreeRTOS_bind(socketListener, &sockAdrListener, sizeof(freertos_sockaddr));

Do you have any clues why I still am able to connect to both IP addresses on both ports?


Edit:
I studied the TCP/IP stack’s source code and found the function causing the issue. Packets meant for a socket bound to a specific endpoint (defined by IP and port) are processed by any socket bound to an endpoint with a matching destination IP. This occurs regardless of the port associated to the socket, as long as there exists any socket within the system that was bound to this port number.

Inside the IP task prvIPTask() that handles IP packets, there is a function that handles TCP packets xProcessReceivedTCPPacket(). Here, the function pxTCPSocketLookup(), checks if the TCP packet can be matched with an existing port of a bound socket.

/**
 * @brief As multiple sockets may be bound to the same local port number
 *        looking up a socket is a little more complex: Both a local port,
 *        and a remote port and IP address are being used to find a match.
 *        For a socket in listening mode, the remote port and IP address
 *        are both 0.
 *
 * @param[in] ulLocalIP Local IP address. Ignored for now.
 * @param[in] uxLocalPort Local port number.
 * @param[in] xRemoteIP Remote (peer) IP address.
 * @param[in] uxRemotePort Remote (peer) port.
 *
 * @return The socket which was found.
 */
FreeRTOS_Socket_t * pxTCPSocketLookup( uint32_t ulLocalIP,
                                       UBaseType_t uxLocalPort,
                                       IPv46_Address_t xRemoteIP,
                                       UBaseType_t uxRemotePort );

Within this function it gets clear that only the remote port (which is taken from the received packet) is ever compared to the ports of existing bound TCP sockets. The packet’s destination IP address is ignored for that matter.

# Pseudo code of how the validity of the packet is checked within pxTCPSocketLookup()
for socket in xBoundTCPSocketsList:
	if socket->usLocalPort == uxLocalPort 
      return socket    # If a matching port is found return the socket
return NULL    # else return NULL

So, as long as any bound socket has a matching port, the packet is passed on for processing, regardless of if the port+IP combination matches an endpoint configuration.

I got the following question: Is this a bug or intended behaviour?

The presence of the comment ulLocalIP Local IP address. Ignored for now makes me suppose that the code is still under construction and at some point in a future update, the check to determine if the received packet really matches an existing endpoint, will be added.

Is my assumption correct and can anybody guess how much time it will take to be implemented?

Thanks for your feedback

1 Like

Hi @Stonebull,
Yes, I think you’re right. It appears that the current version of FreeRTOS+TCP does not support binding different IP addresses. In the present implementation, all endpoints share the same TCP socket list when creating socket handlers, which means they’re sharing port numbers on different endpoints.

Thank you.

1 Like

Hello Stonebull!

On an OS like Linux or Windows, it is custom to bind a socket either to 0.0.0.0, or to a specific IP-address.
There is even a socket option SO_REUSEADDR: when set, it is allowed to open multiple server sockets on the same port number and and bind it to a different IP-address.

Within FreeRTOS+TCP, we have decided to ignore the IP-address when calling bind. E.g. Port 80 can be bound only once, system wide. That choice saved a lot resources. Actually a server socket only has a symbolic meaning. It never exchanges streaming data.

The function pxTCPSocketLookup() looks up a matching socket: both the port and the IP-address must match. If there is no match, a matching server socket will be returned (if any).

The same can be said about UDP sockets: it will be found to a port number, and allow messages directed to any IP-address.

So the reason was : create less server sockets and use less memory.

4 Likes