src/sock_tcp.c
author Tero Marttila <terom@fixme.fi>
Mon, 04 May 2009 20:55:04 +0300
branchnew-transport
changeset 168 a58ad50911fc
parent 159 d3e253d7281a
permissions -rw-r--r--
refactor test.c into tests/*

#include "sock_tcp_internal.h"
#include "sock_internal.h"
#include "log.h"

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

/**
 * Start connecting to the given address in a non-blocking fashion. Returns any errors that immediately crop up,
 * otherwise eventually calls sock_tcp_connect_done().
 */
static err_t sock_tcp_connect_addr (struct sock_tcp *sock, struct addrinfo *addr, error_t *err);



/**
 * Our transport_methods
 */
static void _sock_tcp_destroy (transport_t *transport)
{
    struct sock_tcp *sock = transport_check(transport, &sock_tcp_type);
    
    // proxy
    sock_tcp_destroy(sock);
}

/*
 * Our transport_type
 */
struct transport_type sock_tcp_type = {
    .parent                 = &transport_fd_type,
    .methods                = {
        .read               = transport_fd_methods_read,
        .write              = transport_fd_methods_write,
        .events             = transport_fd_methods_events,
        .destroy            = _sock_tcp_destroy,
    },
};

/**
 * Create a new socket() using the given addr's family/socktype/protocol, and update our transport_fd state.
 */
static err_t sock_tcp_create_socket (struct sock_tcp *sock, struct addrinfo *addr, error_t *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, update transport_fd
    transport_fd_set(SOCK_TCP_FD(sock), fd);


    return SUCCESS;
}

/**
 * Read the socket's error code, if any.
 *
 * The read error number is stored in err->extra on SUCCESS, unless reading the error fails, in which case
 * err contains the normal error info.
 *
 * @return error code on failure
 */
static err_t sock_tcp_read_error (struct sock_tcp *sock, error_t *err)
{
    int optval;
    socklen_t optlen;

    RESET_ERROR(err);

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

    // read error code
    if (getsockopt(SOCK_TCP_FD(sock)->fd, SOL_SOCKET, SO_ERROR, &optval, &optlen))
        RETURN_SET_ERROR_ERRNO(err, ERR_GETSOCKOPT);
    
    // sanity-check optlen... not sure if this is sensible
    if (optlen != sizeof(optval))
        RETURN_SET_ERROR_EXTRA(err, ERR_GETSOCKOPT, EINVAL);
    
    // then store the system error code
    ERROR_EXTRA(err) = optval;

    // ok
    return SUCCESS;
}

/**
 * Attempt to connect to the given addrinfo, or the next one, if that fails, etc.
 *
 * This does not call transport_connected().
 */
static err_t sock_tcp_connect_continue (struct sock_tcp *sock, struct addrinfo *addr, struct error_info *err)
{
    if (!addr)
        // no more addresses left to try
        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_addr(sock, addr, err) == SUCCESS)
            break;

        // log a warning on the failed connect
        log_warn("sock_tcp_connect_addr(%s): %s", addr->ai_canonname, error_msg(err));

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

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

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

/**
 * Cleanup our resolver state and any connect callbacks after a connect
 */
static void sock_tcp_connect_cleanup (struct sock_tcp *sock)
{
    // free the addrinfo
    freeaddrinfo(sock->async_res); 
    sock->async_res = sock->async_cur = NULL;
    
    // remove our event handler
    transport_fd_clear(SOCK_TCP_FD(sock));
}

/**
 * Our async connect operation has completed, clean up, set up state for event-based operation with user callbacks, and
 * invoke transport_connected().
 *
 * The given \a err should be NULL for successful completion, or the error for failures.
 */
static void sock_tcp_connect_done (struct sock_tcp *sock, struct error_info *conn_err)
{
    error_t err;

    // cleanup
    sock_tcp_connect_cleanup(sock);

    if (conn_err)
        // passthrough errors
        JUMP_SET_ERROR_INFO(&err, conn_err);
    
    // set up for default transport event-based operation
    if ((ERROR_CODE(&err) = transport_fd_defaults(SOCK_TCP_FD(sock))))
        goto error;

    // ok, no error

error:                
    // pass on to transport
    transport_connected(SOCK_TCP_TRANSPORT(sock), IS_ERROR(&err) ? &err : NULL, false);
}

/**
 * Our async connect callback
 */
static void sock_tcp_on_connect (struct transport_fd *fd, short what, void *arg)
{
    struct sock_tcp *sock = arg;
    struct error_info err;
    err_t tmp;

    // XXX: timeouts
    (void) what;
    
    // read socket error code
    if (sock_tcp_read_error(sock, &err))
        goto error;

    // did the connect fail?
    if (ERROR_EXTRA(&err))
        JUMP_SET_ERROR(&err, ERR_CONNECT);
    
    // done, success
    return sock_tcp_connect_done(sock, NULL);

error:
    // close the socket
    if ((tmp = transport_fd_close(fd)))
        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_continue(sock, sock->async_cur->ai_next, &err))
        sock_tcp_connect_done(sock, &err);
}

static err_t sock_tcp_connect_addr (struct sock_tcp *sock, struct addrinfo *addr, error_t *err)
{
    int ret;
    err_t tmp;

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

    // then, set it up as nonblocking
    if ((ERROR_CODE(err) = transport_fd_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) {
        // set the "current" address in case it fails and we need to try the next one
        sock->async_cur = addr;

        // ok, connect started, setup our completion callback
        if ((ERROR_CODE(err) = transport_fd_setup(SOCK_TCP_FD(sock), sock_tcp_on_connect, sock)))
            goto error;
    
        // enable for write
        if ((ERROR_CODE(err) = transport_fd_enable(SOCK_TCP_FD(sock), TRANSPORT_WRITE)))
            goto error;

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

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

    return ERROR_CODE(err);
}

/**
 * External interface
 */
void sock_tcp_init (struct sock_tcp *sock)
{
    struct event_base *ev_base = _sock_stream_ctx.ev_base;

    // init without any fd
    transport_fd_init(SOCK_TCP_FD(sock), ev_base, TRANSPORT_FD_INVALID);

}

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

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

    // resolve (blocking)
    if ((ret = getaddrinfo(hostname, service, &hints, &sock->async_res)))
        RETURN_SET_ERROR_EXTRA(err, ERR_GETADDRINFO, ret);
    
    // start connecting on the first result
    if (sock_tcp_connect_continue(sock, sock->async_res, err))
        goto error;

    // ok
    return SUCCESS;

error:
    // cleanup
    if (sock->async_res)
        sock_tcp_connect_cleanup(sock);
    
    return ERROR_CODE(err);
}

void sock_tcp_destroy (struct sock_tcp *sock)
{
    // cleanup our stuff
    if (sock->async_res)
        sock_tcp_connect_cleanup(sock);
    
    // destroy lower level
    transport_fd_destroy(SOCK_TCP_FD(sock)); 
}

/**
 * Public interface
 */
err_t sock_tcp_connect (const struct transport_info *info, transport_t **transport_ptr, 
        const char *host, const char *service, error_t *err)
{
    struct sock_tcp *sock;
 
    // alloc
    if ((sock = calloc(1, sizeof(*sock))) == NULL)
        return ERR_CALLOC;
    
    // initialize transport
    transport_init(SOCK_TCP_TRANSPORT(sock), &sock_tcp_type, info);
    
    // init our state
    sock_tcp_init(sock);
 
    // connect    
    if (sock_tcp_connect_async(sock, host, service, err))
        goto error;

    // good
    *transport_ptr = SOCK_TCP_TRANSPORT(sock);

    return 0;

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