Link Layer Address addition to FreeRTOS_TCP

beaker1969 wrote on Tuesday, June 09, 2015:

Hi

I have been using the FreeRTOS_TCP in out latest product using the the STM32F429. In the Network address configuration stage i have needed the device to use a Link Layer Address if the DHCP fails. I hope that this will be implemented fully in a future release but in the meantime i have implemented a very simple version of it in my code. It is based on the description here http://www.zeroconf.org/QDIPv4LL.html

i have had to modify FreeRTOS_ARP.c and .h in order for checking if the IP address i wish to use is already in use on the network.

i know this is not a full implementation but it works good enough for what i need. I hope this helps anyone who may want to expand it.

in FreeRTOSIPConfig.h

i added

#define ipconfigDHCP_Link_Local_Addressing			1		// added by AW - new for Link Local Addressing.

The main work is done in the FreeRTOS_DHCP.c

in FreeRTOS_DHCP.c i modified the following

i added 3 new DHCP states.

typedef enum
{
	eWaitingSendFirstDiscover = 0,	/* Initial state.  Send a discover the first time it is called, and reset all timers. */
	eWaitingOffer,					/* Either resend the discover, or, if the offer is forthcoming, send a request. */
	eWaitingAcknowledge,			/* Either resend the request. */
	eLeasedAddress,					/* Resend the request at the appropriate time to renew the lease. */
	eNotUsingLeasedAddress,			/* DHCP failed, and a default IP address is being used. */
	eInitDummyWaitingAcknowledge,	/* added by AW - init dummy acknowledge - this may be called many times if link local address is already being used */
	eWaitingDummyAcknowledge,        /* added by AW - for Link Local addressing  */
	eDummyAcknowledgeArpTest		/* added by AW - for Link Local addressing  */
} eDHCPState_t;

i added default Link Layer addressing address and subnet mask.

unsigned char LLAIPAddress[4] = {169,254,221,196};	
unsigned char LLASubnetMask[4] = {255,255,0,0};		

in vDHCPProcess i modified the following to the switch statement.

	case eWaitingOffer :

		/* Look for offers coming in. */
		if( prvProcessDHCPReplies( dhcpMESSAGE_TYPE_OFFER ) == pdPASS )
		{
			/* An offer has been made, generate the request. */
			xDHCPData.xDHCPTxTime = xTaskGetTickCount();
			xDHCPData.xDHCPTxPeriod = dhcpINITIAL_DHCP_TX_PERIOD;
			prvSendDHCPRequest( );
			xDHCPData.eDHCPState = eWaitingAcknowledge;
		}
		else
		{
			/* Is it time to send another Discover? */
			if( ( xTaskGetTickCount() - xDHCPData.xDHCPTxTime ) > xDHCPData.xDHCPTxPeriod )
			{
				/* Increase the time period, and if it has not got to the
				point of giving up - send another discovery. */
				xDHCPData.xDHCPTxPeriod <<= 1;

				//temp = ipconfigMAXIMUM_DISCOVER_TX_PERIOD;
				//if( xDHCPData.xDHCPTxPeriod <= ipconfigMAXIMUM_DISCOVER_TX_PERIOD)		// original.
				if( xDHCPData.xDHCPTxPeriod <= (TickType_t)ipconfigMAXIMUM_DISCOVER_TX_PERIOD  &&  xNetworkAddressing.ulDefaultIPAddress == 0x00 )		//new version
				{
					xDHCPData.ulTransactionId++;
					xDHCPData.xDHCPTxTime = xTaskGetTickCount();
					xDHCPData.xUseBroadcast = !xDHCPData.xUseBroadcast;
					prvSendDHCPDiscover( );
					FreeRTOS_debug_printf( ( "vDHCPProcess: timeout %lu ticks\n",
						xDHCPData.xDHCPTxPeriod ) );
				}
				else
				{
					FreeRTOS_debug_printf( ( "vDHCPProcess: giving up %lu > %lu ticks\n",
						xDHCPData.xDHCPTxPeriod, ipconfigMAXIMUM_DISCOVER_TX_PERIOD ) );

						// only use fake ack if default IP address = 0x00 and link local addressing is used.
						if(ipconfigDHCP_Link_Local_Addressing == 0x01 && xNetworkAddressing.ulDefaultIPAddress == 0x00)
						{
							//do a fake ACK
							xDHCPData.eDHCPState = eInitDummyWaitingAcknowledge;
						}
						else
						{
							/* Revert to static IP address. */
							taskENTER_CRITICAL();
							{
								*ipLOCAL_IP_ADDRESS_POINTER = xNetworkAddressing.ulDefaultIPAddress;
								iptraceDHCP_REQUESTS_FAILED_USING_DEFAULT_IP_ADDRESS( xNetworkAddressing.ulDefaultIPAddress );
							}
							taskEXIT_CRITICAL();

							xDHCPData.eDHCPState = eNotUsingLeasedAddress;
							vIPSetDHCPTimerEnableState( pdFALSE );

							/* DHCP failed, the default configured IP-address will be used
							Now call vIPNetworkUpCalls() to send the network-up event, start Nabto
							and start the ARP timer*/
							vIPNetworkUpCalls( );

							/* Close socket to ensure packets don't queue on it. */
							FreeRTOS_closesocket( xDHCPData.xDHCPSocket );
							xDHCPData.xDHCPSocket = NULL;
						}

				}
			}
		}
		break;

