FreeRTOS+TCP 4.0.0 What is the "proper" way to change end-point settings

Some background: I have 1 interface with 2 end-point. One is IPv6 and the other one is IPv4
For the IPv4 end-point, I like to boot up and configure it with the static IP of 0.0.0.0 This allows me to receive broadcasts for a second or two without any possibility of interfering with any device that is already on the network. After those few seconds are up, I read my network settings from a file and I re-configure my IPv4 end-point.
Depending on the settings in the file, I may have to set a valid static IP or I may have to turn on DHCP.
My first gripe is that +TCP does not seem to allow me to turn DHCP on/off on demand. Please correct me if I’m wrong, but I have had to always enable DHCP and then use xApplicationDHCPHook / xApplicationDHCPHook_Multi to decide if it should be allowed or denied. Anyway that solution works, so moving on…

In version 3 of the TCP stack I used FreeRTOS_SetEndPointConfiguration() to set my static IP address or a default IP that would never actually get used if my config file called for DHCP. Side-note: If DHCP fails, I always fall back to IPv4LL, so the default IP passed on to FreeRTOS_SetEndPointConfiguration() never actually gets used.

Now in version 4.0.0 FreeRTOS_SetEndPointConfiguration() still exists but it only populates pxEndPont->ipv4_settings.ulIPAddress and when I want to use a static IP it never works.
I have to explicitly populate pxEndPont->ipv4_defaults.ulIPAddress or call FreeRTOS_FillEndPoint() otherwise, my xApplicationDHCPHook_Multi() callback get’s called with a default IP of 0.0.0.0

It feels like I’m missing something and I’m not 100% sure on what the intention was between ipv4_settings and ipv4_defaults.
It doesn’t feel right that I have to manually assign pxEndPont->ipv4_defaults.ulIPAddress
Should I transition to using FreeRTOS_FillEndPoint() and stop using FreeRTOS_SetEndPointConfiguration?

Would somebody care to shed some light and educate me on the proper way to change network settings runtime? Maybe there are docs out there that I did not find…

Thanks in advance!

Thanks @epopov for the the question and feedback.

For v4.0.0, It is best to use FreeRTOS_FillEndPoint(). An example is provided in our IPv6 demo

FreeRTOS_FillEndPoint() sets the ipv4_defaults, which will be used if DHCP fails (see here).

I can’t recall if there is a case for FreeRTOS_SetEndPointConfiguration(). I see there was a similar thread earlier for FreeRTOS_SetAddressConfiguration()

@Shub @htibosch @moninom1 @tony-josi-aws @ActoryOu → Thoughts ?

Either way, I think there is scope for updating documentation here.

Hi @epopov ,

Thank you for the detailed post.

If you enable DHCP you still have an option to decide whether that end point uses DHCP or not and that can be done by setting bWantDHCP as True or False as needed :

xEndPoints[ 0 ].bits.bWantDHCP = pdTRUE;

For the second query, Yes, as you mentioned FreeRTOS_FillEndPoint or FreeRTOS_FillEndPoint_IPv6 is the correct way to initialise the endpoint which you want to be used as static IP as mentioned in the Initialisation and Setup Page as well.

ipv4_settings was used for the actual IPv4 settings and ipv4_defaults are used in case of DHCP failed or we want to use the static IP address. So In the DHCP case ipv4_defaults will be set first and ipv4_settings is set later, hence FreeRTOS_FillEndPoint() is the best choice.

Please let me know if this helps.

@NikhilKamath Thanks for responding so quickly.
Please consider the fact that I’m referring to changing settings after +TCP has been initialized. I am calling FreeRTOS_FillEndPoint() prior to calling FreeRTOS_IPInit_Multi() with no ill effects, however…

A few seconds after everything is initialized, I attempt to change the settings of one of my endpoints.
If I execute

xEndPoints[ 0 ].ipv4_defaults.ulIPAddress = uiIPAddress_NBO;
FreeRTOS_SetEndPointConfiguration( &uiIPAddress_NBO, &uiNetMask_NBO, &uiGateway_NBO, &uiDNS_NBO, &xEndPoints[0]);

everything works.
But if I try what you are suggesting and instead call this:

