socketsudpipv6

How to "reply" to a UDP packet with IPv6


I'm used to writing code with IPv4, where you can use recvfrom and sendto to reply back to the sender, but with IPv6, things seem to be a little tricky. While I am on Linux right now, this question is more about IPv6 and local addresses in general.

Computer A: fe80::eabf:bbff:cae0:8d3e Computer B: fe80::da3a:ddff:fee3:e257

To ping Computer A from computer B, I can:

ping fe80::eabf:bbff:cae0:8d3e%eth0

or to send packets to Computer A, I can:

echo "xyz" | nc fe80::eabf:bbff:cae0:8d3e%eth0 5353 -u -w 1

On Computer A, I can execute:

    struct sockaddr_in6 sin6 = {
        .sin6_family = AF_INET6,
        .sin6_addr = IN6ADDR_ANY_INIT,
        .sin6_port = htons( MDNS_PORT )
    };

    sd6 = socket( AF_INET6, SOCK_DGRAM, 0 );

    if ( bind( sd6, (struct sockaddr *)&sin6, sizeof(sin6) ) == -1 ); // Checks (didn't fail)

...

    struct sockaddr sender = { 0 };
    socklen_t sl = sizeof( sender );
    int r = recvfrom( sock, buffer, sizeof(buffer), 0, (struct sockaddr*) &sender, &sl );
    printf( "%d / %d\n", r, sender.sin6_family );
    for( i = 0; i < sl; i++ )
        printf( "%02x ", ((uint8_t*)&sender)[i] );
    printf( "\n" );
    sendto( sock, "hello", 5, 0, &sender, sl );

And, I get the following when sending the nc line from above:

4 / 10
0a 00 a9 89 00 00 00 00 fe 80 00 00 00 00 00 00 da 3a dd ff fe e3 e2 57 02 00 00 00 

Which makes perfect sense - this is the address of Computer B. But, many times, the packet is never transmitted. I think this is because sometimes if I cold ping Computer B from Computer A, the pings never go through either.

Without the mapping specifying the interface, it seems like IPv6 can't always be resolved.

How do I make my replies to IPv6 packets go back over the interface from whence they came?

Before going too far down the path of recvmsg this doesn't seem to work for IPv6 in the same way as IPv4, as for IPv6, when using recvmsg there is no control payload, and thus no way to truly reply with sendto -- EDIT: There IS a way - using setsockopt with IPPROTO_IPV6, IPV6_RECVPKTINFO

Any ideas? Suggestions?


Solution

  • If you declare a variable as struct sockaddr then it is smaller than the sockaddr_in6 that's being stored in it – on my system it's a whole 12 bytes smaller – leading to a buffer overflow every time recvfrom() tries to put an inet6 address there. Either the recvfrom() is overwriting another variable beyond the struct (perhaps it puts garbage in the 'sl' variable when storing the address), or the other way around (if recvfrom() updates &sl with the new size, it might overwrite part of the address).

    The generic "fits all" type you wanted would be struct sockaddr_storage.

    struct sockaddr is small because it predates IPv6, and increasing the size of a public type doesn't really work out on Linux, so it is only useful when used for casting pointers. (On that note, many projects end up inventing a union sockaddr_any or similar, as a more convenient way that avoids casts.)

    (Yes, the sin6_scope_id matters, but recvfrom() always fills it in – it thinks it's filling in a sockaddr_in6 either way.)