src/sock_tcp.c
author Tero Marttila <terom@fixme.fi>
Wed, 08 Apr 2009 01:28:46 +0300
changeset 120 576bab0a1c5a
parent 117 9cb405164250
child 139 55b9dcc2b73a
permissions -rw-r--r--
modify config to support options with multiple params/values

#include "sock_tcp.h"
#include "log.h"

#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>

static void sock_tcp_release (struct sock_stream *base_sock)
{
    struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp);
    
    // close and free
    sock_fd_close(SOCK_TCP_FD(sock));
    sock_tcp_free(sock);
}

/*
 * Our sock_stream_type
 */
static struct sock_stream_type sock_tcp_type = {
    .methods                = {
        .read               = &sock_fd_read,
        .write              = &sock_fd_write,
        .event_init         = &sock_fd_event_init,
        .event_enable       = &sock_fd_event_enable,
        .release            = &sock_tcp_release,
    },
};

static err_t sock_tcp_alloc (struct sock_tcp **sock_ptr)
{
    // alloc
    if ((*sock_ptr = calloc(1, sizeof(**sock_ptr))) == NULL)
        return ERR_CALLOC;
    
    // initialize base with sock_tcp_type
    sock_stream_init(SOCK_TCP_BASE(*sock_ptr), &sock_tcp_type);

    // init without any fd
    sock_fd_init(SOCK_TCP_FD(*sock_ptr), -1);

    // done
    return SUCCESS;
}

err_t sock_tcp_init_socket (struct sock_tcp *sock, struct addrinfo *addr, struct error_info *err)
{
    int fd;

    // call socket
    if ((fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) < 0)
        RETURN_SET_ERROR_ERRNO(err, ERR_SOCKET);

    // ok
    sock_fd_init(SOCK_TCP_FD(sock), fd);

    return SUCCESS;
}

/**
 * Attempt to connect to the given addrinfo, or the next one, if that fails, etc.
 */
static err_t sock_tcp_connect_async_continue (struct sock_tcp *sock, struct addrinfo *addr, struct error_info *err)
{
    // no more addresses left?
    if (!addr)
        // XXX: rename error
        return SET_ERROR(err, ERR_GETADDRINFO_EMPTY);

    // try and connect to each one until we find one that works
    do {
        // attempt to start connect
        if (sock_tcp_connect_async_addr(sock, addr, err) == SUCCESS)
            break;

        // try the next one
        log_warn("sock_tcp_connect_async_addr(%s): %s", addr->ai_canonname, error_msg(err));

    } while ((addr = addr->ai_next));
    

    if (addr) {
        // we succesfully did a sock_tcp_connect_async_addr on valid address
        return SUCCESS;

    } else {
        // all of the connect_async_addr's failed, return the last error
        return ERROR_CODE(err);
    }
}

/**
 * Our async connect operation has completed, clean up addrinfos and events, and call the user callback. The given
 * \a err should be NULL for successful completion, or the error for unsuccesfully completion.
 */
static void sock_tcp_connect_async_done (struct sock_tcp *sock, struct error_info *err)
{
    struct sock_stream *sock_base = SOCK_TCP_BASE(sock);

    // free the addrinfo
    freeaddrinfo(sock->async_res); 
    sock->async_res = sock->async_cur = NULL;

    // remove our event handler so the user can install their own
    sock_fd_deinit_ev(SOCK_TCP_FD(sock));

    // ok, run callback
    sock_base->conn_cb_func(sock_base, err, sock_base->conn_cb_arg);
}

/**
 * Our start_connect callback
 */
static void sock_tcp_connect_cb (int fd, short what, void *arg)
{
    struct sock_tcp *sock = arg;
    int optval;
    socklen_t optlen;
    struct error_info err;
    err_t tmp;

    // XXX: timeouts
    (void) what;

    // init params
    optval = 0;
    optlen = sizeof(optval);

    // read error code
    if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen))
        JUMP_SET_ERROR_ERRNO(&err, ERR_GETSOCKOPT);
    
    // sanity-check optlen... not sure if this is sensible
    if (optlen != sizeof(optval))
        JUMP_SET_ERROR_EXTRA(&err, ERR_GETSOCKOPT, EINVAL);
        
    // did the connect complete succesfully or not?
    if (optval)
        JUMP_SET_ERROR_EXTRA(&err, ERR_CONNECT, optval);
    
    // done
    return sock_tcp_connect_async_done(sock, NULL);

error:
    // close the socket
    if ((tmp = sock_fd_close(SOCK_TCP_FD(sock))))
        log_warn("error closing socket after connect error: %s", error_name(tmp));

    // log a warning
    log_warn("connect to '%s' failed: %s", sock->async_cur->ai_canonname, error_msg(&err));

    // try the next one or fail completely
    if (sock_tcp_connect_async_continue(sock, sock->async_cur->ai_next, &err))
        sock_tcp_connect_async_done(sock, &err);
}

