How to compare socket address in C?

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.

Tags:

C

Sockets