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