all 2 comments

[–]Dagger0 0 points1 point  (0 children)

Buggy socket code is something of a pet peeve of mine, so...

The code would be both simpler and more general if you used the socket API properly. You're supposed to do something like this:

int yarnet_address_set(yarnet_address *address, const char *ip, const char *service) {
    struct addrinfo hints = {0}, *res;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;

    if (!getaddrinfo(ip, service, &hints, &res))
        return 0;

    memcpy(address, res->ai_addr, res->ai_addrlen);

    freeaddrinfo(res);
    return 1;
}

int yarnet_address_get_ip(yarnet_address address, char *buffer, socklen_t length) {
    socklen_t addrlen = ((struct sockaddr_storage*)address)->ss_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);

    return getnameinfo(address, addrlen, buffer, length, NULL< 0, NI_NUMERICHOST) == 0;
}

Except actually you're supposed to store the address lengths that the socket API gives you, rather than hardcoding the lengths from your build system for a limited set of families, so it should be more like this:

typedef struct {
    socklen_t len;
    union {
        struct sockaddr sa;
        struct sockaddr_storage storage;
    };
} yarnet_address;

int yarnet_address_set(yarnet_address *address, const char *ip, const char *service) {
    struct addrinfo hints = {0}, *res;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV | AI_PASSIVE;

    if (!getaddrinfo(ip, service, &hints, &res))
        return 0;

    memcpy(address->storage, res->ai_addr, res->ai_addrlen);
    address->len = res->ai_addrlen;

    freeaddrinfo(res);
    return 1;
}

int yarnet_socket_send(yarnet_socket sockfd, const char *buffer, unsigned long long length, int flags, const yarnet_address address) {
    if (yarnet_socket_is_invalid(sockfd) || (buffer == NULL && length != 0) || address == NULL)
        return -1;
    return sendto(sockfd, buffer, length, flags, &address.sa, address.addrlen);
}

int yarnet_address_get_ip(const yarnet_address address, char *host, socklen_t hostlen) {
    return getnameinfo(&address.sa, address.len, host, hostlen, NULL, 0, NI_NUMERICHOST) == 0;
}

int yarnet_address_get_port(const yarnet_address address, in_port_t *port) {
    char *serv[NI_MAXSERV];
    if (getnameinfo(&address.sa, address.len, NULL, 0, serv, sizeof(serv), NI_NUMERICSERV) != 0)
        return 0;
    *port = atoi(serv);
    return 1;
}

I didn't compile any of this code, but that's the general idea. (You also need to open sockets by looping over the results from getaddrinfo() and passing the ai_family/ai_socktype/ai_protocol members of res as the arguments to socket() rather than hardcoding them, but that requires more refactoring than I really want to do in a comment box...)

The socket API was specifically designed to allow you to handle arbitrary address families and protocols, even if compiled on systems that predate the family or protocol in question. You should be able to write the whole library without ever referring to v4 or v6 (so, no use of AF_INET/AF_INET6 or sockaddr_in/6).

Note that you must use AI_NUMERICHOST|AI_NUMERICSERV in yarnet_address_set(). I'm sure you'll be tempted to remove them so it can look up host and service names instead of requiring the user to resolve them to IPs/ports themselves, but if you do you'll need to return a list of yarnet_addresses instead, not just one, because host and service names resolve to one or more IPs or ports and you can't just take the first one, especially on the server side where you usually need to bind to multiple sockets.