socket.c
author Tero Marttila <terom@fixme.fi>
Wed, 27 Aug 2008 21:30:32 +0300
changeset 41 540737bf6bac
parent 39 0e21a65074a6
child 48 1c67f512779b
permissions -rw-r--r--
sending requests, and partial support for receiving -- incomplete, not tested
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>


#include "socket.h"
#include "common.h"

#define _SOCKOP_SOCKET 0x01
#define _SOCKOP_ENDPOINT_BIND 0x02
#define _SOCKOP_ENDPOINT_CONNECT 0x04
#define _SOCKOP_LISTEN 0x08
#define _SOCKOP_FLAGS_PASSIVE 0x10
#define _SOCKOP_FCNTL_NONBLOCK 0x20

enum socket_op {
    /*
     * Just resolve the address, don't create a socket
     */
    SOCKOP_RESOLVE = _SOCKOP_FLAGS_PASSIVE,

    /*
     * Create a socket whose local end is bound to the given endpoint, and listening
     */
    SOCKOP_LISTEN = _SOCKOP_FLAGS_PASSIVE | _SOCKOP_SOCKET | _SOCKOP_ENDPOINT_BIND | _SOCKOP_LISTEN,

    /*
     * Create a socket whose remote end is connected to the given endpoint, and connected
     */
    SOCKOP_CONNECT = _SOCKOP_SOCKET | _SOCKOP_ENDPOINT_CONNECT,

    /*
     * Create a socket and initiate a nonblocking connect to the given endpoint
     */
    SOCKOP_CONNECT_ASYNC = _SOCKOP_SOCKET | _SOCKOP_FCNTL_NONBLOCK | _SOCKOP_ENDPOINT_CONNECT,
};

static int _socket_do (struct config_endpoint *endpoint, int *sock, int sock_type, struct sockaddr_storage *addr, enum socket_op sockop);

/*
 * Create a socket of the given type that's listening on the given endpoint
 */
int socket_listen (struct config_endpoint *endpoint, int sock_type) {
    int sock = -1;
    struct sockaddr_storage addr;
    
    // and then _socket_do
    if (_socket_do(endpoint, &sock, sock_type, &addr, SOCKOP_LISTEN))
        return -1;
    
    // just return the socket, discard addr
    return sock;
}

/*
 * Initiate an async connect to the given endpoint for the given socket type. This should work for
 * PF_INET and PF_LOCAL sockets, and in both cases, socket writeability should indicate that the
 * connect succeeded.
 *
 * XXX: Currently it looks up the endpoint each time - the working addrinfo should be cached
 */
int socket_connect_async (struct config_endpoint *endpoint, int sock_type) {
    int sock = -1;
    struct sockaddr_storage addr;
    
    // and then _socket_do
    if (_socket_do(endpoint, &sock, sock_type, &addr, SOCKOP_CONNECT_ASYNC))
        return -1;
    
    // return the socket
    return sock;
}

/*
 * Check if the given socket has an error condition set, mostly intended for use with socket_connect_async.
 *
 * Returns 0 and sets *error on success (zero = no error, nonzero = error), -1 on failure (invalid socket).
 */
int socket_check_error (int sock, int *error) {
    socklen_t optlen = sizeof(*error);

    if (getsockopt(sock, SOL_SOCKET, SO_ERROR, error, &optlen))
        PERROR("getsockopt");

    return 0;

error:
    return -1;
}


/*
 * Do something to apply an endpoint to a socket
 */
static int _socket_do (struct config_endpoint *endpoint, int *sock, int sock_type, struct sockaddr_storage *addr, enum socket_op sockop) {
    struct addrinfo *res = NULL, *info, _fake_res;
    struct sockaddr_un _fake_addr_un;

    if (endpoint->family == PF_UNIX) {
        // getaddrinfo doesn't handle PF_UNIX, so we need to build a fake result
        
        // build the sockaddr_un
        _fake_addr_un.sun_family = endpoint->family;
        memcpy(_fake_addr_un.sun_path, endpoint->af.local.path, sizeof(_fake_addr_un.sun_path));
        
        // build the fake addrinfo res
        _fake_res.ai_flags = 0;
        _fake_res.ai_family = PF_UNIX;
        _fake_res.ai_socktype = sock_type;
        _fake_res.ai_protocol = 0;
        _fake_res.ai_addrlen = SUN_LEN(&_fake_addr_un);
        _fake_res.ai_addr = (struct sockaddr *) &_fake_addr_un;
        _fake_res.ai_canonname = (char *) endpoint->af.local.path;  /* XXX: not const */
        _fake_res.ai_next = NULL;

        res = &_fake_res;

    } else if (endpoint->family == PF_INET) {
        // use the real getaddrinfo

        struct addrinfo hints;
        memset(&hints, 0, sizeof(hints));
        
        hints.ai_socktype = sock_type;
        hints.ai_protocol = 0;
        hints.ai_flags = (sockop & _SOCKOP_FLAGS_PASSIVE ? AI_PASSIVE : 0) | AI_CANONNAME;

        int err;

        // check that we have a service, doesn't make any sense without
        if (!endpoint->af.inet.port)
            ERROR("no service specified");

        if ((err = getaddrinfo(endpoint->af.inet.addr, endpoint->af.inet.port, &hints, &res)) != 0)
            ERROR("getaddrinfo: %s#%s: %s", endpoint->af.inet.addr, endpoint->af.inet.port, gai_strerror(err));

    } else {
        ERROR("invalid endpoint");
    }

    for (info = res; info; info = info->ai_next) {
        if (sockop & _SOCKOP_SOCKET && (*sock = socket(info->ai_family, info->ai_socktype, info->ai_protocol)) == -1) {
            PWARNING("socket(%d, %s)", info->ai_family, info->ai_canonname);
            continue;
        }

        if (sockop & _SOCKOP_FCNTL_NONBLOCK && fcntl(*sock, F_SETFL, O_NONBLOCK) == -1) {
            PWARNING("fcntl(%d, %s, F_SETFL O_NONBLOCK)", info->ai_family, info->ai_canonname);
            close(*sock); *sock = -1;
            continue;
        }
        
        if (sockop & _SOCKOP_ENDPOINT_BIND && bind(*sock, info->ai_addr, info->ai_addrlen) == -1) {
            PWARNING("bind(%d, %s)", info->ai_family, info->ai_canonname);
            close(*sock); *sock = -1;
            continue;
        }

        if (sockop & _SOCKOP_LISTEN && listen(*sock, SOCKET_LISTEN_BACKLOG) == -1) {
            PWARNING("listen(%d, %s)", info->ai_family, info->ai_canonname);
            close(*sock); *sock = -1;
            continue;
        }

        if (sockop & _SOCKOP_ENDPOINT_CONNECT && connect(*sock, info->ai_addr, info->ai_addrlen) == -1) {
            if (sockop & _SOCKOP_FCNTL_NONBLOCK && errno == EINPROGRESS) {
                /* to be expected */

            } else {
                PWARNING("connect(%d, %s)", info->ai_family, info->ai_canonname);
                close(*sock); *sock = -1;
                continue;
            }
        }

        // copy the succesfull address over
        memcpy(addr, info->ai_addr, info->ai_addrlen);
        
        // exit the loop with a valid socket
        assert(*sock != -1);
        break;
    }

    if (*sock == -1)
        ERROR("no working results from getaddrinfo: %s#%s", endpoint->af.inet.addr, endpoint->af.inet.port);

    return 0;

error:
    if (res != 0 && res != &_fake_res)
        freeaddrinfo(res);

    return -1;
}