diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:54:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:54:46 +0000 |
commit | cd7b005519ade8ab6c97fcb21590b71b7d1be6e3 (patch) | |
tree | c611a8d0cd5e8f68f41b8c2d16ba580e0f40a38d /rtrlib/transport | |
parent | Initial commit. (diff) | |
download | librtr-upstream.tar.xz librtr-upstream.zip |
Adding upstream version 0.8.0.upstream/0.8.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'rtrlib/transport')
-rw-r--r-- | rtrlib/transport/ssh/ssh_transport.c | 392 | ||||
-rw-r--r-- | rtrlib/transport/ssh/ssh_transport.h | 74 | ||||
-rw-r--r-- | rtrlib/transport/ssh/ssh_transport_private.h | 13 | ||||
-rw-r--r-- | rtrlib/transport/tcp/tcp_transport.c | 368 | ||||
-rw-r--r-- | rtrlib/transport/tcp/tcp_transport.h | 59 | ||||
-rw-r--r-- | rtrlib/transport/tcp/tcp_transport_private.h | 23 | ||||
-rw-r--r-- | rtrlib/transport/transport.c | 87 | ||||
-rw-r--r-- | rtrlib/transport/transport.h | 113 | ||||
-rw-r--r-- | rtrlib/transport/transport_private.h | 107 |
9 files changed, 1236 insertions, 0 deletions
diff --git a/rtrlib/transport/ssh/ssh_transport.c b/rtrlib/transport/ssh/ssh_transport.c new file mode 100644 index 0000000..222ea25 --- /dev/null +++ b/rtrlib/transport/ssh/ssh_transport.c @@ -0,0 +1,392 @@ +/* + * 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 "ssh_transport_private.h" + +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/log_private.h" +#include "rtrlib/lib/utils_private.h" +#include "rtrlib/rtrlib_export_private.h" +#include "rtrlib/transport/transport_private.h" + +#include <assert.h> +#include <errno.h> +#include <libssh/libssh.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> + +#define SSH_DBG(fmt, sock, ...) \ + do { \ + const struct tr_ssh_socket *tmp = sock; \ + lrtr_dbg("SSH Transport(%s@%s:%u): " fmt, tmp->config.username, tmp->config.host, tmp->config.port, \ + ##__VA_ARGS__); \ + } while (0) +#define SSH_DBG1(a, sock) SSH_DBG(a, sock) + +struct tr_ssh_socket { + ssh_session session; + ssh_channel channel; + struct tr_ssh_config config; + char *ident; +}; + +static int tr_ssh_open(void *tr_ssh_sock); +static void tr_ssh_close(void *tr_ssh_sock); +static void tr_ssh_free(struct tr_socket *tr_sock); +static int tr_ssh_recv(const void *tr_ssh_sock, void *buf, const size_t buf_len, const time_t timeout); +static int tr_ssh_send(const void *tr_ssh_sock, const void *pdu, const size_t len, const time_t timeout); +static int tr_ssh_recv_async(const struct tr_ssh_socket *tr_ssh_sock, void *buf, const size_t buf_len); +static const char *tr_ssh_ident(void *tr_ssh_sock); + +/* WARNING: This function has cancelable sections! */ +int tr_ssh_open(void *socket) +{ + struct tr_ssh_socket *ssh_socket = socket; + const struct tr_ssh_config *config = &ssh_socket->config; + + assert(!ssh_socket->channel); + assert(!ssh_socket->session); + + ssh_socket->session = ssh_new(); + if (!ssh_socket->session) { + SSH_DBG("%s: can't create ssh_session", ssh_socket, __func__); + goto error; + } + + const int verbosity = SSH_LOG_NOLOG; + + ssh_options_set(ssh_socket->session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + ssh_options_set(ssh_socket->session, SSH_OPTIONS_HOST, config->host); + ssh_options_set(ssh_socket->session, SSH_OPTIONS_PORT, &(config->port)); + ssh_options_set(ssh_socket->session, SSH_OPTIONS_BINDADDR, config->bindaddr); + ssh_options_set(ssh_socket->session, SSH_OPTIONS_USER, config->username); + + if (config->server_hostkey_path) + ssh_options_set(ssh_socket->session, SSH_OPTIONS_KNOWNHOSTS, config->server_hostkey_path); + + if (config->client_privkey_path) + ssh_options_set(ssh_socket->session, SSH_OPTIONS_IDENTITY, config->client_privkey_path); + if (config->new_socket) { + int fd; + + fd = config->new_socket(config->data); + if (fd >= 0) { + ssh_options_set(ssh_socket->session, SSH_OPTIONS_FD, &fd); + } else { + SSH_DBG1("tr_ssh_init: opening SSH connection failed", ssh_socket); + goto error; + } + } + + ssh_set_blocking(ssh_socket->session, 0); + int ret; + + do { + ret = ssh_connect(ssh_socket->session); + + if (ret == SSH_ERROR) { + SSH_DBG("%s: opening SSH connection failed", ssh_socket, __func__); + goto error; + } else if (ret == SSH_AGAIN) { + socket_t fd = ssh_get_fd(ssh_socket->session); + + fd_set rfds; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + struct timeval timeout = {.tv_sec = ssh_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 sret = select(fd + 1, &rfds, NULL, NULL, &timeout); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate); + + if (sret < 0) { + SSH_DBG("could not select ssh socket, %s", ssh_socket, strerror(errno)); + + } else if (sret == 0) { + SSH_DBG1("connection attempt timed out", ssh_socket); + goto error; + } + } + + } while (ret != SSH_OK); + + ssh_set_blocking(ssh_socket->session, 1); + + // check server identity +#if LIBSSH_VERSION_MAJOR > 0 || LIBSSH_VERSION_MINOR > 8 + if ((config->server_hostkey_path) && (ssh_session_is_known_server(ssh_socket->session) != SSH_KNOWN_HOSTS_OK)) { +#else + if ((config->server_hostkey_path) && (ssh_is_server_known(ssh_socket->session) != SSH_SERVER_KNOWN_OK)) { +#endif + SSH_DBG("%s: Wrong hostkey", ssh_socket, __func__); + goto error; + } + + if (config->client_privkey_path) { + SSH_DBG("%s: Trying publickey authentication", ssh_socket, __func__); + ssh_options_set(ssh_socket->session, SSH_OPTIONS_IDENTITY, config->client_privkey_path); + + int rtval; + +#if LIBSSH_VERSION_MAJOR > 0 || LIBSSH_VERSION_MINOR > 5 + rtval = ssh_userauth_publickey_auto(ssh_socket->session, NULL, NULL); +#else /* else use libSSH version 0.5.0 */ + rtval = ssh_userauth_autopubkey(ssh_socket->session, NULL); +#endif + if (rtval != SSH_AUTH_SUCCESS) { + SSH_DBG("%s: Publickey authentication failed", ssh_socket, __func__); + goto error; + } + + } else { + SSH_DBG("%s: Trying password authentication", ssh_socket, __func__); + + if (ssh_userauth_password(ssh_socket->session, NULL, config->password) != SSH_AUTH_SUCCESS) { + SSH_DBG("%s: Password authentication failed", ssh_socket, __func__); + goto error; + } + } + + ssh_socket->channel = ssh_channel_new(ssh_socket->session); + if (!ssh_socket->channel) + goto error; + + if (ssh_channel_open_session(ssh_socket->channel) == SSH_ERROR) + goto error; + + if (ssh_channel_request_subsystem(ssh_socket->channel, "rpki-rtr") == SSH_ERROR) { + SSH_DBG("%s: Error requesting subsystem rpki-rtr", ssh_socket, __func__); + goto error; + } + SSH_DBG1("Connection established", ssh_socket); + + return TR_SUCCESS; + +error: + tr_ssh_close(ssh_socket); + return TR_ERROR; +} + +void tr_ssh_close(void *tr_ssh_sock) +{ + struct tr_ssh_socket *socket = tr_ssh_sock; + + if (socket->channel) { + if (ssh_channel_is_open(socket->channel)) + ssh_channel_close(socket->channel); + ssh_channel_free(socket->channel); + socket->channel = NULL; + } + if (socket->session) { + ssh_disconnect(socket->session); + ssh_free(socket->session); + socket->session = NULL; + } + SSH_DBG1("Socket closed", socket); +} + +void tr_ssh_free(struct tr_socket *tr_sock) +{ + struct tr_ssh_socket *tr_ssh_sock = tr_sock->socket; + + assert(!tr_ssh_sock->channel); + assert(!tr_ssh_sock->session); + + SSH_DBG1("Freeing socket", tr_ssh_sock); + + lrtr_free(tr_ssh_sock->config.host); + lrtr_free(tr_ssh_sock->config.bindaddr); + lrtr_free(tr_ssh_sock->config.username); + lrtr_free(tr_ssh_sock->config.client_privkey_path); + lrtr_free(tr_ssh_sock->config.server_hostkey_path); + + if (tr_ssh_sock->ident) + lrtr_free(tr_ssh_sock->ident); + lrtr_free(tr_ssh_sock); + tr_sock->socket = NULL; +} + +int tr_ssh_recv_async(const struct tr_ssh_socket *tr_ssh_sock, void *buf, const size_t buf_len) +{ + const int rtval = ssh_channel_read_nonblocking(tr_ssh_sock->channel, buf, buf_len, false); + + if (rtval == 0) { + if (ssh_channel_is_eof(tr_ssh_sock->channel) != 0) { + SSH_DBG1("remote has sent EOF", tr_ssh_sock); + return TR_CLOSED; + } else { + return TR_WOULDBLOCK; + } + } else if (rtval == SSH_ERROR) { + SSH_DBG1("recv(..) error", tr_ssh_sock); + return TR_ERROR; + } + return rtval; +} + +int tr_ssh_recv(const void *tr_ssh_sock, void *buf, const size_t buf_len, const time_t timeout) +{ + ssh_channel rchans[2] = {((struct tr_ssh_socket *)tr_ssh_sock)->channel, NULL}; + struct timeval timev = {timeout, 0}; + int ret; + + ret = ssh_channel_select(rchans, NULL, NULL, &timev); + if (ret == SSH_EINTR) + return TR_INTR; + else if (ret == SSH_ERROR) + return TR_ERROR; + + if (ssh_channel_is_eof(((struct tr_ssh_socket *)tr_ssh_sock)->channel) != 0) + return TR_ERROR; + + if (!rchans[0]) + return TR_WOULDBLOCK; + + return tr_ssh_recv_async(tr_ssh_sock, buf, buf_len); +} + +int tr_ssh_send(const void *tr_ssh_sock, const void *pdu, const size_t len, + const time_t timeout __attribute__((unused))) +{ + int ret = ssh_channel_write(((struct tr_ssh_socket *)tr_ssh_sock)->channel, pdu, len); + + if (ret == SSH_ERROR) + return TR_ERROR; + + return ret; +} + +const char *tr_ssh_ident(void *tr_ssh_sock) +{ + size_t len; + struct tr_ssh_socket *sock = tr_ssh_sock; + + assert(sock); + + if (sock->ident) + return sock->ident; + + len = strlen(sock->config.username) + 1 + strlen(sock->config.host) + 1 + 5 + 1; + sock->ident = lrtr_malloc(len); + if (!sock->ident) + return NULL; + snprintf(sock->ident, len, "%s@%s:%u", sock->config.username, sock->config.host, sock->config.port); + return sock->ident; +} + +RTRLIB_EXPORT int tr_ssh_init(const struct tr_ssh_config *config, struct tr_socket *socket) +{ + socket->close_fp = &tr_ssh_close; + socket->free_fp = &tr_ssh_free; + socket->open_fp = &tr_ssh_open; + socket->recv_fp = &tr_ssh_recv; + socket->send_fp = &tr_ssh_send; + socket->ident_fp = &tr_ssh_ident; + + socket->socket = lrtr_calloc(1, sizeof(struct tr_ssh_socket)); + struct tr_ssh_socket *ssh_socket = socket->socket; + + ssh_socket->channel = NULL; + ssh_socket->session = NULL; + ssh_socket->config.host = lrtr_strdup(config->host); + if (!ssh_socket->config.host) + goto error; + ssh_socket->config.port = config->port; + + ssh_socket->config.username = lrtr_strdup(config->username); + if (!ssh_socket->config.username) + goto error; + + if ((config->password && config->client_privkey_path) || (!config->password && !config->client_privkey_path)) + return TR_ERROR; + + if (config->bindaddr) { + ssh_socket->config.bindaddr = lrtr_strdup(config->bindaddr); + + if (!ssh_socket->config.bindaddr) + goto error; + + } else { + ssh_socket->config.bindaddr = NULL; + } + + if (config->client_privkey_path) { + ssh_socket->config.client_privkey_path = lrtr_strdup(config->client_privkey_path); + if (!ssh_socket->config.client_privkey_path) + goto error; + + } else { + ssh_socket->config.client_privkey_path = NULL; + } + + if (config->server_hostkey_path) { + ssh_socket->config.server_hostkey_path = lrtr_strdup(config->server_hostkey_path); + + if (!ssh_socket->config.client_privkey_path) + goto error; + + } else { + ssh_socket->config.server_hostkey_path = NULL; + } + + if (config->connect_timeout == 0) + ssh_socket->config.connect_timeout = RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT; + else + ssh_socket->config.connect_timeout = config->connect_timeout; + + if (config->password) { + ssh_socket->config.password = lrtr_strdup(config->password); + + if (!ssh_socket->config.password) + goto error; + + } else { + ssh_socket->config.password = NULL; + } + + ssh_socket->ident = NULL; + ssh_socket->config.data = config->data; + ssh_socket->config.new_socket = config->new_socket; + + return TR_SUCCESS; + +error: + if (ssh_socket->config.host) + free(ssh_socket->config.host); + + if (ssh_socket->config.username) + free(ssh_socket->config.username); + + if (ssh_socket->config.bindaddr) + free(ssh_socket->config.bindaddr); + + if (ssh_socket->config.client_privkey_path) + free(ssh_socket->config.client_privkey_path); + + if (ssh_socket->config.server_hostkey_path) + free(ssh_socket->config.server_hostkey_path); + + if (ssh_socket->config.password) + free(ssh_socket->config.password); + + return TR_ERROR; +} diff --git a/rtrlib/transport/ssh/ssh_transport.h b/rtrlib/transport/ssh/ssh_transport.h new file mode 100644 index 0000000..188e256 --- /dev/null +++ b/rtrlib/transport/ssh/ssh_transport.h @@ -0,0 +1,74 @@ +/* + * 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_ssh_transport_h SSH transport socket + * @ingroup mod_transport_h + * @brief An implementation of the SSH protocol for the RTR transport. + * @details This transport implementation uses libssh + * (http://www.libssh.org/) for all ssh specific operations.\n + * See @ref mod_transport_h "transport interface" for a list of supported + * operations. + * + * @{ + * + * @example ssh_tr.c + * Example of how to open a SSH transport connection. + */ + +#ifndef SSH_TRANSPORT_H +#define SSH_TRANSPORT_H + +#include "rtrlib/transport/transport.h" + +/** + * @brief A tr_ssh_config struct holds configuration data for an tr_ssh socket. + * @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. + * @param username Username for authentication. + * @param server_hostkey_path Path to public SSH key of the server or NULL to + * don't verify host authenticity. + * @param client_privkey_path Path to private key of the authentication keypair + * or NULL to use ~/.ssh/id_rsa. + * @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_ssh_config { + char *host; + unsigned int port; + char *bindaddr; + char *username; + char *server_hostkey_path; + char *client_privkey_path; + void *data; + int (*new_socket)(void *data); + unsigned int connect_timeout; + char *password; +}; + +/** + * @brief Initializes the tr_socket struct for a SSH connection. + * @param[in] config SSH configuration for the connection. + * @param[out] socket Initialized transport socket. + * @returns TR_SUCCESS On success. + * @returns TR_ERROR On error. + */ +int tr_ssh_init(const struct tr_ssh_config *config, struct tr_socket *socket); + +#endif +/** @} */ diff --git a/rtrlib/transport/ssh/ssh_transport_private.h b/rtrlib/transport/ssh/ssh_transport_private.h new file mode 100644 index 0000000..b574863 --- /dev/null +++ b/rtrlib/transport/ssh/ssh_transport_private.h @@ -0,0 +1,13 @@ +/* + * 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/ + */ + +#ifndef SSH_TRANSPORT_PRIVATE_H +#define SSH_TRANSPORT_PRIVATE_H +#include "ssh_transport.h" +#endif 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 <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#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 +/** @} */ diff --git a/rtrlib/transport/transport.c b/rtrlib/transport/transport.c new file mode 100644 index 0000000..d1bdfc5 --- /dev/null +++ b/rtrlib/transport/transport.c @@ -0,0 +1,87 @@ +/* + * 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 "transport_private.h" + +#include "rtrlib/lib/utils_private.h" + +inline int tr_open(struct tr_socket *socket) +{ + return socket->open_fp(socket->socket); +} + +inline void tr_close(struct tr_socket *socket) +{ + socket->close_fp(socket->socket); +} + +inline void tr_free(struct tr_socket *socket) +{ + socket->free_fp(socket); +} + +inline int tr_send(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout) +{ + return socket->send_fp(socket->socket, pdu, len, timeout); +} + +inline int tr_recv(const struct tr_socket *socket, void *buf, const size_t len, const time_t timeout) +{ + return socket->recv_fp(socket->socket, buf, len, timeout); +} + +/* cppcheck-suppress unusedFunction */ +inline const char *tr_ident(struct tr_socket *sock) +{ + return sock->ident_fp(sock->socket); +} + +int tr_send_all(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout) +{ + unsigned int total_send = 0; + time_t end_time; + + lrtr_get_monotonic_time(&end_time); + end_time = end_time + timeout; + + while (total_send < len) { + time_t cur_time; + int rtval; + + lrtr_get_monotonic_time(&cur_time); + + rtval = tr_send(socket, ((char *)pdu) + total_send, (len - total_send), (end_time - cur_time)); + if (rtval < 0) + return rtval; + total_send += rtval; + } + return total_send; +} + +int tr_recv_all(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout) +{ + size_t total_recv = 0; + time_t end_time; + + lrtr_get_monotonic_time(&end_time); + end_time += timeout; + + while (total_recv < len) { + time_t cur_time; + int rtval; + + lrtr_get_monotonic_time(&cur_time); + + rtval = tr_recv(socket, ((char *)pdu) + total_recv, (len - total_recv), end_time - cur_time); + if (rtval < 0) + return rtval; + total_recv += rtval; + } + return total_recv; +} diff --git a/rtrlib/transport/transport.h b/rtrlib/transport/transport.h new file mode 100644 index 0000000..c092a2f --- /dev/null +++ b/rtrlib/transport/transport.h @@ -0,0 +1,113 @@ +/* + * 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_transport_h Transport sockets + * @brief The RTR transport sockets implement the communication channel + * (e.g., SSH, TCP, TCP-AO) between an RTR server and client. + * @details Before using the transport socket, a tr_socket must be + * initialized based on a protocol-dependent init function (e.g., + * tr_tcp_init()).\n + * The tr_* functions call the corresponding function pointers, which are + * passed in the tr_socket struct, and forward the remaining arguments. + * + * @{ + */ + +#ifndef RTR_TRANSPORT_H +#define RTR_TRANSPORT_H + +#include <time.h> + +/** + * @brief Default connect timeout + */ +#define RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT 30 + +/** + * @brief The return values for tr_ functions. + */ +enum tr_rtvals { + /** @brief Operation was successful. */ + TR_SUCCESS = 0, + + /** Error occurred. */ + TR_ERROR = -1, + + /** No data is available on the socket. */ + TR_WOULDBLOCK = -2, + + /** Call was interrupted from a signal */ + TR_INTR = -3, + + /** Connection closed */ + TR_CLOSED = -4 +}; + +struct tr_socket; + +/** + * @brief A function pointer to a technology specific close function. + * \sa tr_close + */ +typedef void (*tr_close_fp)(void *socket); + +/** + * @brief A function pointer to a technology specific open function. + * \sa tr_open + */ +typedef int (*tr_open_fp)(void *socket); + +/** + * @brief A function pointer to a technology specific free function. + * All memory associated with the tr_socket will be freed. + * \sa tr_free + */ +typedef void (*tr_free_fp)(struct tr_socket *tr_sock); + +/** + * @brief A function pointer to a technology specific recv function. + * \sa tr_recv + */ +typedef int (*tr_recv_fp)(const void *socket, void *pdu, const size_t len, const time_t timeout); + +/** + * @brief A function pointer to a technology specific send function. + * \sa tr_send + */ +typedef int (*tr_send_fp)(const void *socket, const void *pdu, const size_t len, const time_t timeout); + +/** + * @brief A function pointer to a technology specific info function. + * \sa tr_send + */ +typedef const char *(*tr_ident_fp)(void *socket); + +/** + * @brief A transport socket datastructure. + * + * @param socket A pointer to a technology specific socket. + * @param open_fp Pointer to a function that establishes the socket connection. + * @param close_fp Pointer to a function that closes the socket. + * @param free_fp Pointer to a function that frees all memory allocated with this socket. + * @param send_fp Pointer to a function that sends data through this socket. + * @param recv_fp Pointer to a function that receives data from this socket. + */ +struct tr_socket { + void *socket; + tr_open_fp open_fp; + tr_close_fp close_fp; + tr_free_fp free_fp; + tr_send_fp send_fp; + tr_recv_fp recv_fp; + tr_ident_fp ident_fp; +}; + +#endif +/** @} */ diff --git a/rtrlib/transport/transport_private.h b/rtrlib/transport/transport_private.h new file mode 100644 index 0000000..7fc2e19 --- /dev/null +++ b/rtrlib/transport/transport_private.h @@ -0,0 +1,107 @@ +/* + * 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_transport_h Transport sockets + * @brief The RTR transport sockets implement the communication channel + * (e.g., SSH, TCP, TCP-AO) between an RTR server and client. + * @details Before using the transport socket, a tr_socket must be + * initialized based on a protocol-dependent init function (e.g., + * tr_tcp_init()).\n + * The tr_* functions call the corresponding function pointers, which are + * passed in the tr_socket struct, and forward the remaining arguments. + * + * @{ + */ + +#ifndef RTR_TRANSPORT_PRIVATE_H +#define RTR_TRANSPORT_PRIVATE_H + +#include "transport.h" + +#include <time.h> + +/** + * @brief Establish the connection. + * @param[in] socket Socket that will be used. + * @return TR_SUCCESS On success. + * @return TR_ERROR On error. + */ +int tr_open(struct tr_socket *socket); + +/** + * @brief Close the socket connection. + * @param[in] socket Socket that will be closed. + */ +void tr_close(struct tr_socket *socket); + +/** + * @brief Deallocates all memory that the passed socket uses. + * Socket have to be closed before. + * @param[in] socket which will be freed. + */ +void tr_free(struct tr_socket *socket); + +/** + * @brief Receives <= len Bytes data from the socket. + * @param[in] socket Socket that will be used. + * @param[out] buf Received data, must be an allocated memory area of >=pdu_len bytes. + * @param[in] len Size of pdu in Bytes. + * @param[in] timeout Max. seconds the function will block till len data was received. + * @return >0 Number of Bytes read. + * @return TR_ERROR On error. + * @return TR_WOULDBLOCK If no data was available at the socket before the timeout expired. + */ +int tr_recv(const struct tr_socket *socket, void *buf, const size_t len, const time_t timeout); + +/** + * @brief Send <= len Bytes data over the socket. + * @param[in] socket Socket that will be used. + * @param[out] pdu Data that will be be sent. + * @param[in] len Size of pdu in Bytes. + * @param[in] timeout Max. seconds the function should try to send the data till it returns. + * @return >0 Number of Bytes sent. + * @return TR_ERROR On error. + */ +int tr_send(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout); + +/** + * Repeatedly calls tr_send(..) till len Bytes were sent, the timeout expired or an error occurred. + * @param[in] socket Socket that will be used. + * @param[out] pdu Data that will be be sent. + * @param[in] len Size of pdu in Bytes. + * @param[in] timeout Max. seconds the functions should try to send pdu till it returns. + * @return >0 Number of Bytes sent. + * @return TR_ERROR On Error. + * @return TR_WOULDBLOCK If send would block. + */ +int tr_send_all(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout); + +/** + * Repeatedly calls tr_recv(..) till len Bytes were received, the timeout expired or an error occurred. + * @param[in] socket Socket that will be used. + * @param[out] buf Received data, must be an allocated memory area of >=len bytes. + * @param[in] len Size of pdu in Bytes. + * @param[in] timeout Max. seconds the functions should try to receive len data till it returns. + * @return >0 Number of Bytes received. + * @return TR_ERROR On error. + * @return TR_WOULDBLOCK If send would block. + */ +int tr_recv_all(const struct tr_socket *socket, const void *buf, const size_t len, const time_t timeout); + +/** + * Returns an identifier for the socket endpoint, eg host:port. + * @param[in] socket + * return Pointer to a \0 terminated String + * return NULL on error + */ +const char *tr_ident(struct tr_socket *socket); + +#endif +/** @} */ |