i added the following case’s to the switch statement.

	case eInitDummyWaitingAcknowledge:
		//do a fake ACK
		xDHCPData.xDHCPTxTime = xTaskGetTickCount();
		xDHCPData.xDHCPTxPeriod = dhcpINITIAL_DHCP_TX_PERIOD;
		xDHCPData.eDHCPState = eWaitingDummyAcknowledge;

    //prepare xDHCPData with data to test
		xDHCPData.ulOfferedIPAddress = FreeRTOS_inet_addr_quick( LLAIPAddress[ 0 ], LLAIPAddress[ 1 ], LLAIPAddress[ 2 ], LLAIPAddress[ 3 ] );
		xDHCPData.ulLeaseTime = dhcpDEFAULT_LEASE_TIME;		// don't care about lease time. just put anything.
		xNetworkAddressing.ulNetMask = FreeRTOS_inet_addr_quick( LLASubnetMask[ 0 ], LLASubnetMask[ 1 ], LLASubnetMask[ 2 ], LLASubnetMask[ 3 ] );
		flg_got_dummy_dhcp_ack = 0x01;
		break;


	case eWaitingDummyAcknowledge :
			FreeRTOS_debug_printf( ( "vDHCPProcess: dummy acked %lxip\n", FreeRTOS_ntohl( xDHCPData.ulOfferedIPAddress ) ) );

			/* DHCP completed.  The IP address can now be used, and the
			timer set to the lease timeout time. */
			*ipLOCAL_IP_ADDRESS_POINTER = xDHCPData.ulOfferedIPAddress;

			/* Setting the 'local' broadcast address, something like 192.168.1.255' */
			xNetworkAddressing.ulBroadcastAddress = ( xDHCPData.ulOfferedIPAddress & xNetworkAddressing.ulNetMask ) |  ~xNetworkAddressing.ulNetMask;

			/* Close socket to ensure packets don't queue on it. not needed anymore as DHCP failed. but still need timer for ARP testing. */
			FreeRTOS_closesocket( xDHCPData.xDHCPSocket );
			xDHCPData.xDHCPSocket = NULL;

			xDHCPData.xDHCPTxPeriod = 3000 + ((unsigned char)get_random_number() & 0x3ff) / portTICK_PERIOD_MS;	// do ARP test every (3 + 0-1024mS) seconds.
			xDHCPData.eDHCPState = eDummyAcknowledgeArpTest;		// setting IP address manually so set to not using leased address mode.
			flg_arp_clash = 0x00;		//reset flag that shows if have ARP clash.
			vARPSendGratuitous();
		break;


	case eDummyAcknowledgeArpTest:

		if( ( xTaskGetTickCount() - xDHCPData.xDHCPTxTime ) > xDHCPData.xDHCPTxPeriod )
		{
			if(flg_arp_clash == 0x00)
			{
				//ARP OK. proceed.
				iptraceDHCP_SUCCEDEED( xDHCPData.ulOfferedIPAddress );

				/* DHCP succe, the default configured IP-address will be used
				Now call vIPNetworkUpCalls() to send the network-up event, start Nabto
				and start the ARP timer*/
				vIPNetworkUpCalls( );
			}
			else
			{
				//ARP clashed - try another IP address
				LLAIPAddress[ 2 ] = (unsigned char)(get_random_number() & 0xff);		//get value 0-255. for ip address 3rd byte of IP address to try.
				LLAIPAddress[ 3 ] = (unsigned char)(get_random_number() & 0xff);		//get value 0-255. for ip address 4th byte of IP address to try.
				xDHCPData.eDHCPState = eInitDummyWaitingAcknowledge;
			}
		}
		break;