err_t sock_tcp_connect_async_addr (struct sock_tcp *sock, struct addrinfo *addr, struct error_info *err)
{
    int ret;
    err_t tmp;

    // first, create the socket
    if (sock_tcp_init_socket(sock, addr, err))
        return ERROR_CODE(err);

    // then, set it up as nonblocking
    if ((ERROR_CODE(err) = sock_fd_set_nonblock(SOCK_TCP_FD(sock), true)))
        goto error;

    // then, initiate the connect
    if ((ret = connect(SOCK_TCP_FD(sock)->fd, addr->ai_addr, addr->ai_addrlen)) < 0 && errno != EINPROGRESS) 
        JUMP_SET_ERROR_ERRNO(err, ERR_CONNECT);
    
    if (ret < 0) {
        // ok, connect started, setup our completion callback
        if ((ERROR_CODE(err) = sock_fd_init_ev(SOCK_TCP_FD(sock), &sock_tcp_connect_cb, sock)))
            goto error;
    
        // enable for write
        if ((ERROR_CODE(err) = sock_fd_enable_events(SOCK_TCP_FD(sock), EV_WRITE)))
            goto error;

        // set the "current" address in case it fails and we need to try the next one
        sock->async_cur = addr;

    } else {
        // oops... blocking connect - fail to avoid confusion
        // XXX: come up with a better error name to use
        JUMP_SET_ERROR_EXTRA(err, ERR_CONNECT, EINPROGRESS);
    }
    
    // ok
    return SUCCESS;

error:
    // close the stuff we did open
    if ((tmp = sock_fd_close(SOCK_TCP_FD(sock))))
        log_warn("error closing socket after connect error: %s", error_name(tmp));

    return ERROR_CODE(err);
}

err_t sock_tcp_connect_async_begin (struct sock_tcp *sock, const char *hostname, const char *service, struct error_info *err)
{
    struct addrinfo hints;
    int ret;

    // hints
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    // resolve
    if ((ret = getaddrinfo(hostname, service, &hints, &sock->async_res)))
        RETURN_SET_ERROR_EXTRA(err, ERR_GETADDRINFO, ret);
    
    // start connecting
    if (sock_tcp_connect_async_continue(sock, sock->async_res, err))
        goto error;

    // ok
    return SUCCESS;

error:
    // cleanup
    if (sock->async_res) {
        freeaddrinfo(sock->async_res);
        sock->async_res = NULL;
    }

    return ERROR_CODE(err);
}

err_t sock_tcp_connect_blocking (struct sock_tcp *sock, const char *hostname, const char *service, struct error_info *err)
{
    struct addrinfo hints, *res, *r;
    int ret;
    
    // zero error code
    RESET_ERROR(err);
    
    // hints
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    // resolve
    if ((ret = getaddrinfo(hostname, service, &hints, &res)))
        RETURN_SET_ERROR_EXTRA(err, ERR_GETADDRINFO, ret);

    // try each result in turn
    for (r = res; r; r = r->ai_next) {
        // create the socket
        if ((SOCK_TCP_FD(sock)->fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol)) < 0) {
            // remember error
            SET_ERROR_ERRNO(err, ERR_SOCKET);

            // skip to next one
            continue;
        }
        
        // connect to remote address
        if (connect(SOCK_TCP_FD(sock)->fd, r->ai_addr, r->ai_addrlen)) {
            // remember error
            SET_ERROR_ERRNO(err, ERR_CONNECT);
            
            // close/invalidate socket
            sock_fd_close(SOCK_TCP_FD(sock));

            // skip to next one
            continue;
        }
        
        // valid socket, use this
        break;
    }
    
    // ensure we got some valid socket, else return last error code
    if (SOCK_TCP_FD(sock)->fd < 0) {
        // did we hit some error?
        if (IS_ERROR(err))
            // return last error
            return ERROR_CODE(err);
        
        else
            // no results
            return SET_ERROR(err, ERR_GETADDRINFO_EMPTY);
    }

    // ok, done
    return 0;    
}

void sock_tcp_free (struct sock_tcp *sock)
{
    // free
    free(sock);
}

err_t sock_tcp_connect (struct sock_stream **sock_ptr, const char *host, const char *service, struct error_info *err)
{
    struct sock_tcp *sock;
    
    // allocate
    if ((ERROR_CODE(err) = sock_tcp_alloc(&sock)))
        return ERROR_CODE(err);
    
    // connect    
    if (sock_tcp_connect_blocking(sock, host, service, err))
        goto error;

    // good
    *sock_ptr = SOCK_TCP_BASE(sock);

    return 0;

error:
    // cleanup
    sock_tcp_free(sock);
        
    // return error code
    return ERROR_CODE(err);
}

err_t sock_tcp_connect_async (struct sock_stream **sock_ptr, const char *host, const char *service, 
        sock_stream_connect_cb cb_func, void *cb_arg, struct error_info *err)
{
    struct sock_tcp *sock;
    
    // allocate
    if ((ERROR_CODE(err) = sock_tcp_alloc(&sock)))
        return ERROR_CODE(err);

    // store the callbacks
    SOCK_TCP_BASE(sock)->conn_cb_func = cb_func;
    SOCK_TCP_BASE(sock)->conn_cb_arg = cb_arg;
    
    // connect    
    if (sock_tcp_connect_async_begin(sock, host, service, err))
        goto error;

    // good
    *sock_ptr = SOCK_TCP_BASE(sock);

    return 0;

error:
    // cleanup
    sock_tcp_free(sock);
        
    // return error code
    return ERROR_CODE(err);
}