FreeRTOS_FillEndPoint( xEndPoints[ 0 ].pxNetworkInterface, &( xEndPoints[ 0 ] ), &uiIPAddress_NBO, &uiNetMask_NBO, &uiGateway_NBO, &uiDNS_NBO, MAC_Address_48 );

weird stuff happens… my IPv4 endpoint works, but my IPv6 endpoint stops working… I haven’t gotten to exactly what’s going on, but for now, let’s just leave it at ā€œweird stuff that looks like bugsā€

After some digging, I’m starting to really question whether calling FreeRTOS_FillEndPoint() after the TCP stack is initialized is a good idea.
Inside FreeRTOS_FillEndPoint() you can find things like:

( void ) memset( pxEndPoint, 0, sizeof( *pxEndPoint ) );

That can’t be good with the whole stack operating…
Further down. we see this:

( void ) FreeRTOS_AddEndPoint( pxNetworkInterface, pxEndPoint );

That is also causing something to go wrong but I haven’t figured out exactly what just yet.

Hi @epopov,

IMHO, FreeRTOS+TCP doesn’t support adding endpoints after TCP stack is initialized. The stack only enables the endpoint by executing network down event at the begining of the task.

Thanks.

Hi @epopov ,

Please refer to the multi-endpoint IPv6 and Ipv4 demo https://github.com/FreeRTOS/FreeRTOS/tree/main/FreeRTOS-Plus/Demo/FreeRTOS_Plus_TCP_IPv6_Demo/IPv6_Multi_WinSim_demo.

The demo has all the code, However, there are 2 main points I would like to highlight here:

  1. We have 2 different functions for fill endpoint. The way ā€œFreeRTOS_FillEndPointā€ for IPv4 and ā€œFreeRTOS_FillEndPoint_IPv6ā€ for IPv6.
  2. The current implementation of FreeRTOS_FillEndPoint/FreeRTOS_FillEndPoint_IPv6 expects the fill function to be called before the ā€œFreeRTOS_IPInit_Multiā€ call. At this point we do not have an update endpoint kind of functionality.

However, I was wondering if the issue that you are facing can be resolved by creating 1 IPv6 and 2 IPv4 endpoints and assign one the IPv4 endpoints to have ā€œstatic IP of 0.0.0.0ā€.

Thanks,
Shub

Thanks @Shub and @ActoryOu

I’m not trying to add an endpoint. I’m trying to modify it’s settings.

About the idea of creating 2 IPv4 endpoints and leaving one at 0.0.0.0 Well that doesn’t really help. I don’t need 2 endpoints. One issue is that at the very early stages of booting I don’t know my IP settings but I still want to be able to receive a type of magic packet that causes the device to revert back to safe functional firmware. At that stage I don’t want to have any IP because I don’t want any chance of a duplicate IP on the network where devices from multiple vendors are present. At this stage, a second endpoint is not helping because I still don’t know what settings to apply to it. After those couple of seconds I mount the FS and read my settings. At this point, I don’t need an endpoint with 0.0.0.0 because I now know my final settings. So you see how I just need to modify and endpoint, not add/remove them and having 2 doesn’t really help me in any way.

OK, since there is no proper, official, or well established way to modify endpoints after the stack has been initialized, here’s my quick and dirty solution in case someone else needs to do this. Please feel free to criticize and advise if you think I’m doing something wrong.

To recap: I need to modify an endpoint’s settings way after +TCP is initialized and up and running.

// Note: all parameters in network byte order
void ApplyNetworkSettings( NetworkEndPoint_t * pxEndPoint, uint8_t bEnableDHCP, uint32_t uiIPAddress_NBO, uint32_t uiNetMask_NBO, uint32_t uiGateway_NBO, uint32_t uiDNS_NBO )
{
    // We are about change endpoint data and the global prvDHCP_Enabled flag from a task that is different than the TCP task.
    vTaskSuspendAll();
    {
        pxEndPoint->ipv4_settings.ulNetMask = uiNetMask_NBO;
        pxEndPoint->ipv4_settings.ulGatewayAddress = uiGateway_NBO;
        pxEndPoint->ipv4_settings.ulDNSServerAddresses[ 0 ] = uiDNS_NBO;
        pxEndPoint->ipv4_settings.ulBroadcastAddress = uiNetMask_NBO | ~( pxEndPoint->ipv4_settings.ulNetMask );
        // Copy the current values to the default values.
        ( void ) memcpy( &( pxEndPoint->ipv4_defaults ), &( pxEndPoint->ipv4_settings ), sizeof( pxEndPoint->ipv4_defaults ) );
        // The default IP-address will be used in case DHCP fails with IPv4LL disabled, or if the user callback chooses to use the default IP-address.
        pxEndPoint->ipv4_defaults.ulIPAddress = uiIPAddress_NBO;

        pxEndPoint->bits.bWantDHCP = bEnableDHCP;
    }
    xTaskResumeAll();

    // Force the TCP stack to re-init.
    FreeRTOS_NetworkDown( pxEndPoint->pxNetworkInterface );
}

