Menu

#130 UDP ARP Behaviour

closed
nobody
None
5
2020-02-24
2019-04-08
Pete Bone
No

Hi,

Firstly thank you for FreeRTOS, it is an excellent platform!

On my current project, I am setting my system time as soon as the system boots from the Internet using the NTP protocol so I send out a request packet to a time server using the FreeRTOS_Sendto() function, but the first packet I send fails. It seems the first packet gets transformed into an ARP request for the default gateway IP address and the original packet is dropped. This would be okay maybe if the FreeRTOS_Sento() function returned some kind of failure to indicate the original packet had not been sent, but it appears to return the size of the packet which was originally sent, leading the user to believe the packet was sent successfully. For now I have added a call to the FreeRTOS_OutputARPRequest() function for the configured default gateway IP address which fixes the issue for me.

Having researched on the internet it seems that the Windows and MacOS IP stacks may also behave this way, I have worked with many IP stack implementations over the years (mostly flavours of UNIX) but have not come accross this behaviour before.

My suggestion is, can the FreeRTOS IP stack be made to behave like most other IP stacks, so on the first UDP packet sent, it sends an ARP request obtains the result and then sends the original packet as the user intended? Or at the very least can FreeRTOS_Sendto() return an error so the request can be repeated maybe?

I hope that makes sense!

Kind Regards,

Pete

Discussion

  • Hein Tibosch

    Hein Tibosch - 2019-04-09

    Hi Pete,

    I hope that makes sense!

    Sure it does.

    I have often thought about how this can be made better, but I haven't found a way yet.

    Ideally, when you call sendto(), the next should happen:

    • Your UDP message will be stored temporarily
    • An ARP packet will be set-up and sent (in order to find the router's MAC-address)
    • In case of no-reply: repeat the ARP request up to e.g. 3 times
    • When ARP reply comes in, the original UDP message will be sent
    • After a repeated time-out: the UDP message(s) must be deleted to free the memory

    The UDP socket must be able to store network packets, which is risky: up to how many packets should be stored as long as ARP is still pending? Until now, outgoing UDP packets are not stored, they're delivered immediately ( when the IP-task has time for it ).

    It would need a mechanism take takes care of ARP look-ups, and repeat them after a time-out.
    These look-ups most be stored in a FIFO. Each entry will have a reference to one or more UDP sockets.

    I hope to make clear that FreeRTOS+TCP choose for simpleness, and in a sense predictability. As long as the remote MAC-address is unknown, the UDP packet is literally turned into an ARP packet, thus not claiming more space.

    What if you write a wrapper:

    int32_t my_sendto( Socket_t xSocket, ... )
    {
        if( needs_arp () == pdTRUE )
        {
            FreeRTOS_OutputARPRequest();
            eResult = eARPGetCacheEntry();
            etc...
        }
        if( needs_arp () == pdFALSE )
        {
            FreeRTOS_sendto( xSocket, ... );
        }
    }
    

    Yes, the above will be blocking: the function will wait for an ARP reply. The reply should not take long though, because the peer or the router are normally located on the same LAN.

     
  • Pete Bone

    Pete Bone - 2019-04-09

    Dear Hein,

    Thank you very much for your prompt reply, I accept your point about keeping FreeRTOS+TCP simple and completely understand the complexity of the code required to deal with this in an ideal way. I would do this my self if I had time!
    Is it worth instead, maybe adding a new ERRNO message, something along the lines of :

    #define pdFREERTOSERRNONOTXARPREQ 141 /* Arp Request was required packet dropped please resend packet */

    in projdefs.h, and have FreeRTOS_Sendto() return this under the offending circumstances, just so the programmer is aware their packet has been dropped? As currently FreeRTOS_Sendto() just returns the transmit size as if it has successfully put the packet on the wire which is quite misleading. It took me a bit of time to track down this issue in the first place, but this would have speeded up the process for me if I had some indication of what was happening. I am just trying to save others the time exploring the issue if they happen to be in this situation.
    I also understand that UDP is an 'unreliable' protocol by definition and it can be argued that it is perfectly valid to silently discard the packet so if you decide to leave things as they are, no problem, it's just a suggestion :-)

    I'm happy to implement a work around.

    Kind Regards,

    Pete

     
  • Hein Tibosch

    Hein Tibosch - 2019-04-10

    Hi Pete,

    Is it worth instead, maybe adding a new ERRNO message, something along the lines of :
    #define pdFREERTOS_ERRNO_NOTXARPREQ 141 /* Arp Request was required
    packet dropped please resend packet */

    I find that a good idea, but the implementation is not straightforward neither.

    FreeRTOS_sendto() only passes the packet to the IP-Task, and then it returns. At the moment it returns, the packet might still sit in the queue to the IP-task as a eStackTxEvent message.

    ( I always recommend to give the IP-task a higher task-priority than the tasks that are using +TCP )

    If the sender wants to know if the UDP packet was delivered, sendto() must wait for the IP-task.

    The normal way of implementing this synchronisation is as follows:

        /* As soon as the IP-task is ready, it will set 'eSELECT_CALL_IP' to
           wakeup the calling API */
        xEventGroupWaitBits( pxSocketSet->xSelectGroup, eSELECT_CALL_IP, pdTRUE, pdFALSE, portMAX_DELAY );
    

    and change the function vProcessGeneratedUDPPacket(), to call:

        /* Set the eSELECT_CALL_IP event bit to wakeup the caller. */
        xEventGroupSetBits( pxSocketSet->xSelectGroup, eSELECT_CALL_IP );
    

    Note that xEventGroupWaitBits() here above is called with an unlimited time-out of portMAX_DELAY. It may never happen that the socket will be closed before the IP-task tries to set the event-bit, that would lead to a crash.
    Also the socket reference must be passed in the eStackTxEvent event, which is less of a problem.

    Conclusion: it is possible to create what you want, but I'm afraid it will not be downward compatible: other users might complain that sendto() has become slower than before, just because it synchronises with the IP-task.

     
  • Pete Bone

    Pete Bone - 2019-04-10

    Dear Hein,

    Okay, I see this has become a trade off between comfort and performance so I think you are probably right we should leave well alone and maintain the performance.

    Just one final comment, is it worth adding a note to the documentation for the FreeRTOS_sendto() function on freertos.org to mention the first call to FreeRTOS_sento() may be morphed to an ARP request and the original packet dropped. So the developers are away of this behaviour?

    Thank you very much for your time, it is appreciated.

    Kind Regards,

    Pete

     
  • Richard Barry

    Richard Barry - 2020-02-24
    • status: open --> closed
     

Log in to post a comment.