#include "tcp_internal.h"
#include "sock_internal.h"
#include "log.h"
#include <unistd.h>
/*
* Service methods
*/
void tcp_server__deinit (service_t *service)
{
struct tcp_server *serv = service_check(service, &tcp_server_type);
tcp_server_deinit(serv);
}
/*
* Service type
*/
const struct service_type tcp_server_type = {
.base_type = {
.parent = &service_type_type,
},
.methods = {
.deinit = tcp_server__deinit,
},
};
/*
* We got a new client, build a transport for it and give it to the user
*/
static err_t tcp_server_client (struct tcp_server *serv, evutil_socket_t sock, error_t *err)
{
struct tcp_transport *trans;
// create a new transport for it, this also makes it nonblocking
if (tcp_transport_create(&trans, &serv->base_service.info.trans_info, sock, err))
goto error;
// make it connected
// this will call transport_callbacks::on_connect, which is all the user needs
if (tcp_transport_connected(trans, err))
goto error;
// ok
return SUCCESS;
error:
// cleanup
if (trans)
tcp_transport_destroy(trans);
return ERROR_CODE(err);
}
/*
* Libevent callback
*/
static void tcp_server_on_accept (evutil_socket_t sock, short what, void *arg)
{
struct tcp_server *serv = arg;
evutil_socket_t client_sock;
error_t err;
(void) what;
// accept as a new client connection
if ((client_sock = accept(sock, NULL, NULL)) < 0 && errno != EAGAIN)
JUMP_SET_ERROR_ERRNO(&err, ERR_ACCEPT);
// spurious read event?
if (client_sock < 0)
return;
// handle it
if (tcp_server_client(serv, client_sock, &err))
goto error;
// ok
return;
error:
if (client_sock >= 0)
EVUTIL_CLOSESOCKET(client_sock);
// faaail
service_error(&serv->base_service, &err);
}
/*
* Attempts to construct a listen()'d socket with the given addr, and return it
*
* @param addr the addrinfo to try and create a socket for
* @param err returned error info
* @return listening socket, or -err_t on error
*/
static int tcp_server_sock_addr (struct addrinfo *addr, error_t *err)
{
evutil_socket_t sock;
// create the sock
if ((sock = tcp_sock_create(addr, err)) < 0)
goto error;
// bind it
if (bind(sock, addr->ai_addr, addr->ai_addrlen) < 0)
JUMP_SET_ERROR_ERRNO(err, ERR_BIND);
// listen
if (listen(sock, TCP_SERVER_BACKLOG) < 0)
JUMP_SET_ERROR_ERRNO(err, ERR_LISTEN);
// ok, valid socket
return sock;
error:
if (sock >= 0)
// cleanup
EVUTIL_CLOSESOCKET(sock);
return -ERROR_CODE(err);
}
/*
* Construct a listen()'d socket with the given resolver result, and return it.
*
* @param rr the resolver lookup result to create a socket for
* @param err returned error info
* @return listening socket, or -err_t on error
*/
static int tcp_server_sock (struct resolve_result *rr, error_t *err)
{
struct addrinfo *addr;
evutil_socket_t sock;
// try each addrinfo
while ((addr = resolve_result_next(rr))) {
// attempt to construct given socket
if ((sock = tcp_server_sock_addr(addr, err)) < 0)
// log an informative error warning
log_warn_error(err, "%s", resolve_addr_text(addr));
else
// got a valid socket
break;
}
if (sock >= 0)
// valid socket
return sock;
else
// some error occured
return -ERROR_CODE(err);
}
err_t tcp_server_listen (struct tcp_server *serv, const char *interface, const char *service, error_t *err)
{
struct resolve_result rr;
evutil_socket_t sock;
// get the global event_base
struct event_base *ev_base = _sock_stream_ctx.ev_base;
// init the resolver
resolve_result_init(&rr);
// resolve the interface/service
if (resolve_addr(&rr, interface, service, SOCK_STREAM, AI_PASSIVE, err))
return ERROR_CODE(err);
// create the socket
if ((sock = tcp_server_sock(&rr, err)) < 0)
goto error;
// deinit lookup results
resolve_result_deinit(&rr);
// make it nonblocking
if (evutil_make_socket_nonblocking(sock))
JUMP_SET_ERROR_STR(err, ERR_MISC, "evutil_make_socket_nonblocking");
// construct event for the sock
if ((serv->ev = event_new(ev_base, sock, EV_READ | EV_PERSIST, tcp_server_on_accept, serv)) == NULL)
JUMP_SET_ERROR(err, ERR_EVENT_NEW);
// add it
if (event_add(serv->ev, NULL))
JUMP_SET_ERROR(err, ERR_EVENT_ADD);
// ok
return SUCCESS;
error:
// deinit results just to be sure
resolve_result_deinit(&rr);
if (sock >= 0 && !serv->ev)
// need to close socket ourselves, because we couldn't register our event for it
EVUTIL_CLOSESOCKET(sock);
// general cleanup
tcp_server_deinit(serv);
return ERROR_CODE(err);
}
void tcp_server_deinit (struct tcp_server *serv)
{
if (serv->ev) {
// ignore errors
event_del(serv->ev);
// ignore errors
close(event_get_fd(serv->ev));
// release event
event_free(serv->ev);
// invalidate
serv->ev = NULL;
}
}
static void tcp_server_destroy (struct tcp_server *serv)
{
tcp_server_deinit(serv);
free(serv);
}
/*
* Public interface
*/
err_t tcp_listen (const struct service_info *info, service_t **service_ptr,
const char *interface, const char *service, error_t *err)
{
struct tcp_server *serv;
// alloc
if ((serv = calloc(1, sizeof(*serv))) == NULL)
return SET_ERROR(err, ERR_MEM);
// init service
service_init(&serv->base_service, &tcp_server_type, info);
// init ourselves
if (tcp_server_listen(serv, interface, service, err))
goto error;
// ok
*service_ptr = &serv->base_service;
return SUCCESS;
error:
tcp_server_destroy(serv);
return ERROR_CODE(err);
}