Note 1: The vTaskSuspendAll() / xTaskResumeAll(); is a bit heavy-handed and one will probably be OK without it but it’s cheap insurance in a function that should be called extremely rarely.

Note 2: @Shub in the past when I tried using pxEndPoint->bits.bWantDHCP I ran into issues. Therefore I initialized all end-points with bWantDHCP set to pdTRUE. This forced +TCP to call my DHCP callback all the time and I used that callback and a global flag to allow/deny DHCP. Today, I’m realizing that those issues were not with the bWandDHCP flag itself, but were caused by me incorrectly setting either pxEndPoint->ipv4_settings or pxEndPoint->ipv4_defaults but not both. Now that I’ve figured out how to properly update an end-point’s settings, I don’t need that global flag and I’m going back to using pxEndPoint->bits.bWantDHCP as you suggested. I also edited the sample code above to reflect that.

HI @epopov

Thank you for the explanation. Yes we also tried with FreeRTOS_NetworkDown and it seems to update the endpoint when we update ipv4_defaults.ulIPAddress. And as FreeRTOS_SetEndPointConfiguration is updating ipv4_settings.ulIPAddress, it is getting overwritten, hence the solution does not seem to work with FreeRTOS_SetEndPointConfiguration.

Just for clarification - You are setting xEndPoints[ 0 ].bits.bWantDHCP false when you are initialising the endpoint. And later i can see you are setting it to true when you are updating it in following function -

Is my understanding correct? Just wanted to understand how you are using bWantDHCP.

Also thank you for bringing this to our attention. We are working on updating the code to address endpoint updation, and will update you once done. Thank you

@moninom1
To answer your question, In my particular example, I always init xEndPoints[ 0 ].bits.bWantDHCP to pdFALSE. It is then up to how the end-user configired the device. If they wanted DHCP, then I call ApplyNetworkSettings() with bEnableDHCP set to pdTRUE, but if they wanted a static IP, I call that function with bEnableDHCP set to pdFALSE.

If the team is considering adding a function like this to the API, I’d be very happy if you consider my example from above. What I mean by that is that it would be very nice and convenient to have a single function that takes care of the following:

  • Is thread-safe
  • Can be called multiple times with no ill effects. In other words, does not add the endpoint to any list.
  • Allows runtime ( post init ) alteration of IP settings and whether DHCP is enabled.
  • Possibly the same as above but for the Router Advertisement in IPv6
  • Hides all the details about the ipv4_settings and ipv4_defaults structs from the user.
  • Maybe even call FreeRTOS_NetworkDown() internally. On that note, my real ApplyNetworkSettings() function checks whether anything is actually being changed and only calls FreeRTOS_NetworkDown() when needed.

<rant>
I fully understand that someone might just say ā€œThe end-user can simply restart the device after changing settingsā€ but many times I have to remind myself that we live in the 21-st century and we should be producing smart and well-rounded devices that ā€œjust workā€ and don’t require annoying power-cycles on settings changes. Devices that don’t loose their settings on firmware upgrades… those kinds of annoyances from decades past :wink:
</rant>

Thanks again and I really appreciate all of you looking into this.

Hello @epopov,

As usual, I try to keep it short :slight_smile:

At this moment, FreeRTOS_FillEndPoint() will call FreeRTOS_AddEndPoint(), which has a problem. I discovered and solved it this week in PR #1020.

Also FreeRTOS_AddNetworkInterface() will need a small change.

Important to know: when xApplicationDHCPHook_Multi() is called, the code is running from the IP-task, so it is safe to make changes to the ipv4_defaults of the endpoint.

