From cd7b005519ade8ab6c97fcb21590b71b7d1be6e3 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 11:54:46 +0200 Subject: Adding upstream version 0.8.0. Signed-off-by: Daniel Baumann --- rtrlib/transport/tcp/tcp_transport.c | 368 +++++++++++++++++++++++++++ rtrlib/transport/tcp/tcp_transport.h | 59 +++++ rtrlib/transport/tcp/tcp_transport_private.h | 23 ++ 3 files changed, 450 insertions(+) create mode 100644 rtrlib/transport/tcp/tcp_transport.c create mode 100644 rtrlib/transport/tcp/tcp_transport.h create mode 100644 rtrlib/transport/tcp/tcp_transport_private.h (limited to 'rtrlib/transport/tcp') diff --git a/rtrlib/transport/tcp/tcp_transport.c b/rtrlib/transport/tcp/tcp_transport.c new file mode 100644 index 0000000..1dbc903 --- /dev/null +++ b/rtrlib/transport/tcp/tcp_transport.c @@ -0,0 +1,368 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "tcp_transport_private.h" + +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/log_private.h" +#include "rtrlib/rtrlib_export_private.h" +#include "rtrlib/transport/transport_private.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TCP_DBG(fmt, sock, ...) \ + do { \ + const struct tr_tcp_socket *tmp = sock; \ + lrtr_dbg("TCP Transport(%s:%s): " fmt, tmp->config.host, tmp->config.port, ##__VA_ARGS__); \ + } while (0) +#define TCP_DBG1(a, sock) TCP_DBG(a, sock) + +struct tr_tcp_socket { + int socket; + struct tr_tcp_config config; + char *ident; +}; + +static int tr_tcp_open(void *tr_tcp_sock); +static void tr_tcp_close(void *tr_tcp_sock); +static void tr_tcp_free(struct tr_socket *tr_sock); +static int tr_tcp_recv(const void *tr_tcp_sock, void *pdu, const size_t len, const time_t timeout); +static int tr_tcp_send(const void *tr_tcp_sock, const void *pdu, const size_t len, const time_t timeout); +static const char *tr_tcp_ident(void *socket); + +static int set_socket_blocking(int socket) +{ + int flags = fcntl(socket, F_GETFL); + + if (flags == -1) + return TR_ERROR; + + flags &= ~O_NONBLOCK; + + if (fcntl(socket, F_SETFL, flags) == -1) + return TR_ERROR; + + return TR_SUCCESS; +} + +static int set_socket_non_blocking(int socket) +{ + int flags = fcntl(socket, F_GETFL); + + if (flags == -1) + return TR_ERROR; + + flags |= O_NONBLOCK; + + if (fcntl(socket, F_SETFL, flags) == -1) + return TR_ERROR; + + return TR_SUCCESS; +} + +static int get_socket_error(int socket) +{ + int result; + socklen_t result_len = sizeof(result); + + if (getsockopt(socket, SOL_SOCKET, SO_ERROR, &result, &result_len) < 0) + return TR_ERROR; + + return result; +} + +/* WARNING: This function has cancelable sections! */ +int tr_tcp_open(void *tr_socket) +{ + int rtval = TR_ERROR; + int tcp_rtval = 0; + struct tr_tcp_socket *tcp_socket = tr_socket; + const struct tr_tcp_config *config = &tcp_socket->config; + + assert(tcp_socket->socket == -1); + + struct addrinfo hints; + struct addrinfo *res = NULL; + struct addrinfo *bind_addrinfo = NULL; + + bzero(&hints, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG; + + if (config->new_socket) { + tcp_socket->socket = (*config->new_socket)(config->data); + if (tcp_socket->socket <= 0) { + TCP_DBG("Couldn't establish TCP connection, %s", + tcp_socket, strerror(errno)); + goto end; + } + } + if (tcp_socket->socket < 0) { + tcp_rtval = getaddrinfo(config->host, config->port, &hints, &res); + if (tcp_rtval != 0) { + if (tcp_rtval == EAI_SYSTEM) { + TCP_DBG("getaddrinfo error, %s", tcp_socket, + strerror(errno)); + } else { + TCP_DBG("getaddrinfo error, %s", tcp_socket, + gai_strerror(tcp_rtval)); + } + return TR_ERROR; + } + + tcp_socket->socket = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (tcp_socket->socket == -1) { + TCP_DBG("Socket creation failed, %s", tcp_socket, strerror(errno)); + goto end; + } + + if (tcp_socket->config.bindaddr) { + tcp_rtval = getaddrinfo(tcp_socket->config.bindaddr, 0, &hints, &bind_addrinfo); + if (tcp_rtval != 0) { + if (tcp_rtval == EAI_SYSTEM) { + TCP_DBG("getaddrinfo error, %s", tcp_socket, + strerror(errno)); + } else { + TCP_DBG("getaddrinfo error, %s", tcp_socket, + gai_strerror(tcp_rtval)); + } + goto end; + } + if (bind(tcp_socket->socket, bind_addrinfo->ai_addr, bind_addrinfo->ai_addrlen) != 0) { + TCP_DBG("Socket bind failed, %s", tcp_socket, strerror(errno)); + goto end; + } + + freeaddrinfo(bind_addrinfo); + bind_addrinfo = NULL; + } + + if (set_socket_non_blocking(tcp_socket->socket) == TR_ERROR) { + TCP_DBG("Could not set socket to non blocking, %s", tcp_socket, strerror(errno)); + goto end; + } + + if (connect(tcp_socket->socket, res->ai_addr, res->ai_addrlen) == -1 && errno != EINPROGRESS) { + TCP_DBG("Couldn't establish TCP connection, %s", + tcp_socket, strerror(errno)); + goto end; + } + + freeaddrinfo(res); + res = NULL; + + fd_set wfds; + + FD_ZERO(&wfds); + FD_SET(tcp_socket->socket, &wfds); + + struct timeval timeout = {.tv_sec = tcp_socket->config.connect_timeout, .tv_usec = 0}; + int oldcancelstate; + + /* Enable cancellability for the select call + * to prevent rtr_stop from blocking for a long time. + * It must be the only blocking call in this function. + * Since local resources have all been freed this should be safe. + */ + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate); + int ret = select(tcp_socket->socket + 1, NULL, &wfds, NULL, &timeout); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate); + + if (ret < 0) { + TCP_DBG("Could not select tcp socket, %s", tcp_socket, strerror(errno)); + goto end; + + } else if (ret == 0) { + TCP_DBG("Could not establish TCP connection in time", tcp_socket); + goto end; + } + + int socket_error = get_socket_error(tcp_socket->socket); + + if (socket_error == TR_ERROR) { + TCP_DBG("Could not get socket error, %s", tcp_socket, strerror(errno)); + goto end; + + } else if (socket_error > 0) { + TCP_DBG("Could not establish TCP connection, %s", tcp_socket, strerror(socket_error)); + goto end; + } + + if (set_socket_blocking(tcp_socket->socket) == TR_ERROR) { + TCP_DBG("Could not set socket to blocking, %s", tcp_socket, strerror(errno)); + goto end; + } + } + + TCP_DBG1("Connection established", tcp_socket); + rtval = TR_SUCCESS; + +end: + if (res) + freeaddrinfo(res); + + if (bind_addrinfo) + freeaddrinfo(bind_addrinfo); + if (rtval == -1) + tr_tcp_close(tr_socket); + return rtval; +} + +void tr_tcp_close(void *tr_tcp_sock) +{ + struct tr_tcp_socket *tcp_socket = tr_tcp_sock; + + if (tcp_socket->socket != -1) + close(tcp_socket->socket); + TCP_DBG1("Socket closed", tcp_socket); + tcp_socket->socket = -1; +} + +void tr_tcp_free(struct tr_socket *tr_sock) +{ + struct tr_tcp_socket *tcp_sock = tr_sock->socket; + + assert(tcp_sock); + assert(tcp_sock->socket == -1); + + TCP_DBG1("Freeing socket", tcp_sock); + + lrtr_free(tcp_sock->config.host); + lrtr_free(tcp_sock->config.port); + lrtr_free(tcp_sock->config.bindaddr); + + if (tcp_sock->ident) + lrtr_free(tcp_sock->ident); + tr_sock->socket = NULL; + lrtr_free(tcp_sock); +} + +int tr_tcp_recv(const void *tr_tcp_sock, void *pdu, const size_t len, const time_t timeout) +{ + const struct tr_tcp_socket *tcp_socket = tr_tcp_sock; + int rtval; + + if (timeout == 0) { + rtval = recv(tcp_socket->socket, pdu, len, MSG_DONTWAIT); + } else { + struct timeval t = {timeout, 0}; + + if (setsockopt(tcp_socket->socket, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)) == -1) { + TCP_DBG("setting SO_RCVTIMEO failed, %s", tcp_socket, strerror(errno)); + return TR_ERROR; + } + rtval = recv(tcp_socket->socket, pdu, len, 0); + } + + if (rtval == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return TR_WOULDBLOCK; + if (errno == EINTR) + return TR_INTR; + TCP_DBG("recv(..) error: %s", tcp_socket, strerror(errno)); + return TR_ERROR; + } + if (rtval == 0) + return TR_CLOSED; + return rtval; +} + +int tr_tcp_send(const void *tr_tcp_sock, const void *pdu, const size_t len, const time_t timeout) +{ + const struct tr_tcp_socket *tcp_socket = tr_tcp_sock; + int rtval; + + if (timeout == 0) { + rtval = send(tcp_socket->socket, pdu, len, MSG_DONTWAIT); + } else { + struct timeval t = {timeout, 0}; + + if (setsockopt(tcp_socket->socket, SOL_SOCKET, SO_SNDTIMEO, &t, sizeof(t)) == -1) { + TCP_DBG("setting SO_SNDTIMEO failed, %s", tcp_socket, strerror(errno)); + return TR_ERROR; + } + rtval = send(tcp_socket->socket, pdu, len, 0); + } + + if (rtval == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return TR_WOULDBLOCK; + if (errno == EINTR) + return TR_INTR; + TCP_DBG("send(..) error: %s", tcp_socket, strerror(errno)); + return TR_ERROR; + } + if (rtval == 0) + return TR_ERROR; + return rtval; +} + +const char *tr_tcp_ident(void *socket) +{ + size_t len; + struct tr_tcp_socket *sock = socket; + + assert(sock); + + if (sock->ident) + return sock->ident; + + len = strlen(sock->config.port) + strlen(sock->config.host) + 2; + sock->ident = lrtr_malloc(len); + if (!sock->ident) + return NULL; + snprintf(sock->ident, len, "%s:%s", sock->config.host, sock->config.port); + + return sock->ident; +} + +RTRLIB_EXPORT int tr_tcp_init(const struct tr_tcp_config *config, struct tr_socket *socket) +{ + socket->close_fp = &tr_tcp_close; + socket->free_fp = &tr_tcp_free; + socket->open_fp = &tr_tcp_open; + socket->recv_fp = &tr_tcp_recv; + socket->send_fp = &tr_tcp_send; + socket->ident_fp = &tr_tcp_ident; + + socket->socket = lrtr_malloc(sizeof(struct tr_tcp_socket)); + struct tr_tcp_socket *tcp_socket = socket->socket; + + tcp_socket->socket = -1; + tcp_socket->config.host = lrtr_strdup(config->host); + tcp_socket->config.port = lrtr_strdup(config->port); + if (config->bindaddr) + tcp_socket->config.bindaddr = lrtr_strdup(config->bindaddr); + else + tcp_socket->config.bindaddr = NULL; + + if (config->connect_timeout == 0) + tcp_socket->config.connect_timeout = RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT; + else + tcp_socket->config.connect_timeout = config->connect_timeout; + + tcp_socket->ident = NULL; + tcp_socket->config.data = config->data; + tcp_socket->config.new_socket = config->new_socket; + + return TR_SUCCESS; +} diff --git a/rtrlib/transport/tcp/tcp_transport.h b/rtrlib/transport/tcp/tcp_transport.h new file mode 100644 index 0000000..3198050 --- /dev/null +++ b/rtrlib/transport/tcp/tcp_transport.h @@ -0,0 +1,59 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_tcp_transport_h TCP transport socket + * @ingroup mod_transport_h + * @brief An implementation of the TCP protocol for the RTR transport. + * See @ref mod_transport_h "transport interface" for a list of supported operations. + * + * @{ + */ + +#ifndef RTR_TCP_TRANSPORT_H +#define RTR_TCP_TRANSPORT_H + +#include "rtrlib/transport/transport.h" + +/** + * @brief A tr_tcp_config struct holds configuration for a TCP connection. + * @param host Hostname or IP address to connect to. + * @param port Port to connect to. + * @param bindaddr Hostname or IP address to connect from. NULL for + * determination by OS. + * to use the source address of the system's default route to the server + * @param data Information to pass to callback function + * in charge of retrieving socket + * @param new_socket(void *opaque_info) callback routine, that + * Pointer to the function that is called every time a new connection + * is made. The returned socket is expected to be ready for use (e.g. + * in state established), and must use a reliably stream-oriented transport. + * When new_socket() is used, host, port, and bindaddr are not used. + * @param connect_timeout Time in seconds to wait for a successful connection. + * Defaults to #RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT + */ +struct tr_tcp_config { + char *host; + char *port; + char *bindaddr; + void *data; + int (*new_socket)(void *data); + unsigned int connect_timeout; +}; + +/** + * @brief Initializes the tr_socket struct for a TCP connection. + * @param[in] config TCP configuration for the connection. + * @param[out] socket Initialized transport socket. + * @returns TR_SUCCESS On success. + * @returns TR_ERROR On error. + */ +int tr_tcp_init(const struct tr_tcp_config *config, struct tr_socket *socket); +#endif +/** @} */ diff --git a/rtrlib/transport/tcp/tcp_transport_private.h b/rtrlib/transport/tcp/tcp_transport_private.h new file mode 100644 index 0000000..ef41a32 --- /dev/null +++ b/rtrlib/transport/tcp/tcp_transport_private.h @@ -0,0 +1,23 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_tcp_transport_h TCP transport socket + * @ingroup mod_transport_h + * @brief An implementation of the TCP protocol for the RTR transport. + * See @ref mod_transport_h "transport interface" for a list of supported operations. + * + * @{ + */ + +#ifndef RTR_TCP_TRANSPORT_PRIVATE_H +#define RTR_TCP_TRANSPORT_PRIVATE_H +#include "tcp_transport.h" +#endif +/** @} */ -- cgit v1.2.3