in FreeRTOS_ARP.c

i added

volatile unsigned char flg_arp_clash = 0x00;	//added by AW. for use in DHCP for checking of Link Layer Addressing of IP address assigned.

i modified the ipARP_REPLY case statement in eARPProcessPacket routine.

		case ipARP_REPLY :
			vARPClash(pxARPHeader);		//added by AW.
			iptracePROCESSING_RECEIVED_ARP_REPLY( pxARPHeader->ulTargetProtocolAddress );
			vARPRefreshCacheEntry( &( pxARPHeader->xSenderHardwareAddress ), pxARPHeader->ulSenderProtocolAddress );
			break;

i added the following routine

static void vARPClash(xARPHeader_t *pxARPHeader )
{
	//process received ARP frame to see if there is a clash.
	if(pxARPHeader->ulSenderProtocolAddress == *ipLOCAL_IP_ADDRESS_POINTER)
	{
		extern void vApplicationARPClashHook(void);
		flg_arp_clash = 0x01;
	}

}

in FreeRTOS_ARP.h

extern volatile unsigned char flg_arp_clash;	//added by AW. for use in DHCP for checking     of Link Layer Addressing of IP address assigned.

the random number generator built into the STM32f429

uint32_t get_random_number(void)
{
	//use HAL system to get random number.
	uint32_t ret_value;

	  if(HAL_RNG_GenerateRandomNumber(&hrng, &ret_value) == HAL_OK)
	  {
	    return ret_value;
	  }
	  else
	  {
	    return 0;
	  }
}

rtel wrote on Tuesday, June 09, 2015:

Very nice - and thanks for sharing.

Zeroconf is indeed on the roadmap - the order in which things get added really depends on the user demand. Zero conf based on the idea provided on the link you sent would indeed seem to add some nice functionality with limited effort.

We have already done some work on DHCP in the head revision, which will become a new labs release very soon. That is to allow changes between static and dynamic addressing at run time, and to get gateway, etc. settings from DHCP without accepting the IP address.

Regards.

beaker1969 wrote on Tuesday, June 09, 2015:

glad you liked it. Hopefully my input on zeroconf will push it up the list a bit :wink:

great news on the DHCP option in runtime. I had done a modification to do that but it will be great to have it in an offical release.

Thanks

heinbali01 wrote on Wednesday, June 10, 2015:

Hi Alan, we really appreciate your post with all those ideas. I’m studying all that you wrote and will send you a detailed answer today.

Thanks

heinbali01 wrote on Wednesday, June 10, 2015:

use a Link Layer Address if the DHCP fails. I hope that this will be
implemented fully in a future release but in the meantime i have
implemented a very simple version of it in my code.

If it’s up to me it will become an option: ‘ipconfigUSE_DHCP_Link_Local_Addressing’, and it will depend on ‘ipconfigUSE_DHCP’.

An alternative to using IPv4LL is to use the “last DHCP-supplied IP-address”. In my office we switch off all devices at night. In the morning, in case the DHCP server is still down, all devices will assume yesterday’s IP address (also gateway and netmask). Now after I reset my buggy router/DHCP server, the devices can go online (audio streaming) immediately.

Now suppose the devices use IPv4LL, and later on you turn on DHCP, how can the devices still communicate among them? Some will have a 169.254.x.x address where others received a regular 196.168.x.x address. Should all IPv4LL users then contact tthe DHCP server and re-apply for an IP-address?