Here is an example that I just tested. Note that it is a quick sketch, just to explain and explore ideas.

First I have started the endpoint with these properties:

===>   Interface 'eth0' endpoint '0.0.0.0' is up
IPv4 address = 0.0.0.0
IP-address : 0.0.0.0
End-point  : up = yes method static
Net mask   : 255.255.255.0
GW         : 0.0.0.0
DNS-0      : 0.0.0.0
DNS-1      : 0.0.0.0
Broadcast  : 0.0.0.255
MAC address: 00-11-22-33-44-41

After 3 seconds I want to start DHCP:

static BaseType_t bDHCP_started = pdFALSE;

/* Only do this once, after running 3 seconds. */
if( ( bDHCP_started == pdFALSE ) &&
    ( xTaskGetTickCount () > pdMS_TO_TICKS( 3000U ) ) )
{
    /* Remember that DHCP was started. */
    bDHCP_started = pdTRUE;
    /* Set the DHCP bit. */
    xEndPoints[0].bits.bWantDHCP = pdTRUE;
    /* Put the endpoint in the down state. */
//  vApplicationIPNetworkEventHook_Multi( eNetworkDown, &( xEndPoints[ 0 ] ) );
/* Try this in stead: */
xEndPoints[ 0 ].bits.bEndPointUp = pdFALSE_UNSIGNED;

EDIT : probably not a good idea to call the network event hook from the application.
Actually I was looking for a function that brings a single endpoint in the eNetworkDown state.

    /* Tell the IP-task that DHCP must be re-started. */
    xSendDHCPEvent( &( xEndPoints[ 0 ] ) );
}

You may wat to stop the scheduler temporarily while running the above code.

Now I see that DHCP negotiation is starting again and my DHCP application hook is called:

eDHCPCallbackAnswer_t xApplicationDHCPHook_Multi( eDHCPCallbackPhase_t eDHCPPhase,
                                                  struct xNetworkEndPoint * pxEndPoint,
                                                  IP_Address_t * pxIPAddress )
{
    eDHCPCallbackAnswer_t aAnswer = eDHCPContinue;

    if( pxEndPoint->bits.bIPv6 == pdTRUE_UNSIGNED )
    {
        FreeRTOS_printf( ("DHCP[%d] IP %pip\n",
            ( int ) eDHCPPhase, pxIPAddress->xIP_IPv6.ucBytes) );
    }
    else
    {
        if( ( bEnableDHCP == pdFALSE ) && ( eDHCPPhase == eDHCPPhasePreDiscover ) )
        {
            /* Use a static address 192.168.2.199. */
            uint32_t ulIPAddress;
            FreeRTOS_inet_pton4( "192.168.2.199", ( void* )&ulIPAddress );
            uint8_t * pucIPAddress = (uint8_t * )&ulIPAddress;
            FreeRTOS_FillEndPoint( &(xInterfaces[0]),
                                   &(xEndPoints[0]),
                                   pucIPAddress,
                                   ucNetMask,
                                   ucGatewayAddress,
                                   ucDNSServerAddress,
                                   ucMACAddress );
            /* Just to make clear that DHCP was cancelled. */
            pxEndPoint->bits.bWantDHCP = pdFALSE;
            aAnswer = eDHCPUseDefaults;
        }
        FreeRTOS_printf( ("DHCP[%d] IP %xip\n",
            ( int ) eDHCPPhase, FreeRTOS_ntohl( pxIPAddress->ulIP_IPv4 ) ) );
    }
    return aAnswer;
}

My vApplicationIPNetworkEventHook_Multi() is called again when DHCP is ready or in case I stopped DHCP.

These are the new properties of the endpoint:

===>   Interface 'eth0' endpoint '192.168.2.199' is up
uxNetworkisUp = 5 expected 5
IPv4 address = 192.168.2.199
End-point  : up = yes method static
Net mask   : 255.255.255.0
GW         : 192.168.2.1
DNS-0      : 118.98.44.100
DNS-1      : 0.0.0.0
Broadcast  : 192.168.2.255
MAC address: 00-11-22-33-44-41

But once again: the FreeRTOS_FillEndPoint() can be called once for every endpoint until PR #1020 is applyied.

2 Likes

Thanks @htibosch for the insights and btw, thumbs up for PR 1020 that is a cool one that makes +TCP one step closer to what a full blown stack can do.

