0

Trying to manipulate IPv6 addresses in struct sin6_addr, e.g. cleaning it:

struct sockaddr a;
memset(&(((struct sockaddr_in6 *)&a)->sin6_addr.s6_addr), 0, 16);
printf("SIZEOF: %lu\n", sizeof((((struct sockaddr_in6 *)&a)->sin6_addr.s6_addr)));

And facing the warning:

1.c:34:2: warning: 'memset' will always overflow; destination buffer has size 8, but size argument is 16 [-Wfortify-source]
        memset(&(((struct sockaddr_in6 *)&a)->sin6_addr.s6_addr), 0, 16);

The same time, printf("SIZEOF...") returns 16 bytes, so should be enough space?

Also, if I do it like this:

struct sockaddr a;
struct in6_addr aa;
aa = ((struct sockaddr_in6 *)&a)->sin6_addr.s6_addr;
memset(&aa, 0, 16);

There are no warnings shown. What do I do wrong?

UPD. Fixed a in printf()

Mikhail Zakharov
  • 904
  • 11
  • 22
  • 2
    ["The sockaddr_in6 structure is bigger than the generic sockaddr"](https://man7.org/linux/man-pages/man7/ipv6.7.html#NOTES)? – teapot418 May 02 '23 at 08:42
  • Rule #1 for avoiding bugs in C code: never do a pointer cast until you have very in-depth knowledge of the language. Because such a cast is almost certain to be wrong in some way or the other - this one here being wrong in several different ways. For example it is only safe to cast from one struct to another if the target struct contains the destination struct as first member. And if two pointer types are compatible then you need not cast. If they aren't, then casting is unlikely a solution but the cause of a 2nd problem you didn't have before casting... and now we have two problems. – Lundin May 02 '23 at 10:23

3 Answers3

5

There is already complete answer from Attie, but this gives an alternative for storing socket addresses:

struct sockaddr_storage

Man page of socket(7) says the following:

the sockets API provides the data type struct sockaddr_storage.
This type is suitable to accommodate all
supported domain-specific socket address structures; it is large
enough and is aligned properly.

The sockaddr_storage structure is useful in programs that must
handle socket addresses in a generic way (e.g., programs that must deal with both IPv4 and IPv6 socket addresses).

So instead of this:

struct sockaddr a;

You should use this:

struct sockaddr_storage a;

for getting enough space for the most of different address types.

SKi
  • 8,007
  • 2
  • 26
  • 57
3

Unfortunately, these structures don't work like that... What you'll find is that the struct sockaddr is a "base" definition that really only includes the socket family field, and some minimum space reservation - and then by typecasting, you're able to access the fields of another structure. As you're using IPv6, this means that you'll have a much longer address, and will need to allocate storage accordingly.

As the various addressing schemes use different information (e.g: address length), you need to make sure you allocate enough storage for what you're trying to use.

In your situation, the following would work:

struct sockaddr_in6 a;
memset(&(a->sin6_addr.s6_addr), 0, sizeof(a->sin6_addr.s6_addr));
printf("SIZEOF: %lu\n", sizeof(a->sin6_addr.s6_addr));

/* don't be afraid to clear the whole structure before filling it in too! */
memset(&a, 0, sizeof(a));

When you subsequently use it to connect / bind, then you'd typecast it there. When passing the structure to the kernel, you can almost think of it as passing a type-less pointer... the kernel knows you're using IPv6 (because you said so when calling socket(), and in the address family field), and therefore which type to use. The typecast here just keeps the compiler happy - you're still sharing the full IPv6 info, and the size of that structure.

int fd, ret;

fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
ret = connect(fd, (struct sockaddr *)&a, sizeof(a));

Think of structures as a template you can lay over memory - it's just an indicator of how to read the data that is held in memory... by casting a region of memory to another type doesn't cause the underlying "thing" to change shape at all.


If you're allocating the address on the heap (not stack), then when you call *alloc(), you need to give the correct size for the addressing you're using.

struct sockaddr_in6 *a;

a = malloc(sizeof *a);
a = malloc(sizeof (struct sockaddr_in6)); /* this works too... */

a = malloc(sizeof (struct sockaddr)); /* this is BROKEN */
Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Attie
  • 6,690
  • 2
  • 24
  • 34
  • Thank yo very much. The "b" var was my typo, that's left from experiments, sorry. – Mikhail Zakharov May 02 '23 at 08:53
  • So, it is possible to cast `struct sockaddr_in6` to `struct sockaddr` but, won't work if I'd want to get from the base `struct sockadd` to `struct sockaddr_in6` and access IPv6 related fields, right? Why in this case, the example with `aa` works? – Mikhail Zakharov May 02 '23 at 08:55
  • 1
    I've added a bit more of a description to the `connect()` details... but yes - casting a `sockaddr_in6` to a `sockaddr` will effectively limit you to seeing only the socket family field - the rest will be unaccessible (even though they're still there). – Attie May 02 '23 at 08:59
  • Attie, thank you very much, that's what I was I worried about. Perhaps, I'll have to use `addrinfo` struct to have a "universal" sockaddr-like structure. – Mikhail Zakharov May 02 '23 at 09:03
  • 1
    "_I'll have to use addrinfo struct to have a "universal" sockaddr-like structure_"- if you need to pass addressing info around your application, then either: A) hold it as `sockaddr`, allocate it on the heap (e.g: using `malloc()`), and inspect the `sa_family` field before handling, or B) use a type that is large enough to encompass all addressing schemes you want to use (e.g: `union { struct sockaddr plain; struct sockaddr_in ipv4; struct sockaddr_in6 ipv6; }`) – Attie May 02 '23 at 09:09
  • 1
    To be honest, I tried to follow plan "A" in the app, and occasionally stumbled at the issue with a bad feeling of that I'll have to rewrite everything related to the address manipulation functions :) – Mikhail Zakharov May 02 '23 at 09:19
