I mean, which fields of struct sockaddr
should I compare when I check whether two struct sockaddr
's have the same ip address and port number? And what about sockaddr_in
?
Can I just cast sockaddr_in
to sockaddr
, and compare it to a real sockaddr
?
First you need to check the family (IPv4, IPv6, or other). Then you can cast each sockaddr to the appropriate "derived" type like sockaddr_in. See how Apple does it here: http://www.opensource.apple.com/source/postfix/postfix-197/postfix/src/util/sock_addr.c
First off, when you are dealing with values, you need to use struct sockaddr_storage
, since struct sockaddr
is only safe for pointers, or you will run into size and alignment problems.
Second, struct sockaddr
is what passes in C for a "base class". The first member is sa_family_t sa_family;
(although, since this struct
predates struct members being in separate namespaces, each "subclass" uses a unique prefix (I have come across at least 40 subclasses)).
Third - while you might think each struct
definition is reliable, it turns out that the size of the class varies between kernel/library versions. So, you always have to pass the sizeof()
the actual struct sockaddr_FOO
in order to avoid garbage. For example, old versions of struct sockaddr_in6
did not have the sin6_scope_id
member.
You should probably wrap this in a struct
for sanity (and provide various helper functions):
struct SocketAddress
{
struct sockaddr_storage addr;
socklen_t addr_len;
};
Then, your comparison code would look like:
// returns < 0 if (left < right)
// returns > 0 if (left > right)
// returns 0 if (left == right)
// Note that in general, "less" and "greater" are not particularly
// meaningful, but this does provide a strict weak ordering since
// you probably need one.
int socket_cmp(struct SocketAddress *left, struct SocketAddress *right)
{
socklen_t min_addr_len = left->addr_len < right->addr_len ? left->addr_len : right->addr_len;
// If head matches, longer is greater.
int default_rv = right->addr_len - left->addr_len;
int rv = memcmp(left, right, min_addr_len);
return rv ? rv : default_rv;
}
But wait! While the above code is enough if you're careful, being careful still entails a lot of details. For example:
Are all socket addresses going to generated within a single run of your program, or will some of them be read from some external medium? For the latter case, you will want to canonicalize cases like the sin6_scope_id
.
Do you have to deal with both IPv4 and IPv4-on-IPv6 addresses (mapped like ffff::1.2.3.4
) in the same program? The easiest way is to deal exclusively with IPv6 in your program, since the relevant functions also accept IPv4 addresses. For best portability, be sure to disable (using setsockopt
) the IPV6_V6ONLY
flag. Alternatively, you could enable that flag to help ensure that IPv6 addresses never contain an IPv4 address. (Note that the lookup of localhost
differs - but this is a general problem for all domain lookups, which may return more than one result).
You need to ensure that all struct padding is zeroed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With