#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);
}