Bug in PF_ROUTE on macOS?

I have a question about using PF_ROUTE on macOS to detect IP address changes. Basically, it seems to me that it is broken for IPv4. I have put together a sample program that simply creates the PF_ROUTE socket and then prints out when RTM_NEWADDR, RTM_DELADDR and RTM_IFINFO are received.


What I notice is that when I use a single interface (wifi or ethernet cable) and disconnect the network adapter (disable wifi or unplug the cable) I get nothing at all. If I then reconnect (enable wifi or plug in the cable) I get RTM_NEWADDR but no RTM_IFINFO.


If I have both the wifi and the cable connected at the same time, both disconnecting and then reconnecting one of the interfaces (e.g. disable wifi then re-enable wifi) produces no events at all.


IPv6 seems to work. If I test IPv6 in the same manner, I get an RTM_NEWADDR on connection and RTM_DELADDR on disconnection (the address is the IPv6 link local address - my DHCP server does not serve up IPv6 addresses).


Also if I try to do if_indextoname() when I get the RTM_IFINFO event, it doesn't always work. I need to insert a sleep to be able to consistently get the name back (I chose 500 milliseconds, I didn't spend any time trying other values to see if a lower value would work).


I have tested this program on a MacBook Pro running 10.13, an iMac running 10.14 and a VM running 10.12 - all behave the same way.


So, my question is: is this a bug in the OS, or do I have a fundamental misunderstanding of how the PF_ROUTE socket is supposed to work?


Thanks,

Kevin


#include <SystemConfiguration/SystemConfiguration.h>
#include <net/route.h>
#include <errno.h>

struct cmn_msghdr
{
    u_short    msglen;
    u_char    version;
    u_char    type;
};

int main(int argc, const char * argv[])
{
    char buf[1024];
    size_t len;
    int skt, family = AF_UNSPEC;

    if ( argv[1] && argv[1][0] == '4' )
        family = AF_INET;
    else if ( argv[1] && argv[1][0] == '6' )
        family = AF_INET6;

    // Create a PF_ROUTE socket over which we will receive change messages
    skt = socket( PF_ROUTE, SOCK_RAW, family );
    if ( skt == -1 )
    {
        printf( "ERR: Failed to create PF_ROUTE socket. error %d\n", errno );
        return -1;
    }

    printf( "Watching for %s address changes. Press Ctrl-C to exit\n",
           family == AF_UNSPEC ? "IP" : ( family == AF_INET6 ? "IPv6" : "IPv4" ) );

    // Loop forever waiting for messages
    for (;;)
    {
        len = recv( skt, buf, sizeof(buf), 0 );
        if ( len < 0 )
        {
            switch (errno)
            {
                case EINTR:
                case EAGAIN:
                    printf( "ERR: EINTR or EAGAIN on PF_ROUTE socket\n" );
                    continue;
                default:
                    printf( "ERR: Failed to receive on PF_ROUTE socket. error %d\n", errno );
                    continue;
            }
        }
        if ( len < sizeof( cmn_msghdr ) )
        {
            printf( "ERR: Data received on PF_ROUTE socket too small: %ld bytes\n", len );
            continue;
        }

        struct cmn_msghdr *hdr = (struct cmn_msghdr *)buf;
        if ( hdr->version != RTM_VERSION )
        {
            printf( "ERR: RTM version %d is not supported\n", hdr->version );
            continue;
        }

        switch( hdr->type )
        {
            case RTM_NEWADDR:
                printf( "RTM_NEWADDR\n" );
                break;
            case RTM_DELADDR:
                printf( "RTM_DELADDR\n" );
                break;
            case RTM_IFINFO:
                printf( "RTM_IFINFO\n" );
                break;
            default:
                // Don't care
                continue;
        }
    }

    return 0;
}

Replies

Hmmm, worrying about

PF_ROUTE
I have to ask you about this:

[I’m] using

PF_ROUTE
on macOS to detect IP address changes.

Normally I recommend that folks do this using the Sytem Configuration framework dynamic store API. Did you try that? If not, I recommend that you use it rather than using this much lower-level API.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I am aware of the System Configuration API. And yes I have a similar test program using that API and it works correctly. However, this API, lower-level though it may be, is supposedly supported by the OS. So it being broken seems unacceptable to me.


As to the specific project I'm working on, it's the open source strongSwan project which already heavily uses this low-level API to work across various unix / linux implementations. Creating a Mac-specific implementation (i.e. using the SC API) to work around an OS-level bug is not an ideal solution.

So it being broken seems unacceptable to me.

Fair enough. In that case I recommend that you file a bug report describing the issue.

Please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"