FreeRTOS+TCP first ping not being sent.

cvanbrederode wrote on Thursday, July 11, 2019:

So I have a controller board up and running with FreeRTOS v10.0.1 and +TCP v2.0.1, and everything is working gangbusters (I have simple Telnet, TFTP, and ModBusTCP/UDP running).

Just today, I went to implement outgoing pings, as a testing feature during development. I have a setup similar to the example on the website, where the task sending the pings waits on a queue, that is notified by the ping reply callback.

Works great, except for one minor issue. The first ping never gets sent, and is reported as a timeout (after 500ms). As far as I can tell, the first call to FreeRTOS_SendPingRequest doesn’t actually send a ping (it never shows up in Wireshark), but it also does not return pdFAIL either.

Watching wireshark, the first time I run the ping command, which sends four pings, I only see three Echo request/repy pairs. The second (and further) times I run the command, I see four pairs. What I do see before the first echo request is an ARP packet “Who has?”, which gets a ARP response.

Although, if I call ping on an IP that is not present on the network, the echo requests are not sent. I also don’t see any ARP “Who has?”, but there’s a little part of me that thinks that may be how I’m running wireshark.

EDIT: I just did another test, where I have the PC connect over ModBusTCP before attempting the ping, so that the computer’s IP is in the ARP cache, and the first ping is sent normally then.

Is this normal behavior and I’m just missing something?

heinbali01 wrote on Friday, July 12, 2019:

Chris, I think that you have mentioned the answer in your post: the first ping gets lost because it is turned into an ARP packet.
You can see that code in FreeRTOS_UDP_IP.c:

void vProcessGeneratedUDPPacket( NetworkBufferDescriptor_t * const pxNetworkBuffer );

When eARPGetCacheEntry returns eARPCacheMiss, an ARP-request will be sent.

A ICMP ( echo ) packet is send to an IP-address, but before it can be sent, it must know the MAC-address.
When there is no answer to the ARP request, you won’t even see an ICMP packet.

After you have had TCP contact with the remote peer, it’s IP-address is stored in the ARP table. And thus no echo packet will get lost.

You could prevent this behaviour by checking if the IP-address is known in the ARP cache table. And if it is not known, send out an extra PING request.

Note that the function FreeRTOS_OutputARPRequest() is not for public use: it calls xNetworkInterfaceOutput(), which is a non-reentrant function.

Apologies if this is documented somewhere and I am just failing to find it, but how would one go about checking if the IP address is in the ARP cache? FreeRTOS_OutputARPRequest() is the only reference to ARP I’m seeing on the FreeRTOS+TCP API page.

Thanks!

That is a good question. When the IP-stack doesn’t know the MAC-address of a peer, it turns a message into an ARP request. Therefore the first ARP or UDP messages get lost.

Recently I proposed a new function that resolves the address, it is called xARPWaitResolution():

BaseType_t xARPWaitResolution( uint32_t ulIPAddress,
							   TickType_t uxTicksToWait )
{
	BaseType_t xResult = -pdFREERTOS_ERRNO_EADDRNOTAVAIL;
	TimeOut_t xTimeOut;
	MACAddress_t xMACAddress;
	eARPLookupResult_t xLookupResult;

	xLookupResult = eARPGetCacheEntry( &( ulIPAddress ), &( xMACAddress ) );

	if( xLookupResult == eARPCacheMiss )
	{
		const TickType_t uxSleepTime = pdMS_TO_TICKS( 10U );

		/* We might use ipconfigMAX_ARP_RETRANSMISSIONS here. */
		FreeRTOS_OutputARPRequest( ulIPAddress );
		vTaskSetTimeOutState( &xTimeOut );

		for( ; ; )
		{
			xLookupResult = eARPGetCacheEntry( &( ulIPAddress ), &( xMACAddress ) );

			if( ( xTaskCheckForTimeOut( &( xTimeOut ), &( uxTicksToWait ) ) == pdTRUE ) ||
				( xLookupResult != eARPCacheMiss ) )
			{
				break;
			}

			vTaskDelay( uxSleepTime );
		}
	}

	if( xLookupResult == eARPCacheHit )
	{
		xResult = 0;
	}

	return xResult;
}
/*-----------------------------------------------------------*/

It returns zero when the MAC-address is found.

uxTicksToWait determines the maximum time to wait. On a LAN, it will normally take a few milliseconds.

ulIPAddress is network-endian encoded, i.e. big-endian.

I will propose to add the routine to the official repo. Please try it out.