summaryrefslogtreecommitdiffstats
path: root/rtrlib/transport
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:54:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:54:46 +0000
commitcd7b005519ade8ab6c97fcb21590b71b7d1be6e3 (patch)
treec611a8d0cd5e8f68f41b8c2d16ba580e0f40a38d /rtrlib/transport
parentInitial commit. (diff)
downloadlibrtr-cd7b005519ade8ab6c97fcb21590b71b7d1be6e3.tar.xz
librtr-cd7b005519ade8ab6c97fcb21590b71b7d1be6e3.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.c392
-rw-r--r--rtrlib/transport/ssh/ssh_transport.h74
-rw-r--r--rtrlib/transport/ssh/ssh_transport_private.h13
-rw-r--r--rtrlib/transport/tcp/tcp_transport.c368
-rw-r--r--rtrlib/transport/tcp/tcp_transport.h59
-rw-r--r--rtrlib/transport/tcp/tcp_transport_private.h23
-rw-r--r--rtrlib/transport/transport.c87
-rw-r--r--rtrlib/transport/transport.h113
-rw-r--r--rtrlib/transport/transport_private.h107
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
+/** @} */