#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include "socket.h"
#include "common.h"
#define _SOCKOP_SOCKET 0x01
#define _SOCKOP_ENDPOINT_BIND 0x02
#define _SOCKOP_ENDPOINT_CONNECT 0x04
#define _SOCKOP_LISTEN 0x08
#define _SOCKOP_FLAGS_PASSIVE 0x10
#define _SOCKOP_FCNTL_NONBLOCK 0x20
enum socket_op {
/*
* Just resolve the address, don't create a socket
*/
SOCKOP_RESOLVE = _SOCKOP_FLAGS_PASSIVE,
/*
* Create a socket whose local end is bound to the given endpoint, and listening
*/
SOCKOP_LISTEN = _SOCKOP_FLAGS_PASSIVE | _SOCKOP_SOCKET | _SOCKOP_ENDPOINT_BIND | _SOCKOP_LISTEN,
/*
* Create a socket whose remote end is connected to the given endpoint, and connected
*/
SOCKOP_CONNECT = _SOCKOP_SOCKET | _SOCKOP_ENDPOINT_CONNECT,
/*
* Create a socket and initiate a nonblocking connect to the given endpoint
*/
SOCKOP_CONNECT_ASYNC = _SOCKOP_SOCKET | _SOCKOP_FCNTL_NONBLOCK | _SOCKOP_ENDPOINT_CONNECT,
};
static int _socket_do (struct config_endpoint *endpoint, int *sock, int sock_type, struct sockaddr_storage *addr, enum socket_op sockop);
/*
* Create a socket of the given type that's listening on the given endpoint
*/
int socket_listen (struct config_endpoint *endpoint, int sock_type) {
int sock = -1;
struct sockaddr_storage addr;
// and then _socket_do
if (_socket_do(endpoint, &sock, sock_type, &addr, SOCKOP_LISTEN))
return -1;
// just return the socket, discard addr
return sock;
}
/*
* Initiate an async connect to the given endpoint for the given socket type. This should work for
* PF_INET and PF_LOCAL sockets, and in both cases, socket writeability should indicate that the
* connect succeeded.
*
* XXX: Currently it looks up the endpoint each time - the working addrinfo should be cached
*/
int socket_connect_async (struct config_endpoint *endpoint, int sock_type) {
int sock = -1;
struct sockaddr_storage addr;
// and then _socket_do
if (_socket_do(endpoint, &sock, sock_type, &addr, SOCKOP_CONNECT_ASYNC))
return -1;
// return the socket
return sock;
}
/*
* Check if the given socket has an error condition set, mostly intended for use with socket_connect_async.
*
* Returns 0 and sets *error on success (zero = no error, nonzero = error), -1 on failure (invalid socket).
*/
int socket_check_error (int sock, int *error) {
socklen_t optlen = sizeof(*error);
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, error, &optlen))
PERROR("getsockopt");
return 0;
error:
return -1;
}
/*
* Do something to apply an endpoint to a socket
*/
static int _socket_do (struct config_endpoint *endpoint, int *sock, int sock_type, struct sockaddr_storage *addr, enum socket_op sockop) {
struct addrinfo *res = NULL, *info, _fake_res;
struct sockaddr_un _fake_addr_un;
if (endpoint->family == PF_UNIX) {
// getaddrinfo doesn't handle PF_UNIX, so we need to build a fake result
// build the sockaddr_un
_fake_addr_un.sun_family = endpoint->family;
memcpy(_fake_addr_un.sun_path, endpoint->af.local.path, sizeof(_fake_addr_un.sun_path));
// build the fake addrinfo res
_fake_res.ai_flags = 0;
_fake_res.ai_family = PF_UNIX;
_fake_res.ai_socktype = sock_type;
_fake_res.ai_protocol = 0;
_fake_res.ai_addrlen = SUN_LEN(&_fake_addr_un);
_fake_res.ai_addr = (struct sockaddr *) &_fake_addr_un;
_fake_res.ai_canonname = (char *) endpoint->af.local.path; /* XXX: not const */
_fake_res.ai_next = NULL;
res = &_fake_res;
} else if (endpoint->family == PF_INET) {
// use the real getaddrinfo
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = sock_type;
hints.ai_protocol = 0;
hints.ai_flags = (sockop & _SOCKOP_FLAGS_PASSIVE ? AI_PASSIVE : 0) | AI_CANONNAME;
int err;
// check that we have a service, doesn't make any sense without
if (!endpoint->af.inet.port)
ERROR("no service specified");
if ((err = getaddrinfo(endpoint->af.inet.addr, endpoint->af.inet.port, &hints, &res)) != 0)
ERROR("getaddrinfo: %s#%s: %s", endpoint->af.inet.addr, endpoint->af.inet.port, gai_strerror(err));
} else {
ERROR("invalid endpoint");
}
for (info = res; info; info = info->ai_next) {
if (sockop & _SOCKOP_SOCKET && (*sock = socket(info->ai_family, info->ai_socktype, info->ai_protocol)) == -1) {
PWARNING("socket(%d, %s)", info->ai_family, info->ai_canonname);
continue;
}
if (sockop & _SOCKOP_FCNTL_NONBLOCK && fcntl(*sock, F_SETFL, O_NONBLOCK) == -1) {
PWARNING("fcntl(%d, %s, F_SETFL O_NONBLOCK)", info->ai_family, info->ai_canonname);
close(*sock); *sock = -1;
continue;
}
if (sockop & _SOCKOP_ENDPOINT_BIND && bind(*sock, info->ai_addr, info->ai_addrlen) == -1) {
PWARNING("bind(%d, %s)", info->ai_family, info->ai_canonname);
close(*sock); *sock = -1;
continue;
}
if (sockop & _SOCKOP_LISTEN && listen(*sock, SOCKET_LISTEN_BACKLOG) == -1) {
PWARNING("listen(%d, %s)", info->ai_family, info->ai_canonname);
close(*sock); *sock = -1;
continue;
}
if (sockop & _SOCKOP_ENDPOINT_CONNECT && connect(*sock, info->ai_addr, info->ai_addrlen) == -1) {
if (sockop & _SOCKOP_FCNTL_NONBLOCK && errno == EINPROGRESS) {
/* to be expected */
} else {
PWARNING("connect(%d, %s)", info->ai_family, info->ai_canonname);
close(*sock); *sock = -1;
continue;
}
}
// copy the succesfull address over
memcpy(addr, info->ai_addr, info->ai_addrlen);
// exit the loop with a valid socket
assert(*sock != -1);
break;
}
if (*sock == -1)
ERROR("no working results from getaddrinfo: %s#%s", endpoint->af.inet.addr, endpoint->af.inet.port);
return 0;
error:
if (res != 0 && res != &_fake_res)
freeaddrinfo(res);
return -1;
}