I found your example of calling xSendDHCPEvent() quite interesting, but of course for this to work as a generic solution, I’d have to leave xEndPoints[0].bits.bWantDHCP always enabled and use vApplicationIPNetworkEventHook_Multi() to either allow DHCP to proceed, or change the defaults and return eDHCPUseDefaults whenever I decide to use static IP address.
The more I look into this, the more it feels like we need some better endpoint management API functions… There are workarounds that work though, so I’ll just move on.

1 Like

Could one of the contributors please summarize the state of play for this question, now that it’s two years later? I see that SetAddressConfiguration() is deprecated in the API. Should I use the suggested code from @epopov to change IP address when the endpoint is already initialized?

The backstory in my application is that I will be implementing a custom UDP broadcast protocol (not DHCP) to facilitate setting IP address from an application running on a PC on the local network. Thus, I need to boot up and start networking with some default IP, then change it later when configured by the custom UDP protocol.

Pinging @htibosch too, just in case…

@mark03 wrote:

Pinging @htibosch too, just in case…

Hi Mark!

Could one of the contributors please summarize the state of play for this question, now that it’s two years later?

I have been doing other things during the last 2 years, among which playing with Zephyr. Until I missed FreeRTOS and my AWS colleagues and the forum too much.

Like you, I use a UDP message system for configuring the IP configurations.

Configuring a network through the network is always a bit tricky: when I mistakenly put in an invalid IP address (100.1.168.192 in stead of 192.168.1.100), there is no way of correcting the mistake.

Luckily my device has an LCD menu, and I made an escape route:

Press the button 5 times
Enable DHCP
Store network configuration
Reboot device

and my device is alive again.

I am using the Lantronix UDP messages. That uses both UDP uni-cast and UDP broadcast. It exchanges a binary structures containing network settings. I was required to make an interface, compatible with the XPort.

These messages have a reboot bit, when set the commands are executed, configuration is stored, and the device will reboot.

I have been working on an easier way of setting up the network, writing IP-addresses as strings, in stead of numbers. I called the module net_setup.c:

net_setup.zip (3.4 KB)

MAC-addresses are also stored as strings:

"00-17-17-17-17-43"

Here is an example of a mixed configuration: IP4 and IP6 :

setup_endpoints.c (4.2 KB)

But I think @epopov is right:

The more I look into this, the more it feels like we need some better endpoint management API functions…

we can come up with something better.

Cheers

@mark03 I’m still using my custom ApplyNetworkSettings() function described above and It works the way I want it and I haven’t found a better solution. I personally don’t have the spare time needed to polish my solution, test it extensively and submit a PR.

Try and see if it helps you.

Thanks everyone, I’ll use the @epopov ā€˜s ApplyNetworkSettings() solution for the time being.

Thank you all for your contributions to this interesting discussion.

@epopov wrote:

I fully understand that someone might just say ā€œThe end-user can simply restart the device after changing settingsā€

I agree that we want to create ā€œsmart and well-rounded devices that just workā€.

don’t require annoying power-cycles

I would call it an automatic device reset (a warm boot). It will only take place when there is an essential network change.

When I change the network settings in my device, I can call ApplyNetworkSettings(), to ensure that RA and/or DHCP do their thing. And after 4 seconds, everything should be up and running. A warm reboot takes 4 seconds more (mounting a disk and programming DSP).

Old information held by application

Network settings are re-configured because something didn’t work: GW was wrong, DNS had the wrong address. As long as my application has cached ā€œold informationā€, the software will still not work. Or maybe the application has given up looking for a server address, putting itself in a state eSTATE_FATAL_GIVEUP.

( I am thinking of a module that uploads logging to a server on the internet. It will give up after trying several times to reach the PHP server )

Sockets with old information:

My application opens many sockets: for TCP (client/server), UDP (NTP, DNS), and these have been opened before the network settings were updated. What should we do with these sockets? Can they still be used?

Each socket has a pointer to an endpoint:

typedef struct xSOCKET
{
    /**< The end-point to which the socket is bound. */
    NetworkEndPoint_t * pxEndPoint;
} Socket_t

Is the combination (socket & endpoint) still as expected?

Can a TCP connection be continued safely, or should we reconnect it?