uint32_t get_random_number(void)
{
    if(HAL_RNG_GenerateRandomNumber(&hrng, &ret_value) == HAL_OK)
}

Note that every +TCP port already has an implementation of a random number generator called: ‘ipconfigRAND32()’.

Thanks,
Hein

rtel wrote on Wednesday, June 10, 2015:

all devices will assume yesterday’s IP address

I’m curious. Do you save this information to non-volatile memory so it can be used again on next power up?

Now suppose the devices use IPv4LL, and later on you turn on DHCP, how can the devices still communicate among them?

Assuming they were all on the same local network (i.e. not using a mix of IP addresses) then could you not still use the hostname rather than the IP address to contact them?

Regards.

heinbali01 wrote on Wednesday, June 10, 2015:

Do you save this information to non-volatile memory so it
can be used again on next power up?

Yes I store it somewhere in the program flash. If devices aren’t moved around, they get the same DHCP anwers everyday and there is no need the change the stored information.

Assuming they were all on the same local network (i.e. not
using a mix of IP addresses) then could you not still use
the hostname rather than the IP address to contact them?

I would like to do this kind of test:

“Device_1” gets a LL address
Enable DHCP-server
“Device_2” gets a normal address

Both devices have LLMNR and NBNS protocols enabled.

ping (or surf to) “Device_1”
ping (or surf to) “Device_2”

Maybe it just works because LinkLayer and multi-cast addresses always work, I don’t know.

I’m also curious if “Device_1” will be able to get onto the Internet (through a gateway)/

Regards.

rtel wrote on Wednesday, June 10, 2015:

I’m also curious if “Device_1” will be able to get onto the Internet
(through a gateway)/

I don’t think so, it would not be on the same network as the gateway
(same when Windows takes a ll address), but you would be able to connect
to it locally.

beaker1969 wrote on Monday, June 22, 2015:

I’ve added some more functionality to the DHCP function so that if it initially fails and allocates a Link Layer address the RTOS will continue to look for a DHCP server and if it finds one will request an address from it.

The modifications are.

remove the following from ‘eWaitingDummyAcknowledge’.

/* Close socket to ensure packets don't queue on it. not needed anymore as DHCP failed. but still need timer for ARP testing. */
FreeRTOS_closesocket( xDHCPData.xDHCPSocket );
xDHCPData.xDHCPSocket = NULL;

add the following case to the switch etc in ‘vDHCPProcess’

	case eWaitingOfferAgain :

		/* Look for offers coming in. */
		if( prvProcessDHCPReplies( dhcpMESSAGE_TYPE_OFFER ) == pdPASS )
		{
			/* An offer has been made, generate the request. */
			xDHCPData.xDHCPTxTime = xTaskGetTickCount();
			xDHCPData.xDHCPTxPeriod = dhcpINITIAL_DHCP_TX_PERIOD;
			prvSendDHCPRequest( );
			xDHCPData.eDHCPState = eWaitingAcknowledge;
		}
		else
		{
			/* Is it time to send another Discover? */
			if( ( xTaskGetTickCount() - xDHCPData.xDHCPTxTime ) > xDHCPData.xDHCPTxPeriod )
			{
					xDHCPData.ulTransactionId++;
					xDHCPData.xDHCPTxTime = xTaskGetTickCount();
					xDHCPData.xUseBroadcast = !xDHCPData.xUseBroadcast;

					prvSendDHCPDiscover( );

					FreeRTOS_debug_printf( ( "vDHCPProcess: LL timeout %lu ticks\n",
						xDHCPData.xDHCPTxPeriod ) );
			}
		}
		break;

add ‘eWaitingOfferAgain’ to typdef enum of ‘eDHCPState_t’

because the source address of the DHCP discover must be 0.0.0.0 i had to modifiy
‘FreeRTOS_UDP_IP.c’

from what i can figure out FreeRTOS_TCP puts the default IP address in the ‘vProcessGeneratedUDPPacket’ routine. Therefore to change it i have to detect when an DHCP discover is being sent and edit it.

at the beginning of ‘vProcessGeneratedUDPPacket’ add -

uint16_t location;		
xDHCPMessage_t *pxDHCPMessageAW;

