diff options
Diffstat (limited to 'src/rexmpp_tcp.c')
-rw-r--r-- | src/rexmpp_tcp.c | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/src/rexmpp_tcp.c b/src/rexmpp_tcp.c new file mode 100644 index 0000000..55e6c1b --- /dev/null +++ b/src/rexmpp_tcp.c @@ -0,0 +1,358 @@ +/** + @file rexmpp_tcp.c + @brief TCP connection establishment. + @author defanor <defanor@uberspace.net> + @date 2020 + @copyright MIT license. +*/ + +#include <ares.h> +#include <netdb.h> +#include <arpa/nameser.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <errno.h> +#include <memory.h> +#include <sys/time.h> +#include <unistd.h> +#include <stdio.h> +#include <arpa/inet.h> +#include <fcntl.h> + +#include "rexmpp_tcp.h" + + +void rexmpp_dns_aaaa_cb (void *ptr, + int status, + int timeouts, + unsigned char *abuf, + int alen) +{ + rexmpp_tcp_conn_t *conn = ptr; + conn->resolver_status_v6 = status; + if (status == ARES_SUCCESS) { + conn->resolution_v6 = REXMPP_CONN_RESOLUTION_SUCCESS; + ares_parse_aaaa_reply(abuf, alen, &(conn->addr_v6), NULL, NULL); + conn->addr_cur_v6 = -1; + } else { + conn->resolution_v6 = REXMPP_CONN_RESOLUTION_FAILURE; + } +} + +void rexmpp_dns_a_cb (void *ptr, + int status, + int timeouts, + unsigned char *abuf, + int alen) +{ + rexmpp_tcp_conn_t *conn = ptr; + conn->resolver_status_v4 = status; + if (status == ARES_SUCCESS) { + conn->resolution_v4 = REXMPP_CONN_RESOLUTION_SUCCESS; + ares_parse_a_reply(abuf, alen, &(conn->addr_v4), NULL, NULL); + conn->addr_cur_v4 = -1; + if (conn->resolution_v6 == REXMPP_CONN_RESOLUTION_WAITING) { + /* Wait for 50 ms for IPv6. */ + gettimeofday(&(conn->next_connection_time), NULL); + conn->next_connection_time.tv_usec += REXMPP_TCP_IPV6_DELAY_MS * 1000; + if (conn->next_connection_time.tv_usec >= 1000000) { + conn->next_connection_time.tv_usec -= 1000000; + conn->next_connection_time.tv_sec++; + } + } + } else { + conn->resolution_v4 = REXMPP_CONN_RESOLUTION_FAILURE; + } +} + +rexmpp_tcp_conn_error_t +rexmpp_tcp_conn_init (rexmpp_tcp_conn_t *conn, + const char *host, + int port) +{ + int i; + for (i = 0; i < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS; i++) { + conn->sockets[i] = -1; + } + conn->connection_attempts = 0; + conn->port = port; + conn->addr_v4 = NULL; + conn->addr_v6 = NULL; + conn->resolver_error = ares_init(&(conn->resolver_channel)); + conn->fd = -1; + conn->next_connection_time.tv_sec = 0; + conn->next_connection_time.tv_usec = 0; + if (conn->resolver_error) { + return REXMPP_CONN_RESOLVER_ERROR; + } + + conn->resolution_v4 = REXMPP_CONN_RESOLUTION_INACTIVE; + conn->resolution_v6 = REXMPP_CONN_RESOLUTION_INACTIVE; + + struct sockaddr_in addr_v4; + int flags; + if (inet_pton(AF_INET, host, &addr_v4)) { + addr_v4.sin_family = AF_INET; + addr_v4.sin_port = htons(port); + conn->sockets[conn->connection_attempts] = + socket(AF_INET, SOCK_STREAM, 0); + flags = fcntl(conn->sockets[conn->connection_attempts], F_GETFL, 0); + fcntl(conn->sockets[conn->connection_attempts], F_SETFL, flags | O_NONBLOCK); + if (connect(conn->sockets[conn->connection_attempts], + (struct sockaddr*)&addr_v4, + sizeof(addr_v4))) { + if (errno != EINPROGRESS) { + return REXMPP_CONN_ERROR; + } + } else { + return REXMPP_CONN_DONE; + } + conn->connection_attempts++; + return REXMPP_CONN_IN_PROGRESS; + } + struct sockaddr_in addr_v6; + if (inet_pton(AF_INET6, host, &addr_v6)) { + addr_v6.sin_family = AF_INET6; + addr_v6.sin_port = htons(port); + conn->sockets[conn->connection_attempts] = + socket(AF_INET6, SOCK_STREAM, 0); + flags = fcntl(conn->sockets[conn->connection_attempts], F_GETFL, 0); + fcntl(conn->sockets[conn->connection_attempts], F_SETFL, flags | O_NONBLOCK); + if (connect(conn->sockets[conn->connection_attempts], + (struct sockaddr*)&addr_v6, + sizeof(addr_v6))) { + if (errno != EINPROGRESS) { + return REXMPP_CONN_ERROR; + } + } else { + return REXMPP_CONN_DONE; + } + conn->connection_attempts++; + return REXMPP_CONN_IN_PROGRESS; + } + + conn->resolution_v4 = REXMPP_CONN_RESOLUTION_WAITING; + conn->resolution_v6 = REXMPP_CONN_RESOLUTION_WAITING; + + ares_query(conn->resolver_channel, host, + ns_c_in, ns_t_aaaa, rexmpp_dns_aaaa_cb, conn); + ares_query(conn->resolver_channel, host, + ns_c_in, ns_t_a, rexmpp_dns_a_cb, conn); + + return REXMPP_CONN_IN_PROGRESS; +} + +int rexmpp_tcp_conn_finish (rexmpp_tcp_conn_t *conn) { + int i; + for (i = 0; i < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS; i++) { + if (conn->sockets[i] != -1 && conn->sockets[i] != conn->fd) { + close(conn->sockets[i]); + conn->sockets[i] = -1; + } + } + ares_destroy(conn->resolver_channel); + if (conn->addr_v4 != NULL) { + ares_free_hostent(conn->addr_v4); + conn->addr_v4 = NULL; + } + if (conn->addr_v6 != NULL) { + ares_free_hostent(conn->addr_v6); + conn->addr_v6 = NULL; + } + return conn->fd; +} + +int rexmpp_tcp_conn_ipv4_available(rexmpp_tcp_conn_t *conn) { + return (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_SUCCESS && + conn->addr_v4 != NULL && + conn->addr_v4->h_addr_list[conn->addr_cur_v4 + 1] != NULL); +} + +int rexmpp_tcp_conn_ipv6_available(rexmpp_tcp_conn_t *conn) { + return (conn->resolution_v6 == REXMPP_CONN_RESOLUTION_SUCCESS && + conn->addr_v6 != NULL && + conn->addr_v6->h_addr_list[conn->addr_cur_v6 + 1] != NULL); +} + +rexmpp_tcp_conn_error_t +rexmpp_tcp_conn_proceed (rexmpp_tcp_conn_t *conn, + fd_set *read_fds, + fd_set *write_fds) +{ + struct timeval now; + int i; + + /* Check for successful connections. */ + for (i = 0; i < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS; i++) { + int err; + socklen_t err_len = sizeof(err); + if (conn->sockets[i] != -1 && FD_ISSET(conn->sockets[i], write_fds)) { + if (getsockopt(conn->sockets[i], SOL_SOCKET, SO_ERROR, &err, &err_len)) { + return REXMPP_CONN_ERROR; + } else { + if (err == 0) { + conn->fd = conn->sockets[i]; + return REXMPP_CONN_DONE; + } else if (err != EINPROGRESS) { + close(conn->sockets[i]); + conn->sockets[i] = -1; + } + } + } + } + + /* Name resolution. */ + if (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_WAITING || + conn->resolution_v6 == REXMPP_CONN_RESOLUTION_WAITING) { + ares_process(conn->resolver_channel, read_fds, write_fds); + } + + /* New connections. */ + int repeat; + do { + repeat = 0; + if (conn->connection_attempts < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS && + (rexmpp_tcp_conn_ipv4_available(conn) || + rexmpp_tcp_conn_ipv6_available(conn))) { + gettimeofday(&now, NULL); + if (now.tv_sec > conn->next_connection_time.tv_sec || + (now.tv_sec == conn->next_connection_time.tv_sec && + now.tv_usec >= conn->next_connection_time.tv_usec)) { + /* Time to attempt a new connection. */ + int use_ipv6 = 0; + if (rexmpp_tcp_conn_ipv4_available(conn) && + rexmpp_tcp_conn_ipv6_available(conn)) { + if (conn->addr_cur_v4 >= conn->addr_cur_v6) { + use_ipv6 = 1; + } + } else if (rexmpp_tcp_conn_ipv6_available(conn)) { + use_ipv6 = 1; + } + + struct sockaddr_in6 addr_v6; + struct sockaddr_in addr_v4; + struct sockaddr *addr; + socklen_t addrlen; + int domain; + + if (use_ipv6) { + conn->addr_cur_v6++; + memcpy(&addr_v6.sin6_addr, + conn->addr_v6->h_addr_list[conn->addr_cur_v6], + conn->addr_v6->h_length); + addr_v6.sin6_family = conn->addr_v6->h_addrtype; + addr_v6.sin6_port = htons(conn->port); + domain = conn->addr_v6->h_addrtype; + addr = (struct sockaddr*)&addr_v6; + addrlen = sizeof(addr_v6); + } else { + conn->addr_cur_v4++; + memcpy(&addr_v4.sin_addr, + conn->addr_v4->h_addr_list[conn->addr_cur_v4], + conn->addr_v4->h_length); + addr_v4.sin_family = conn->addr_v4->h_addrtype; + addr_v4.sin_port = htons(conn->port); + domain = conn->addr_v4->h_addrtype; + addr = (struct sockaddr*)&addr_v4; + addrlen = sizeof(addr_v4); + } + + conn->sockets[conn->connection_attempts] = + socket(domain, SOCK_STREAM, 0); + int flags = fcntl(conn->sockets[conn->connection_attempts], F_GETFL, 0); + fcntl(conn->sockets[conn->connection_attempts], F_SETFL, flags | O_NONBLOCK); + if (connect(conn->sockets[conn->connection_attempts], addr, addrlen)) { + if (errno == EINPROGRESS) { + gettimeofday(&(conn->next_connection_time), NULL); + conn->next_connection_time.tv_usec += REXMPP_TCP_CONN_DELAY_MS * 1000; + if (conn->next_connection_time.tv_usec >= 1000000) { + conn->next_connection_time.tv_usec -= 1000000; + conn->next_connection_time.tv_sec++; + } + conn->connection_attempts++; + } else { + close(conn->sockets[conn->connection_attempts]); + conn->sockets[conn->connection_attempts] = -1; + if (conn->connection_attempts < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS && + (rexmpp_tcp_conn_ipv4_available(conn) || + rexmpp_tcp_conn_ipv6_available(conn))) { + repeat = 1; + } + } + } else { + conn->fd = conn->sockets[conn->connection_attempts]; + return REXMPP_CONN_DONE; + } + } + } + } while (repeat); + + int active_connections = 0; + for (i = 0; i < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS; i++) { + if (conn->sockets[i] != -1) { + active_connections++; + break; + } + } + + gettimeofday(&now, NULL); + + if (active_connections || + conn->resolution_v4 == REXMPP_CONN_RESOLUTION_WAITING || + conn->resolution_v6 == REXMPP_CONN_RESOLUTION_WAITING || + (conn->next_connection_time.tv_sec > now.tv_sec || + (conn->next_connection_time.tv_sec == now.tv_sec && + conn->next_connection_time.tv_usec > now.tv_usec))) { + return REXMPP_CONN_IN_PROGRESS; + } else { + return REXMPP_CONN_FAILURE; + } +} + +int rexmpp_tcp_conn_fds (rexmpp_tcp_conn_t *conn, + fd_set *read_fds, + fd_set *write_fds) +{ + int max_fd = 0, i; + max_fd = ares_fds(conn->resolver_channel, read_fds, write_fds); + for (i = 0; i < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS; i++) { + if (conn->sockets[i] != -1) { + FD_SET(conn->sockets[i], write_fds); + if (max_fd < conn->sockets[i]) { + max_fd = conn->sockets[i] + 1; + } + } + } + return max_fd; +} + +struct timeval *rexmpp_tcp_conn_timeout (rexmpp_tcp_conn_t *conn, + struct timeval *max_tv, + struct timeval *tv) +{ + struct timeval now; + struct timeval *ret; + ret = ares_timeout(conn->resolver_channel, max_tv, tv); + if (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_SUCCESS || + conn->resolution_v6 == REXMPP_CONN_RESOLUTION_SUCCESS) { + gettimeofday(&now, NULL); + if (now.tv_sec < conn->next_connection_time.tv_sec || + (now.tv_sec == conn->next_connection_time.tv_sec && + now.tv_usec <= conn->next_connection_time.tv_usec)) { + if (ret == NULL || + ret->tv_sec > conn->next_connection_time.tv_sec - now.tv_sec || + (ret->tv_sec == conn->next_connection_time.tv_sec - now.tv_sec && + ret->tv_usec > conn->next_connection_time.tv_usec - now.tv_usec)) { + ret = tv; + tv->tv_sec = conn->next_connection_time.tv_sec - now.tv_sec; + if (conn->next_connection_time.tv_usec > now.tv_usec) { + tv->tv_usec = conn->next_connection_time.tv_usec - now.tv_usec; + } else { + tv->tv_usec = conn->next_connection_time.tv_usec + 1000000 - now.tv_usec; + tv->tv_sec--; + } + } + } + } + return ret; +} |