1

With respect to this part of your question:

Also, if I do it like this:

struct sockaddr a;
struct in6_addr aa;
aa = ((struct sockaddr_in6 *)&a)->sin6_addr.s6_addr;
memset(&aa, 0, 16);

There are no warnings shown. What do I do wrong?

There are several issues with this code.

First, this cast

(struct sockaddr_in6 *)&a

is just plain wrong. a is not a struct sockaddr_in6, it is a struct sockaddr. With that cast, you basically lied to the compiler. Treating that memory as something it's not is a strict aliasing violation and that invokes undefined behavior. (With the exception that it's always allowed to access any object as [[un]signed] char data.)

Then, by accessing the sin6_addr.s6_addr of something that is not a struct sockaddr_in6, you risk both accessing memory outside of the actual struct sockaddr a object and any possible alignment issues should struct sockaddr_in6 have a stricter alignment requirement than a struct sockaddr. Violating either of those will also invoke undefined behavior. And note that merely creating a misaligned pointer is sufficient to invoke undefined behavior - dereferencing such a pointer is not necessary to invoke undefined behavior.

And since you now know that a struct sockaddr_in6 is larger than a struct sockaddr, you know you've accessed memory outside of the struct sockaddr a object. That's undefined behavior.

You're also reading uninitialized data - and that invokes undefined behavior too.

That code invokes undefined behavior in up to four different ways:

  1. Strict aliasing violation
  2. Access outside the bounds of an object
  3. Alignment violation
  4. Read of unintialized data

And one of the results of undefined behavior of any sort is "appears to work as expected".

Depending on the compiler you're using, increasing the warning level and/or other options may generate some warnings and/or errors. GCC's -Wall -pedantic might help here.

And yes, POSIX plays a bit loose with strict aliasing when you have to cast something like a struct sockaddr_in6 to a struct sockaddr to actually use the object. But the POSIX standard does impose requirements that POSIX-conforming implementations must allow this (one of the characteristics of behavior that is undefined by the C standard is that implementations are permitted to actually define the behavior):

When a pointer to a sockaddr_storage structure is cast as a pointer to a sockaddr structure, the ss_family field of the sockaddr_storage structure shall map onto the sa_family field of the sockaddr structure. When a pointer to a sockaddr_storage structure is cast as a pointer to a protocol-specific address structure, the ss_family field shall map onto a field of that structure that is of type sa_family_t and that identifies the protocol's address family.

(In my opinion, that POSIX requirement is not really sufficient to cover all the sockaddr_* usages/conversions/casts that are expected to work...)

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56