find

memcpy( ( void *) &( pxUDPPacket->xEthernetHeader.xSourceAddress ), ( void * ) xDefaultPartUDPPacketHeader.ucBytes, sizeof( xDefaultPartUDPPacketHeader ) );

and add this below it.

		location = sizeof( xUDPPacket_t );										
		pxDHCPMessageAW = (xDHCPMessage_t*) &(pxNetworkBuffer->pucEthernetBuffer[ location ]);		
		if(pxDHCPMessageAW->ucOpcode == 0x01 && pxDHCPMessageAW->ucAddressType == 0x01 && pxDHCPMessageAW->ucClientHardwareAddress[4] == *(ipLOCAL_MAC_ADDRESS+4))
		{
			pxUDPPacket->xIPHeader.ulSourceIPAddress = 0UL;		set Ip Address to 0.0.0.0
		}

yes, i i know its crude but it seems to work ok.

i also had to copy over the following from ‘FreeRTOS_DHCP.c’ to the begining of FreeRTOS_UDP_IP.c

#define dhcpCLIENT_HARDWARE_ADDRESS_LENGTH		16
#define dhcpSERVER_HOST_NAME_LENGTH				64
#define dhcpBOOT_FILE_NAME_LENGTH 				128

#include "pack_struct_start.h"
struct xDHCPMessage
{
	uint8_t ucOpcode;
	uint8_t ucAddressType;
	uint8_t ucAddressLength;
	uint8_t ucHops;
	uint32_t ulTransactionID;
	uint16_t usElapsedTime;
	uint16_t usFlags;
	uint32_t ulClientIPAddress_ciaddr;
	uint32_t ulYourIPAddress_yiaddr;
	uint32_t ulServerIPAddress_siaddr;
	uint32_t ulRelayAgentIPAddress_giaddr;
	uint8_t ucClientHardwareAddress[ dhcpCLIENT_HARDWARE_ADDRESS_LENGTH ];
	uint8_t ucServerHostName[ dhcpSERVER_HOST_NAME_LENGTH ];
	uint8_t ucBootFileName[ dhcpBOOT_FILE_NAME_LENGTH ];
	uint32_t ulDHCPCookie;
	uint8_t ucFirstOptionByte;
}
#include "pack_struct_end.h"
typedef struct xDHCPMessage xDHCPMessage_t;

hope this helps someone.

rtel wrote on Monday, June 22, 2015:

Great thanks. I know Hein has been looking at this and you have exchanged some info. I don’t think the zero conf stuff will make it into the next release, but it will get included soon, as there will be a series of released made in the coming weeks and months. I’m currently creating the documentation for the next labs release now, which includes the Zynq demo, and forced myself to freeze the code to ensure this actually gets out. After that though the SAM4, STM32 and LPC18xx demos will be cleaned up and rolled out…

Regards.

lightelf wrote on Monday, June 22, 2015:

TI Tiva 129, STM32 and LPC18xx share same Ethernet MAC (IP block from Synopsys), so one driver can be used.

rtel wrote on Monday, June 22, 2015:

That’s good to know. Our current implementations use adapted versions
of the drivers provided by the chip companies themselves, so it looks
like a future attraction is to also provide a set of “IP” specific,
rather than “chip” specific drivers that could be optimised. That would
also speed up porting to new devices that also used the same IP.

Regards.

lightelf wrote on Monday, June 22, 2015:

By the way, what about PTP (1588v2) support? For mentioned IP block it can be implemented directly in the MAC driver (raw Ethernet slave at least).

lightelf wrote on Thursday, September 10, 2015:

Just a note: Analog Devices BF60x in the same wagon, as well as AllWinner SoCs

heinbali01 wrote on Thursday, September 10, 2015:

Hi Alan,

If you want have a look at the latest release of +TCP ( see www.freertos.org/labs ).

You can now define ipconfigDHCP_FALL_BACK_AUTO_IP in your FreeRTOSIPConfig.h: after DHCP has failed, it will use a LinkLayer IP address in the range 169.254.x.x. It will send-out ARP requests to test if the selected IP address has already been chosen by another device. As soon as a free IP address has been found, the IP driver will use that and start running.

Regards.