-2

I'm writing some socket code and based on some params I'm using either IPv4 or IPv6. For that I have a code like this:

struct sockaddr final_addr;
...
struct sockaddr_in6 addr6;
... 
memcpy(&final_addr, &addr6, size);
...
bind(fd, &final_addr, size);

This works fine. However if I do (which was my initial idea)

struct sockaddr final_addr;
...
struct sockaddr_in6 addr6;
... 
final_addr = *reinterpret_cast<struct sockaddr*>(&addr6);
...
bind(fd, &final_addr, size);

then it fails on bind with Cannot assign requested address error.

Note that this incorrect code works fine if I switch to IPv4's sockaddr_in.

What's going on here? Why can't I just reinterpret sockaddr_in6 as sockaddr?

freakish
  • 54,167
  • 9
  • 132
  • 169
  • `sockaddr` is an opaque pointer that either backs up a `sockaddr_in` (IPv4) or `sockaddr_in6` (IPv6). Besides the `sin_family` field these structures are totally different. Neither `reinterpret_cast` nor `memcpy()` will work correctly to convert from one to the other. – πάντα ῥεῖ Oct 06 '16 at 17:13
  • does sockaddr_in6 have a const keyword in with the definition? – BenPen Oct 06 '16 at 17:17
  • 1
    I think you misunderstood. Ive removed most of the initialization code. The code works fine (sockets bind and work correctly) with memcpy. It fails with reinterpret_cast though. – freakish Oct 06 '16 at 17:18
  • It could be some sort of a qualifier to the sockaddr definition. Can you have your IDE find the definition of sockaddr? – BenPen Oct 06 '16 at 17:20
  • @πάνταῥεῖ I'm not a specialist, but I don't think you are correct. Every resource I've seen does casting `sockaddr_in6*` to `sockaddr*` on `bind` (e.g. http://stackoverflow.com/questions/13504934/binding-sockets-to-ipv6-addresses ) How would you call `bind` otherwise? Or is it that cast via `()` does something different? – freakish Oct 06 '16 at 17:34
  • @BenPen About constantnes: it does not. Does it matter? Isn't it only compile time flag? – freakish Oct 06 '16 at 17:36
  • What is the value of `size` in the first version? – AnT stands with Russia Oct 06 '16 at 17:37
  • `sockaddr` is merely identical with `sockkaddr_in`: http://pubs.opengroup.org/onlinepubs/009696699/basedefs/sys/socket.h.html – πάντα ῥεῖ Oct 06 '16 at 17:38
  • Some implementations can have run time checks for qualifiers, I think, but it was only a thought... – BenPen Oct 06 '16 at 17:38
  • @AnT It's actually `sizeof(addr6)`. I double checked sizes, they are correct. Otherwise I wouldn't expect it to work in any case. – freakish Oct 06 '16 at 17:39
  • @freakish: If so, then the first version copies `sizeof(struct sockaddr_in6)` bytes, while the second version copies `sizeof(struct sockaddr)` bytes. – AnT stands with Russia Oct 06 '16 at 17:41
  • @AnT Ahh, but of course. They differ in size. That makes total sense now. Please post it as answer. – freakish Oct 06 '16 at 17:42
  • @AnT By the way: is that a memory leak? Why am I allowed to copy bigger chunk of memory to a smaller one? Shouldn't it segfault? I have to rethink this `memcpy`. – freakish Oct 06 '16 at 17:47
  • @freakish: It can only segfault if you cross into the "uncharted" address space region protected by the OS. As long as you are clobbering memory belonging to your process (like trashing other local variables located nearby in memory) no segfault will occur. – AnT stands with Russia Oct 06 '16 at 17:55
  • And clobbering memory in your stack can create ugly behavior, up to and including crashing only when you return from a function after it "finished successfully." – BenPen Oct 06 '16 at 17:58
  • @BenPen Right, the function never returned. There was an infinite listener loop at the end. Maybe that's why I haven't noticed. – freakish Oct 06 '16 at 18:36

2 Answers2

2

If in the first version of the code size is sizeof(addr6) (as you stated in the comments), then the first version of the code uses memcpy to copy sizeof(struct sockaddr_in6) bytes of data.

The second version of the code uses regular struct sockaddr assignment to copy only sizeof(struct sockaddr) bytes.

sizeof(struct sockaddr) is smaller than sizeof(struct sockaddr_in6), which makes these two code samples different.

Note that in the first version the recipient object in that memcpy is of struct sockaddr type, i.e. it is smaller than the number of bytes copied. Memory overrun occurs, which clobbers some other data stored in adjacent memory locations. The code "works" only by accident. I.e. if this bit "works", then some other piece of code (the one that relies on the now-clobbered data) is likely to fail.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • "works" means that you wrote over stuff that was ... Further up in the stack? so you overwrote the stuff on the stack allocated previously? In a bad enough case it would write all over your return code address, right? – BenPen Oct 06 '16 at 17:55
  • Also you are subject to being clobbered as well. – BenPen Oct 06 '16 at 18:00
1

sockaddr is not large enough to hold data from a sockaddr_in6. The first code example "works" only because the source address data is being copied in full, and you are passing a full address to bind(), but you have also trashed stack memory during the copying. The second code example does not work because it is truncating the address data during the assignment, but it is not trashing stack memory anymore.

Neither code example will work correctly for IPv6, but both will "work" OK for IPv4 because sockaddr is large enough to hold data from a sockaddr_in, so no trashing or truncating is happening.

To ensure final_addr is large enough to hold data from either sockaddr_in or sockaddr_in6, it needs to be declared as a sockaddr_storage instead, which is guaranteed to be large enough to hold data from any sockaddr_... struct type:

struct sockaddr_storage final_addr;
int size;

if (use IPv6)
{
    struct sockaddr_in6 addr6;
    // populate addr6 as needed...

    memcpy(&final_addr, &addr6, sizeof(addr6));
    or
    *reinterpret_cast<struct sockaddr_in6*>(&final_addr) = addr6;

    size = sizeof(addr6);
}
else
{
    struct sockaddr_in addr4;
    // populate addr4 as needed...

    memcpy(&final_addr, &addr4, sizeof(addr4));
    or
    *reinterpret_cast<struct sockaddr_in*>(&final_addr) = addr4;

    size = sizeof(addr4);
}

bind(fd, reinterpret_cast<struct sockaddr*>(&final_addr), size);

A better option would be to use getaddrinfo() or equivalent to create a suitable sockaddr_... memory block for you:

struct addrinfo hints;
memset(&hints, 0, sizeof(hints));

hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = AF_UNSPEC;

struct addrinfo *addr = NULL;

if (getaddrinfo("ip address here", "port here", &hints, &addr) == 0)
{
    bind(fd, addr->ai_addr, addr->ai_addrlen);
    freeaddrinfo(addr);
}

Alternatively:

struct addrinfo hints;
memset(&hints, 0, sizeof(hints));

hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = ...; // SOCK_STREAM, SOCK_DGRAM, etc...
hints.ai_protocol = ...; // IPPROTO_TCP, IPPROTO_UDP, etc...

struct addrinfo *addrs = NULL;

if (getaddrinfo(NULL, "port here", &hints, &addrs) == 0)
{
    for (struct addrinfo *addr = addrs; addr != NULL; addr = addr->ai_next)
    {
        int fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
        if (fd != -1)
        {
            bind(fd, addr->ai_addr, addr->ai_addrlen);
            // save fd somewhere for later use
            ...
        }
    }
    freeaddrinfo(addrs);
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770