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