1227 lines
35 KiB
C
1227 lines
35 KiB
C
|
|
static int create_socket_pair(NATIVE_SOCKET *a, NATIVE_SOCKET *b)
|
|
{
|
|
#ifdef _WIN32
|
|
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (sock == INVALID_SOCKET)
|
|
return -1;
|
|
|
|
// Bind to loopback address with port 0 (dynamic port assignment)
|
|
struct sockaddr_in addr;
|
|
int addr_len = sizeof(addr);
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 127.0.0.1
|
|
addr.sin_port = 0; // Let system choose port
|
|
|
|
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
|
closesocket(sock);
|
|
return -1;
|
|
}
|
|
|
|
if (getsockname(sock, (struct sockaddr*)&addr, &addr_len) == SOCKET_ERROR) {
|
|
closesocket(sock);
|
|
return -1;
|
|
}
|
|
|
|
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
|
closesocket(sock);
|
|
return -1;
|
|
}
|
|
|
|
// Optional: Set socket to non-blocking mode
|
|
// This prevents send() from blocking if the receive buffer is full
|
|
u_long mode = 1;
|
|
if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR) {
|
|
closesocket(sock);
|
|
return -1;
|
|
}
|
|
|
|
*a = sock;
|
|
*b = sock;
|
|
return 0;
|
|
#else
|
|
int fds[2];
|
|
if (pipe(fds) < 0)
|
|
return -1;
|
|
*a = fds[0];
|
|
*b = fds[1];
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static int set_socket_blocking(NATIVE_SOCKET sock, bool value)
|
|
{
|
|
#ifdef _WIN32
|
|
u_long mode = !value;
|
|
if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR)
|
|
return -1;
|
|
#endif
|
|
|
|
#ifdef __linux__
|
|
int flags = fcntl(sock, F_GETFL, 0);
|
|
if (flags < 0)
|
|
return -1;
|
|
if (value) flags &= ~O_NONBLOCK;
|
|
else flags |= O_NONBLOCK;
|
|
if (fcntl(sock, F_SETFL, flags) < 0)
|
|
return -1;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static NATIVE_SOCKET create_listen_socket(HTTP_String addr,
|
|
Port port, bool reuse_addr, int backlog)
|
|
{
|
|
NATIVE_SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sock == NATIVE_SOCKET_INVALID)
|
|
return NATIVE_SOCKET_INVALID;
|
|
|
|
if (set_socket_blocking(sock, false) < 0) {
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
return NATIVE_SOCKET_INVALID;
|
|
}
|
|
|
|
if (reuse_addr) {
|
|
int one = 1;
|
|
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one, sizeof(one));
|
|
}
|
|
|
|
struct in_addr addr_buf;
|
|
if (addr.len == 0)
|
|
addr_buf.s_addr = htonl(INADDR_ANY);
|
|
else {
|
|
|
|
char copy[100];
|
|
if (addr.len >= (int) sizeof(copy)) {
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
return NATIVE_SOCKET_INVALID;
|
|
}
|
|
memcpy(copy, addr.ptr, addr.len);
|
|
copy[addr.len] = '\0';
|
|
|
|
if (inet_pton(AF_INET, copy, &addr_buf) < 0) {
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
return NATIVE_SOCKET_INVALID;
|
|
}
|
|
}
|
|
|
|
struct sockaddr_in bind_buf;
|
|
bind_buf.sin_family = AF_INET;
|
|
bind_buf.sin_addr = addr_buf;
|
|
bind_buf.sin_port = htons(port);
|
|
if (bind(sock, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) {
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
return NATIVE_SOCKET_INVALID;
|
|
}
|
|
|
|
if (listen(sock, backlog) < 0) {
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
return NATIVE_SOCKET_INVALID;
|
|
}
|
|
|
|
return sock;
|
|
}
|
|
|
|
static void close_socket_pair(NATIVE_SOCKET a, NATIVE_SOCKET b)
|
|
{
|
|
#ifdef _WIN32
|
|
closesocket(a);
|
|
(void) b;
|
|
#else
|
|
close(a);
|
|
close(b);
|
|
#endif
|
|
}
|
|
|
|
int socket_manager_init(SocketManager *sm, Socket *socks,
|
|
int num_socks)
|
|
{
|
|
if (mutex_init(&sm->mutex) < 0)
|
|
return -1;
|
|
sm->plain_sock = NATIVE_SOCKET_INVALID;
|
|
sm->secure_sock = NATIVE_SOCKET_INVALID;
|
|
if (create_socket_pair(&sm->wait_sock, &sm->signal_sock) < 0)
|
|
return -1;
|
|
sm->at_least_one_secure_connect = false;
|
|
|
|
sm->num_used = 0;
|
|
sm->max_used = num_socks;
|
|
sm->sockets = socks;
|
|
|
|
for (int i = 0; i < num_socks; i++) {
|
|
socks[i].state = SOCKET_STATE_FREE;
|
|
socks[i].gen = 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void socket_manager_free(SocketManager *sm)
|
|
{
|
|
close_socket_pair(sm->wait_sock, sm->signal_sock);
|
|
|
|
if (sm->secure_sock != NATIVE_SOCKET_INVALID)
|
|
server_secure_context_free(&sm->server_secure_context);
|
|
|
|
if (sm->at_least_one_secure_connect)
|
|
client_secure_context_free(&sm->client_secure_context);
|
|
|
|
if (sm->plain_sock != NATIVE_SOCKET_INVALID)
|
|
CLOSE_NATIVE_SOCKET(sm->plain_sock);
|
|
|
|
if (sm->secure_sock != NATIVE_SOCKET_INVALID)
|
|
CLOSE_NATIVE_SOCKET(sm->secure_sock);
|
|
|
|
mutex_free(&sm->mutex);
|
|
}
|
|
|
|
int socket_manager_listen_tcp(SocketManager *sm,
|
|
HTTP_String addr, Port port, int backlog,
|
|
bool reuse_addr)
|
|
{
|
|
if (sm->plain_sock != NATIVE_SOCKET_INVALID)
|
|
return -1;
|
|
|
|
sm->plain_sock = create_listen_socket(addr, port, reuse_addr, backlog);
|
|
if (sm->plain_sock == NATIVE_SOCKET_INVALID)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int socket_manager_listen_tls(SocketManager *sm,
|
|
HTTP_String addr, Port port, int backlog,
|
|
bool reuse_addr, HTTP_String cert_file,
|
|
HTTP_String key_file)
|
|
{
|
|
if (sm->secure_sock != NATIVE_SOCKET_INVALID)
|
|
return -1;
|
|
|
|
sm->secure_sock = create_listen_socket(addr, port, reuse_addr, backlog);
|
|
if (sm->secure_sock == NATIVE_SOCKET_INVALID)
|
|
return -1;
|
|
|
|
if (server_secure_context_init(&sm->server_secure_context,
|
|
cert_file, key_file) < 0) {
|
|
CLOSE_NATIVE_SOCKET(sm->secure_sock);
|
|
sm->secure_sock = NATIVE_SOCKET_INVALID;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int socket_manager_add_certificate(SocketManager *sm,
|
|
HTTP_String domain, HTTP_String cert_file, HTTP_String key_file)
|
|
{
|
|
if (sm->secure_sock == NATIVE_SOCKET_INVALID)
|
|
return -1;
|
|
|
|
int ret = server_secure_context_add_certificate(
|
|
&sm->server_secure_context, domain, cert_file, key_file);
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool is_secure(Socket *s)
|
|
{
|
|
#ifdef HTTPS_ENABLED
|
|
return s->server_secure_context != NULL
|
|
|| s->client_secure_context != NULL;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static bool connect_pending(void)
|
|
{
|
|
#ifdef _WIN32
|
|
return WSAGetLastError() == WSAEWOULDBLOCK;
|
|
#else
|
|
return errno == EINPROGRESS;
|
|
#endif
|
|
}
|
|
|
|
static bool
|
|
connect_failed_because_of_peer_2(int err)
|
|
{
|
|
#ifdef _WIN32
|
|
return err == WSAECONNREFUSED
|
|
|| err == WSAETIMEDOUT
|
|
|| err == WSAENETUNREACH
|
|
|| err == WSAEHOSTUNREACH;
|
|
#else
|
|
return err == ECONNREFUSED
|
|
|| err == ETIMEDOUT
|
|
|| err == ENETUNREACH
|
|
|| err == EHOSTUNREACH;
|
|
#endif
|
|
}
|
|
|
|
static bool
|
|
connect_failed_because_of_peer(void)
|
|
{
|
|
#ifdef _WIN32
|
|
int err = WSAGetLastError();
|
|
#else
|
|
int err = errno;
|
|
#endif
|
|
return connect_failed_because_of_peer_2(err);
|
|
}
|
|
|
|
static void free_addr_list(AddressAndPort *addrs, int num_addr)
|
|
{
|
|
#ifdef HTTPS_ENABLED
|
|
for (int i = 0; i < num_addr; i++) {
|
|
RegisteredName *name = addrs[i].name;
|
|
if (name) {
|
|
assert(name->refs > 0);
|
|
name->refs--;
|
|
if (name->refs == 0)
|
|
free(name);
|
|
}
|
|
}
|
|
#else
|
|
(void) addrs;
|
|
(void) num_addr;
|
|
#endif
|
|
}
|
|
|
|
// This function moves the socket state machine
|
|
// to the next state until an I/O event would
|
|
// be required to continue.
|
|
static void socket_update(Socket *s)
|
|
{
|
|
// Each case of this switch encodes a state transition.
|
|
// If the evaluated case requires a given I/O event to
|
|
// continue, the loop will exit so that the caller can
|
|
// wait for that event. If the case can continue to a
|
|
// different case, the again flag is set, which causes
|
|
// a different case to be evaluated.
|
|
bool again;
|
|
do {
|
|
again = false;
|
|
switch (s->state) {
|
|
case SOCKET_STATE_PENDING:
|
|
{
|
|
// This point may be reached because
|
|
// 1. The socket was just created by a connect
|
|
// operation.
|
|
// 2. Connecting to a host failed and now we
|
|
// need to try the next one.
|
|
// If (2) is true, we have some resources
|
|
// to clean up.
|
|
|
|
if (s->sock != NATIVE_SOCKET_INVALID) {
|
|
// This is not the first attempt
|
|
|
|
#ifdef HTTPS_ENABLED
|
|
if (s->ssl) {
|
|
SSL_free(s->ssl);
|
|
s->ssl = NULL;
|
|
}
|
|
#endif
|
|
|
|
CLOSE_NATIVE_SOCKET(s->sock);
|
|
|
|
s->next_addr++;
|
|
if (s->next_addr == s->num_addr) {
|
|
// All addresses have been tried and failed
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
AddressAndPort addr;
|
|
if (s->num_addr == 1)
|
|
addr = s->addr;
|
|
else
|
|
addr = s->addrs[s->next_addr];
|
|
|
|
int family = (addr.is_ipv4 ? AF_INET : AF_INET6);
|
|
NATIVE_SOCKET sock = socket(family, SOCK_STREAM, 0);
|
|
if (sock == NATIVE_SOCKET_INVALID) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
continue;
|
|
}
|
|
|
|
if (set_socket_blocking(sock, false) < 0) {
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
continue;
|
|
}
|
|
|
|
int ret;
|
|
if (addr.is_ipv4) {
|
|
struct sockaddr_in buf;
|
|
buf.sin_family = AF_INET;
|
|
buf.sin_port = htons(addr.port);
|
|
memcpy(&buf.sin_addr, &addr.ipv4, sizeof(HTTP_IPv4));
|
|
ret = connect(sock, (struct sockaddr*) &buf, sizeof(buf));
|
|
} else {
|
|
struct sockaddr_in6 buf;
|
|
buf.sin6_family = AF_INET6;
|
|
buf.sin6_port = htons(addr.port);
|
|
memcpy(&buf.sin6_addr, &addr.ipv6, sizeof(HTTP_IPv6));
|
|
ret = connect(sock, (struct sockaddr*) &buf, sizeof(buf));
|
|
}
|
|
|
|
if (ret == 0) {
|
|
// Connect resolved immediately
|
|
s->sock = sock;
|
|
s->state = SOCKET_STATE_CONNECTED;
|
|
s->events = 0;
|
|
again = true;
|
|
} else if (connect_pending()) {
|
|
// Connect is pending, which is expected
|
|
s->sock = sock;
|
|
s->state = SOCKET_STATE_CONNECTING;
|
|
s->events = POLLOUT;
|
|
} else if (connect_failed_because_of_peer()) {
|
|
// Conenct failed due to the peer host
|
|
// We should try a different address.
|
|
s->sock = sock;
|
|
s->state = SOCKET_STATE_PENDING;
|
|
s->events = 0;
|
|
again = true;
|
|
} else {
|
|
// An error occurred that we can't recover from
|
|
s->sock = sock;
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
again = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SOCKET_STATE_CONNECTING:
|
|
{
|
|
// This point is reached when a connect()
|
|
// operation completes.
|
|
|
|
int err = 0;
|
|
socklen_t len = sizeof(err);
|
|
if (getsockopt(s->sock, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0) {
|
|
// Failed to get socket error status
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
continue;
|
|
}
|
|
|
|
if (err == 0) {
|
|
// Connection succeded
|
|
s->state = SOCKET_STATE_CONNECTED;
|
|
s->events = 0;
|
|
again = true;
|
|
} else if (connect_failed_because_of_peer_2(err)) {
|
|
// Try the next address
|
|
s->state = SOCKET_STATE_PENDING;
|
|
s->events = 0;
|
|
again = true;
|
|
} else {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SOCKET_STATE_CONNECTED:
|
|
{
|
|
if (!is_secure(s)) {
|
|
|
|
// We managed to connect to the peer.
|
|
// We can free the target array if it
|
|
// was allocated dynamically.
|
|
if (s->num_addr > 1)
|
|
free(s->addrs);
|
|
|
|
s->events = 0;
|
|
s->state = SOCKET_STATE_ESTABLISHED_READY;
|
|
} else {
|
|
#ifdef HTTPS_ENABLED
|
|
if (s->ssl == NULL) {
|
|
s->ssl = SSL_new(s->client_secure_context->p);
|
|
if (s->ssl == NULL) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
break;
|
|
}
|
|
|
|
if (SSL_set_fd(s->ssl, s->sock) != 1) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
break;
|
|
}
|
|
|
|
AddressAndPort addr;
|
|
if (s->num_addr > 1)
|
|
addr = s->addrs[s->next_addr];
|
|
else
|
|
addr = s->addr;
|
|
|
|
if (addr.name)
|
|
SSL_set_tlsext_host_name(s->ssl, addr.name->data);
|
|
}
|
|
|
|
int ret = SSL_connect(s->ssl);
|
|
if (ret == 1) {
|
|
// Handshake done
|
|
|
|
// We managed to connect to the peer.
|
|
// We can free the target array if it
|
|
// was allocated dynamically.
|
|
if (s->num_addr == 1)
|
|
free_addr_list(&s->addr, 1);
|
|
else {
|
|
assert(s->num_addr > 1);
|
|
free_addr_list(s->addrs, s->num_addr);
|
|
free(s->addrs);
|
|
}
|
|
|
|
s->state = SOCKET_STATE_ESTABLISHED_READY;
|
|
s->events = 0;
|
|
break;
|
|
}
|
|
|
|
int err = SSL_get_error(s->ssl, ret);
|
|
if (err == SSL_ERROR_WANT_READ) {
|
|
s->events = POLLIN;
|
|
break;
|
|
}
|
|
|
|
if (err == SSL_ERROR_WANT_WRITE) {
|
|
s->events = POLLOUT;
|
|
break;
|
|
}
|
|
|
|
s->state = SOCKET_STATE_PENDING;
|
|
s->events = 0;
|
|
again = true;
|
|
#endif
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SOCKET_STATE_ACCEPTED:
|
|
{
|
|
if (!is_secure(s)) {
|
|
s->state = SOCKET_STATE_ESTABLISHED_READY;
|
|
s->events = 0;
|
|
} else {
|
|
#ifdef HTTPS_ENABLED
|
|
// Start server-side SSL handshake
|
|
if (!s->ssl) {
|
|
|
|
s->ssl = SSL_new(s->server_secure_context->p);
|
|
if (s->ssl == NULL) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
break;
|
|
}
|
|
|
|
if (SSL_set_fd(s->ssl, s->sock) != 1) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int ret = SSL_accept(s->ssl);
|
|
if (ret == 1) {
|
|
// Handshake done
|
|
s->state = SOCKET_STATE_ESTABLISHED_READY;
|
|
s->events = 0;
|
|
break;
|
|
}
|
|
|
|
int err = SSL_get_error(s->ssl, ret);
|
|
if (err == SSL_ERROR_WANT_READ) {
|
|
s->events = POLLIN;
|
|
break;
|
|
}
|
|
|
|
if (err == SSL_ERROR_WANT_WRITE) {
|
|
s->events = POLLOUT;
|
|
break;
|
|
}
|
|
|
|
// Server socket error - close the connection
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
#endif
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SOCKET_STATE_ESTABLISHED_WAIT:
|
|
s->state = SOCKET_STATE_ESTABLISHED_READY;
|
|
s->events = 0;
|
|
break;
|
|
|
|
case SOCKET_STATE_SHUTDOWN:
|
|
{
|
|
if (!is_secure(s)) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
} else {
|
|
#ifdef HTTPS_ENABLED
|
|
int ret = SSL_shutdown(s->ssl);
|
|
if (ret == 1) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
break;
|
|
}
|
|
|
|
int err = SSL_get_error(s->ssl, ret);
|
|
if (err == SSL_ERROR_WANT_READ) {
|
|
s->events = POLLIN;
|
|
break;
|
|
}
|
|
|
|
if (err == SSL_ERROR_WANT_WRITE) {
|
|
s->events = POLLOUT;
|
|
break;
|
|
}
|
|
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
#endif
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Do nothing
|
|
break;
|
|
}
|
|
} while (again);
|
|
}
|
|
|
|
int socket_manager_wakeup(SocketManager *sm)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
// Send a byte through the signal socket to wake up any thread
|
|
// blocked on poll() with the wait socket
|
|
char byte = 1;
|
|
int ret = 0;
|
|
#ifdef _WIN32
|
|
if (send(sm->signal_sock, &byte, 1, 0) < 0)
|
|
ret = -1;
|
|
#else
|
|
if (write(sm->signal_sock, &byte, 1) < 0)
|
|
ret = -1;
|
|
#endif
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return ret;
|
|
}
|
|
|
|
static void socket_manager_register_events_nolock(
|
|
SocketManager *sm, EventRegister *reg)
|
|
{
|
|
reg->num_polled = 0;
|
|
|
|
reg->polled[reg->num_polled].fd = sm->wait_sock;
|
|
reg->polled[reg->num_polled].events = POLLIN;
|
|
reg->polled[reg->num_polled].revents = 0;
|
|
reg->ptrs[reg->num_polled] = NULL;
|
|
reg->num_polled++;
|
|
|
|
// If the manager isn't at full capacity, monitor
|
|
// the listener sockets for incoming connections.
|
|
if (sm->num_used < sm->max_used) {
|
|
|
|
if (sm->plain_sock != NATIVE_SOCKET_INVALID) {
|
|
reg->polled[reg->num_polled].fd = sm->plain_sock;
|
|
reg->polled[reg->num_polled].events = POLLIN;
|
|
reg->polled[reg->num_polled].revents = 0;
|
|
reg->ptrs[reg->num_polled] = NULL;
|
|
reg->num_polled++;
|
|
}
|
|
|
|
if (sm->secure_sock != NATIVE_SOCKET_INVALID) {
|
|
reg->polled[reg->num_polled].fd = sm->secure_sock;
|
|
reg->polled[reg->num_polled].events = POLLIN;
|
|
reg->polled[reg->num_polled].revents = 0;
|
|
reg->ptrs[reg->num_polled] = NULL;
|
|
reg->num_polled++;
|
|
}
|
|
}
|
|
|
|
// Iterate over each socket and register those that
|
|
// are waiting for I/O. If at least one socket that
|
|
// is ready to be processed exists, return an empty
|
|
// event registration list so that those entries can
|
|
// be processed immediately.
|
|
for (int i = 0, j = 0; j < sm->num_used; i++) {
|
|
Socket *s = &sm->sockets[i];
|
|
if (s->state == SOCKET_STATE_FREE)
|
|
continue;
|
|
j++;
|
|
|
|
// If at least one socket can be processed, return an
|
|
// empty list.
|
|
if (s->state == SOCKET_STATE_DIED || s->state == SOCKET_STATE_ESTABLISHED_READY) {
|
|
reg->num_polled = 0;
|
|
return;
|
|
}
|
|
|
|
if (s->events) {
|
|
reg->polled[reg->num_polled].fd = s->sock;
|
|
reg->polled[reg->num_polled].events = s->events;
|
|
reg->polled[reg->num_polled].revents = 0;
|
|
reg->ptrs[reg->num_polled] = s;
|
|
reg->num_polled++;
|
|
}
|
|
}
|
|
}
|
|
|
|
int socket_manager_register_events(SocketManager *sm,
|
|
EventRegister *reg)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
socket_manager_register_events_nolock(sm, reg);
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static SocketHandle
|
|
socket_to_handle(SocketManager *sm, Socket *s)
|
|
{
|
|
return ((uint32_t) (s - sm->sockets) << 16) | s->gen;
|
|
}
|
|
|
|
static Socket *handle_to_socket(SocketManager *sm, SocketHandle handle)
|
|
{
|
|
uint16_t gen = handle & 0xFFFF;
|
|
uint16_t idx = handle >> 16;
|
|
if (idx >= sm->max_used)
|
|
return NULL;
|
|
if (sm->sockets[idx].gen != gen)
|
|
return NULL;
|
|
return &sm->sockets[idx];
|
|
}
|
|
|
|
static int socket_manager_translate_events_nolock(
|
|
SocketManager *sm, SocketEvent *events,
|
|
EventRegister *reg)
|
|
{
|
|
int num_events = 0;
|
|
for (int i = 0; i < reg->num_polled; i++) {
|
|
|
|
if (!reg->polled[i].revents)
|
|
continue;
|
|
|
|
if (reg->polled[i].fd == sm->plain_sock ||
|
|
reg->polled[i].fd == sm->secure_sock) {
|
|
|
|
// We only listen for input events from the listener
|
|
// if the socket pool isn't fool. This ensures that
|
|
// at least one socket struct is available. Note that
|
|
// it's still possible that we were at capacity MAX-1
|
|
// and then got events from both the TCP and TCP/TLS
|
|
// listeners, causing one to be left witout a struct.
|
|
// This means we still need to check for full capacity.
|
|
// Fortunately, poll() is level-triggered, which means
|
|
// we'll handle this at the next iteration.
|
|
if (sm->num_used == sm->max_used)
|
|
continue;
|
|
|
|
// Determine whether the event came from
|
|
// the encrypted listener or not.
|
|
bool secure = (reg->polled[i].fd == sm->secure_sock);
|
|
|
|
Socket *s = sm->sockets;
|
|
while (s->state != SOCKET_STATE_FREE) {
|
|
s++;
|
|
assert(s - sm->sockets < + sm->max_used);
|
|
}
|
|
|
|
NATIVE_SOCKET sock = accept(reg->polled[i].fd, NULL, NULL);
|
|
if (sock == NATIVE_SOCKET_INVALID)
|
|
continue;
|
|
|
|
if (set_socket_blocking(sock, false) < 0) {
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
continue;
|
|
}
|
|
|
|
s->state = SOCKET_STATE_ACCEPTED;
|
|
s->sock = sock;
|
|
s->events = 0;
|
|
s->user = NULL;
|
|
#ifdef HTTPS_ENABLED
|
|
s->ssl = NULL;
|
|
s->server_secure_context = NULL;
|
|
s->client_secure_context = NULL;
|
|
if (secure)
|
|
&s->server_secure_context = sm->server_secure_context;
|
|
#endif
|
|
|
|
socket_update(s);
|
|
if (s->state == SOCKET_STATE_DIED) {
|
|
CLOSE_NATIVE_SOCKET(sock);
|
|
s->state = SOCKET_STATE_FREE;
|
|
s->gen++;
|
|
if (s->gen == 0)
|
|
s->gen = 1;
|
|
continue;
|
|
}
|
|
|
|
sm->num_used++;
|
|
|
|
} else if (reg->polled[i].fd == sm->wait_sock) {
|
|
|
|
// Consume one byte from the wakeup signal
|
|
char byte;
|
|
#ifdef _WIN32
|
|
recv(sm->wait_sock, &byte, 1, 0);
|
|
#else
|
|
read(sm->wait_sock, &byte, 1);
|
|
#endif
|
|
|
|
} else {
|
|
|
|
Socket *s = reg->ptrs[i];
|
|
if (reg->polled[i].revents)
|
|
socket_update(s);
|
|
}
|
|
}
|
|
|
|
for (int i = 0, j = 0; j < sm->num_used; i++) {
|
|
Socket *s = &sm->sockets[i];
|
|
if (s->state == SOCKET_STATE_FREE)
|
|
continue;
|
|
j++;
|
|
|
|
if (s->state == SOCKET_STATE_DIED) {
|
|
|
|
events[num_events++] = (SocketEvent) {
|
|
SOCKET_EVENT_DISCONNECT,
|
|
SOCKET_HANDLE_INVALID,
|
|
s->user
|
|
};
|
|
|
|
// Free resources associated to socket
|
|
s->state = SOCKET_STATE_FREE;
|
|
if (s->sock != NATIVE_SOCKET_INVALID)
|
|
CLOSE_NATIVE_SOCKET(s->sock);
|
|
if (s->sock == SOCKET_STATE_PENDING ||
|
|
s->sock == SOCKET_STATE_CONNECTING) {
|
|
if (s->num_addr > 1)
|
|
free(s->addrs);
|
|
}
|
|
sm->num_used--;
|
|
|
|
} else if (s->state == SOCKET_STATE_ESTABLISHED_READY) {
|
|
events[num_events++] = (SocketEvent) {
|
|
SOCKET_EVENT_READY,
|
|
socket_to_handle(sm, s),
|
|
s->user
|
|
};
|
|
}
|
|
}
|
|
|
|
return num_events;
|
|
}
|
|
|
|
int socket_manager_translate_events(SocketManager *sm,
|
|
SocketEvent *events, EventRegister *reg)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
int ret = socket_manager_translate_events_nolock(sm, events, reg);
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return ret;
|
|
}
|
|
|
|
static int resolve_connect_targets(ConnectTarget *targets,
|
|
int num_targets, AddressAndPort *resolved, int max_resolved)
|
|
{
|
|
int num_resolved = 0;
|
|
for (int i = 0; i < num_targets; i++) {
|
|
switch (targets[i].type) {
|
|
case CONNECT_TARGET_NAME:
|
|
{
|
|
char portstr[16];
|
|
int len = snprintf(portstr, sizeof(portstr), "%u", targets[i].port);
|
|
assert(len > 1 && len < (int) sizeof(portstr));
|
|
|
|
struct addrinfo hints = {0};
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
#ifdef HTTPS_ENABLED
|
|
RegisteredName *name = malloc(sizeof(RegisteredName) + targets[i].name.len + 1);
|
|
if (name == NULL) {
|
|
free_addr_list(resolved, num_resolved);
|
|
return -1;
|
|
}
|
|
name->refs = 0;
|
|
memcpy(name->data, targets[i].name.ptr, targets[i].name.len);
|
|
name->data[targets[i].name.len] = '\0';
|
|
char *hostname = name->data;
|
|
#else
|
|
// 512 bytes is more than enough for a DNS hostname (max 253 chars)
|
|
char hostname[1<<9];
|
|
if (targets[i].name.len >= (int) sizeof(hostname))
|
|
return -1;
|
|
memcpy(hostname, targets[i].name.ptr, targets[i].name.len);
|
|
hostname[targets[i].name.len] = '\0';
|
|
#endif
|
|
struct addrinfo *res = NULL;
|
|
int ret = getaddrinfo(hostname, portstr, &hints, &res);
|
|
if (ret != 0) {
|
|
#ifdef HTTPS_ENABLED
|
|
// Free the name allocated for this target
|
|
free(name);
|
|
#endif
|
|
free_addr_list(resolved, num_resolved);
|
|
return -1;
|
|
}
|
|
|
|
for (struct addrinfo *rp = res; rp; rp = rp->ai_next) {
|
|
if (rp->ai_family == AF_INET) {
|
|
HTTP_IPv4 ipv4 = *(HTTP_IPv4*) &((struct sockaddr_in*)rp->ai_addr)->sin_addr;
|
|
if (num_resolved < max_resolved) {
|
|
resolved[num_resolved].is_ipv4 = true;
|
|
resolved[num_resolved].ipv4 = ipv4;
|
|
resolved[num_resolved].port = targets[i].port;
|
|
#ifdef HTTPS_ENABLED
|
|
resolved[num_resolved].name = name;
|
|
name->refs++;
|
|
#endif
|
|
num_resolved++;
|
|
}
|
|
} else if (rp->ai_family == AF_INET6) {
|
|
HTTP_IPv6 ipv6 = *(HTTP_IPv6*) &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr;
|
|
if (num_resolved < max_resolved) {
|
|
resolved[num_resolved].is_ipv4 = false;
|
|
resolved[num_resolved].ipv6 = ipv6;
|
|
resolved[num_resolved].port = targets[i].port;
|
|
#ifdef HTTPS_ENABLED
|
|
resolved[num_resolved].name = name;
|
|
name->refs++;
|
|
#endif
|
|
num_resolved++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef HTTPS_ENABLED
|
|
if (name->refs == 0)
|
|
free(name);
|
|
#endif
|
|
|
|
freeaddrinfo(res);
|
|
}
|
|
break;
|
|
case CONNECT_TARGET_IPV4:
|
|
if (num_resolved < max_resolved) {
|
|
resolved[num_resolved].is_ipv4 = true;
|
|
resolved[num_resolved].ipv4 = targets[i].ipv4;
|
|
resolved[num_resolved].port = targets[i].port;
|
|
#ifdef HTTPS_ENABLED
|
|
resolved[num_resolved].name = NULL;
|
|
#endif
|
|
num_resolved++;
|
|
}
|
|
break;
|
|
case CONNECT_TARGET_IPV6:
|
|
if (num_resolved < max_resolved) {
|
|
resolved[num_resolved].is_ipv4 = false;
|
|
resolved[num_resolved].ipv6 = targets[i].ipv6;
|
|
resolved[num_resolved].port = targets[i].port;
|
|
#ifdef HTTPS_ENABLED
|
|
resolved[num_resolved].name = NULL;
|
|
#endif
|
|
num_resolved++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return num_resolved;
|
|
}
|
|
|
|
#define MAX_CONNECT_TARGETS 16
|
|
|
|
int socket_connect(SocketManager *sm, int num_targets,
|
|
ConnectTarget *targets, bool secure, void *user)
|
|
{
|
|
if (sm->num_used == sm->max_used)
|
|
return -1;
|
|
|
|
#ifdef HTTPS_ENABLED
|
|
if (!sm->at_least_one_secure_connect) {
|
|
if (client_secure_context_init(&sm->client_secure_context) < 0)
|
|
return -1;
|
|
sm->at_least_one_secure_connect = true;
|
|
}
|
|
#else
|
|
if (secure)
|
|
return -1;
|
|
#endif
|
|
|
|
AddressAndPort resolved[MAX_CONNECT_TARGETS];
|
|
int num_resolved = resolve_connect_targets(
|
|
targets, num_targets, resolved, MAX_CONNECT_TARGETS);
|
|
|
|
if (num_resolved <= 0)
|
|
return -1;
|
|
|
|
Socket *s = sm->sockets;
|
|
while (s->state != SOCKET_STATE_FREE) {
|
|
s++;
|
|
assert(s - sm->sockets < + sm->max_used);
|
|
}
|
|
|
|
if (num_resolved == 1) {
|
|
s->num_addr = 1;
|
|
s->next_addr = 0;
|
|
s->addr = resolved[0];
|
|
} else {
|
|
s->num_addr = num_resolved;
|
|
s->next_addr = 0;
|
|
s->addrs = malloc(num_resolved * sizeof(AddressAndPort));
|
|
if (s->addrs == NULL)
|
|
return -1;
|
|
for (int i = 0; i < num_resolved; i++)
|
|
s->addrs[i] = resolved[i];
|
|
}
|
|
|
|
s->state = SOCKET_STATE_PENDING;
|
|
s->sock = NATIVE_SOCKET_INVALID;
|
|
s->user = user;
|
|
#ifdef HTTPS_ENABLED
|
|
s->server_secure_context = NULL;
|
|
s->client_secure_context = NULL;
|
|
s->ssl = NULL;
|
|
if (secure)
|
|
s->client_secure_context = &sm->client_secure_context;
|
|
#endif
|
|
sm->num_used++;
|
|
|
|
socket_update(s);
|
|
return 0;
|
|
}
|
|
|
|
static bool would_block(void)
|
|
{
|
|
#ifdef _WIN32
|
|
int err = WSAGetLastError();
|
|
return err == WSAEWOULDBLOCK;
|
|
#else
|
|
return errno == EAGAIN || errno == EWOULDBLOCK;
|
|
#endif
|
|
}
|
|
|
|
static bool interrupted(void)
|
|
{
|
|
#ifdef _WIN32
|
|
return false;
|
|
#else
|
|
return errno == EINTR;
|
|
#endif
|
|
}
|
|
|
|
static int socket_recv_nolock(SocketManager *sm, SocketHandle handle,
|
|
char *dst, int max)
|
|
{
|
|
Socket *s = handle_to_socket(sm, handle);
|
|
if (s == NULL)
|
|
return 0;
|
|
|
|
if (s->state != SOCKET_STATE_ESTABLISHED_READY) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (!is_secure(s)) {
|
|
int ret = recv(s->sock, dst, max, 0);
|
|
if (ret == 0) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
} else if (ret < 0) {
|
|
if (would_block()) {
|
|
s->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
|
s->events = POLLIN;
|
|
} else if (!interrupted()) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
}
|
|
ret = 0;
|
|
}
|
|
return ret;
|
|
} else {
|
|
#ifdef HTTPS_ENABLED
|
|
int ret = SSL_read(s->ssl, dst, max);
|
|
if (ret <= 0) {
|
|
int err = SSL_get_error(s->ssl, ret);
|
|
if (err == SSL_ERROR_WANT_READ) {
|
|
s->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
|
s->events = POLLIN;
|
|
} else if (err == SSL_ERROR_WANT_WRITE) {
|
|
s->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
|
s->events = POLLOUT;
|
|
} else {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
}
|
|
ret = 0;
|
|
}
|
|
return ret;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
int socket_recv(SocketManager *sm, SocketHandle handle,
|
|
char *dst, int max)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
int ret = socket_recv_nolock(sm, handle, dst, max);
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return ret;
|
|
}
|
|
|
|
static int socket_send_nolock(SocketManager *sm, SocketHandle handle,
|
|
char *src, int len)
|
|
{
|
|
Socket *s = handle_to_socket(sm, handle);
|
|
if (s == NULL)
|
|
return 0;
|
|
|
|
if (s->state != SOCKET_STATE_ESTABLISHED_READY) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (!is_secure(s)) {
|
|
int ret = send(s->sock, src, len, 0);
|
|
if (ret < 0) {
|
|
if (would_block()) {
|
|
s->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
|
s->events = POLLOUT;
|
|
} else if (!interrupted()) {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
}
|
|
ret = 0;
|
|
}
|
|
return ret;
|
|
} else {
|
|
#ifdef HTTPS_ENABLED
|
|
int ret = SSL_write(s->ssl, src, len);
|
|
if (ret <= 0) {
|
|
int err = SSL_get_error(s->ssl, ret);
|
|
if (err == SSL_ERROR_WANT_READ) {
|
|
s->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
|
s->events = POLLIN;
|
|
} else if (err == SSL_ERROR_WANT_WRITE) {
|
|
s->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
|
s->events = POLLOUT;
|
|
} else {
|
|
s->state = SOCKET_STATE_DIED;
|
|
s->events = 0;
|
|
}
|
|
ret = 0;
|
|
}
|
|
return ret;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
int socket_send(SocketManager *sm, SocketHandle handle,
|
|
char *src, int len)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
int ret = socket_send_nolock(sm, handle, src, len);
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return ret;
|
|
}
|
|
|
|
int socket_close(SocketManager *sm, SocketHandle handle)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
int ret;
|
|
Socket *s = handle_to_socket(sm, handle);
|
|
if (s == NULL)
|
|
ret = -1;
|
|
else {
|
|
// Only transition to SHUTDOWN if socket is not already DIED
|
|
if (s->state != SOCKET_STATE_DIED) {
|
|
s->state = SOCKET_STATE_SHUTDOWN;
|
|
s->events = 0;
|
|
socket_update(s);
|
|
}
|
|
}
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return ret;
|
|
}
|
|
|
|
int socket_is_secure(SocketManager *sm, SocketHandle handle)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
Socket *s = handle_to_socket(sm, handle);
|
|
|
|
int ret;
|
|
if (s == NULL)
|
|
ret = -1;
|
|
else
|
|
ret = is_secure(s);
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return ret;
|
|
}
|
|
|
|
int socket_set_user(SocketManager *sm, SocketHandle handle, void *user)
|
|
{
|
|
if (mutex_lock(&sm->mutex) < 0)
|
|
return -1;
|
|
|
|
int ret;
|
|
Socket *s = handle_to_socket(sm, handle);
|
|
if (s == NULL)
|
|
ret = -1;
|
|
else {
|
|
s->user = user;
|
|
ret = 0;
|
|
}
|
|
|
|
if (mutex_unlock(&sm->mutex) < 0)
|
|
return -1;
|
|
return ret;
|
|
}
|