From f90f421b165c7880cd88db8795f00073dd768f60 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sat, 13 Mar 2021 17:10:08 +0100 Subject: Add libs --- src/libosmocc/socket.c | 583 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 583 insertions(+) create mode 100644 src/libosmocc/socket.c (limited to 'src/libosmocc/socket.c') diff --git a/src/libosmocc/socket.c b/src/libosmocc/socket.c new file mode 100644 index 0000000..d25e1f1 --- /dev/null +++ b/src/libosmocc/socket.c @@ -0,0 +1,583 @@ +/* Osmo-CC: Socket handling + * + * (C) 2016 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "../libtimer/timer.h" +#include "message.h" +#include "cause.h" +#include "socket.h" + +static const char version_string[] = OSMO_CC_VERSION; + +static int _getaddrinfo(const char *host, uint16_t port, struct addrinfo **result) +{ + char portstr[8]; + struct addrinfo hints; + int rc; + + sprintf(portstr, "%d", port); + + /* bind socket */ + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + hints.ai_protocol = 0; + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + + rc = getaddrinfo(host, portstr, &hints, result); + if (rc < 0) { + PDEBUG(DCC, DEBUG_ERROR, "Failed to create socket for host '%s', port '%d': %s.\n", host, port, gai_strerror(rc)); + return rc; + } + return rc; +} + +/* send a reject message toward CC process. + * the CC process will change the reject message to a release message when not in INIT_IN state + */ +static void rej_msg(osmo_cc_socket_t *os, uint32_t callref, uint8_t socket_cause, uint8_t isdn_cause, uint16_t sip_cause) +{ + osmo_cc_msg_t *msg; + + /* create message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_REQ); + if (!msg) + abort(); + + /* add cause */ + osmo_cc_add_ie_cause(msg, os->location, isdn_cause, sip_cause, socket_cause); + osmo_cc_convert_cause_msg(msg); + + /* message down */ + os->recv_msg_cb(os->priv, callref, msg); +} + +void tx_keepalive_timeout(struct timer *timer) +{ + osmo_cc_conn_t *conn = (osmo_cc_conn_t *)timer->priv; + osmo_cc_msg_t *msg; + + /* send keepalive message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_DUMMY_REQ); + osmo_cc_msg_list_enqueue(&conn->os->write_list, msg, conn->callref); + timer_start(&conn->tx_keepalive_timer, OSMO_CC_SOCKET_TX_KEEPALIVE); +} + +static void close_conn(osmo_cc_conn_t *conn, uint8_t socket_cause); + +void rx_keepalive_timeout(struct timer *timer) +{ + osmo_cc_conn_t *conn = (osmo_cc_conn_t *)timer->priv; + + PDEBUG(DCC, DEBUG_ERROR, "OsmoCC-Socket failed due to timeout.\n"); + close_conn(conn, OSMO_CC_SOCKET_CAUSE_TIMEOUT); +} + +/* create socket process and bind socket */ +int osmo_cc_open_socket(osmo_cc_socket_t *os, const char *host, uint16_t port, void *priv, void (*recv_msg_cb)(void *priv, uint32_t callref, osmo_cc_msg_t *msg), uint8_t location) +{ + int try = 0, auto_port = 0; + struct addrinfo *result, *rp; + int rc, sock, flags; + + memset(os, 0, sizeof(*os)); + +try_again: + /* check for given port, if NULL, autoselect port */ + if (!port || auto_port) { + port = OSMO_CC_DEFAULT_PORT + try; + try++; + auto_port = 1; + } + + PDEBUG(DCC, DEBUG_DEBUG, "Create socket for host %s port %d.\n", host, port); + + rc = _getaddrinfo(host, port, &result); + if (rc < 0) + return rc; + for (rp = result; rp; rp = rp->ai_next) { + int on = 1; + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sock < 0) + continue; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (unsigned char *)&on, sizeof(on)); + rc = bind(sock, rp->ai_addr, rp->ai_addrlen); + if (rc == 0) + break; + close(sock); + } + freeaddrinfo(result); + if (rp == NULL) { + if (auto_port && port < OSMO_CC_DEFAULT_PORT_MAX) { + PDEBUG(DCC, DEBUG_DEBUG, "Failed to bind host %s port %d, trying again.\n", host, port); + goto try_again; + } + PDEBUG(DCC, DEBUG_ERROR, "Failed to bind given host %s port %d.\n", host, port); + return -EIO; + } + + /* listen to socket */ + rc = listen(sock, 10); + if (rc < 0) { + PDEBUG(DCC, DEBUG_ERROR, "Failed to listen on socket.\n"); + return rc; + } + + /* set nonblocking io */ + flags = fcntl(sock, F_GETFL); + flags |= O_NONBLOCK; + fcntl(sock, F_SETFL, flags); + + os->socket = sock; + os->recv_msg_cb = recv_msg_cb; + os->priv = priv; + os->location = location; + + return port; +} + +/* create a connection */ +static osmo_cc_conn_t *open_conn(osmo_cc_socket_t *os, int sock, uint32_t callref, int read_setup) +{ + osmo_cc_conn_t *conn, **connp; + + /* create connection */ + conn = calloc(1, sizeof(*conn)); + if (!conn) { + PDEBUG(DCC, DEBUG_ERROR, "No mem!\n"); + abort(); + } + conn->os = os; + conn->socket = sock; + conn->read_version = 1; + conn->write_version = 1; + conn->read_setup = read_setup; + if (callref) + conn->callref = callref; + else + conn->callref = osmo_cc_new_callref(); + + timer_init(&conn->tx_keepalive_timer, tx_keepalive_timeout, conn); + timer_init(&conn->rx_keepalive_timer, rx_keepalive_timeout, conn); + timer_start(&conn->tx_keepalive_timer, OSMO_CC_SOCKET_TX_KEEPALIVE); + timer_start(&conn->rx_keepalive_timer, OSMO_CC_SOCKET_RX_KEEPALIVE); + + PDEBUG(DCC, DEBUG_DEBUG, "New socket connection (callref %d).\n", conn->callref); + + /* attach to list */ + connp = &os->conn_list; + while (*connp) + connp = &((*connp)->next); + *connp = conn; + + return conn; +} + +/* remove a connection */ +static void close_conn(osmo_cc_conn_t *conn, uint8_t socket_cause) +{ + osmo_cc_conn_t **connp; + osmo_cc_msg_list_t *ml; + + /* detach connection first, to prevent a destruction during message handling (double free) */ + connp = &conn->os->conn_list; + while (*connp != conn) + connp = &((*connp)->next); + *connp = conn->next; + /* send reject message, if socket_cause is set */ + if (socket_cause && !conn->read_setup) { + /* receive a release or reject (depending on state), but only if we sent a setup */ + rej_msg(conn->os, conn->callref, socket_cause, 0, 0); + } + + PDEBUG(DCC, DEBUG_DEBUG, "Destroy socket connection (callref %d).\n", conn->callref); + + /* close socket */ + if (conn->socket) + close(conn->socket); + /* free partly received message */ + if (conn->read_msg) + osmo_cc_free_msg(conn->read_msg); + /* free send queue */ + while ((ml = conn->write_list)) { + osmo_cc_free_msg(ml->msg); + conn->write_list = ml->next; + free(ml); + } + /* free timers */ + timer_exit(&conn->tx_keepalive_timer); + timer_exit(&conn->rx_keepalive_timer); + /* free connection (already detached above) */ + free(conn); +} + +/* close socket and remove */ +void osmo_cc_close_socket(osmo_cc_socket_t *os) +{ + osmo_cc_msg_list_t *ml; + + PDEBUG(DCC, DEBUG_DEBUG, "Destroy socket.\n"); + + /* free all connections */ + while (os->conn_list) + close_conn(os->conn_list, 0); + /* close socket */ + if (os->socket > 0) { + close(os->socket); + os->socket = 0; + } + /* free send queue */ + while ((ml = os->write_list)) { + osmo_cc_free_msg(ml->msg); + os->write_list = ml->next; + free(ml); + } +} + +/* send message to send_queue of sock instance */ +int osmo_cc_sock_send_msg(osmo_cc_socket_t *os, uint32_t callref, osmo_cc_msg_t *msg, const char *host, uint16_t port) +{ + osmo_cc_msg_list_t *ml; + + /* turn _IND into _REQ and _CNF into _RSP */ + msg->type &= ~1; + + /* create list entry */ + ml = osmo_cc_msg_list_enqueue(&os->write_list, msg, callref); + if (host) + strncpy(ml->host, host, sizeof(ml->host) - 1); + ml->port = port; + + return 0; +} + +/* receive message + * return 1 if work was done. + */ +static int receive_conn(osmo_cc_conn_t *conn) +{ + uint8_t socket_cause = OSMO_CC_SOCKET_CAUSE_BROKEN_PIPE; + int rc; + osmo_cc_msg_t *msg; + uint8_t msg_type; + int len; + int work = 0; + + /* get version from remote */ + if (conn->read_version) { + rc = recv(conn->socket, conn->read_version_string + conn->read_version_pos, strlen(version_string) - conn->read_version_pos, 0); + if (rc < 0 && errno == EAGAIN) + return work; + work = 1; + if (rc <= 0) { + goto close; + } + conn->read_version_pos += rc; + if (conn->read_version_pos == strlen(version_string)) { + conn->read_version = 0; + if (!!memcmp(conn->read_version_string, version_string, strlen(version_string) - 1)) { + PDEBUG(DCC, DEBUG_NOTICE, "Remote does not seem to be an Osmo-CC socket, rejecting!\n"); + socket_cause = OSMO_CC_SOCKET_CAUSE_FAILED; + goto close; + } + if (conn->read_version_string[strlen(version_string) - 1] != version_string[strlen(version_string) - 1]) { + PDEBUG(DCC, DEBUG_NOTICE, "Remote Osmo-CC socket has wrong version (local=%s, remote=%s), rejecting!\n", version_string, conn->read_version_string); + socket_cause = OSMO_CC_SOCKET_CAUSE_VERSION_MISMATCH; + goto close; + } + } else + return work; + } + +try_next_message: + /* read message header from remote */ + if (!conn->read_msg) { + rc = recv(conn->socket, ((uint8_t *)&conn->read_hdr) + conn->read_pos, sizeof(conn->read_hdr) - conn->read_pos, 0); + if (rc < 0 && errno == EAGAIN) + return work; + work = 1; + if (rc <= 0) { + goto close; + } + conn->read_pos += rc; + if (conn->read_pos == sizeof(conn->read_hdr)) { + conn->read_msg = osmo_cc_new_msg(conn->read_hdr.type); + if (!conn->read_msg) + abort(); + conn->read_msg->length_networkorder = conn->read_hdr.length_networkorder; + /* prepare for reading message */ + conn->read_pos = 0; + } else + return work; + } + + /* read message data from remote */ + msg = conn->read_msg; + len = ntohs(msg->length_networkorder); + if (len == 0) + goto empty_message; + rc = recv(conn->socket, msg->data + conn->read_pos, len - conn->read_pos, 0); + if (rc < 0 && errno == EAGAIN) + return work; + work = 1; + if (rc <= 0) { + goto close; + } + conn->read_pos += rc; + if (conn->read_pos == len) { +empty_message: + /* start RX keepalive timeer, if not already */ + timer_start(&conn->rx_keepalive_timer, OSMO_CC_SOCKET_RX_KEEPALIVE); + /* we got our setup message, so we clear the flag */ + conn->read_setup = 0; + /* prepare for reading header */ + conn->read_pos = 0; + /* detach message first, because the connection might be destroyed during message handling */ + msg_type = conn->read_msg->type; + conn->read_msg = NULL; + /* drop dummy or forward message */ + if (msg_type == OSMO_CC_MSG_DUMMY_REQ) + osmo_cc_free_msg(msg); + else + conn->os->recv_msg_cb(conn->os->priv, conn->callref, msg); + if (msg_type == OSMO_CC_MSG_REL_REQ || msg_type == OSMO_CC_MSG_REJ_REQ) { + PDEBUG(DCC, DEBUG_DEBUG, "closing socket because we received a release or reject message.\n"); + close_conn(conn, 0); + return 1; /* conn removed */ + } + goto try_next_message; + } + return work; + +close: + PDEBUG(DCC, DEBUG_ERROR, "OsmoCC-Socket failed, socket cause %d.\n", socket_cause); + close_conn(conn, socket_cause); + return work; /* conn removed */ +} + +/* transmit message + * return 1 if work was done. + */ +static int transmit_conn(osmo_cc_conn_t *conn) +{ + uint8_t socket_cause = OSMO_CC_SOCKET_CAUSE_BROKEN_PIPE; + int rc; + osmo_cc_msg_t *msg; + int len; + osmo_cc_msg_list_t *ml; + int work = 0; + + /* send socket version to remote */ + if (conn->write_version) { + rc = write(conn->socket, version_string, strlen(version_string)); + if (rc < 0 && errno == EAGAIN) + return work; + work = 1; + if (rc <= 0) { + goto close; + } + if (rc != strlen(version_string)) { + PDEBUG(DCC, DEBUG_ERROR, "short write, please fix handling!\n"); + abort(); + } + conn->write_version = 0; + } + + /* send message to remote */ + while (conn->write_list) { + timer_stop(&conn->tx_keepalive_timer); + msg = conn->write_list->msg; + len = sizeof(*msg) + ntohs(msg->length_networkorder); + rc = write(conn->socket, msg, len); + if (rc < 0 && errno == EAGAIN) + return work; + work = 1; + if (rc <= 0) { + goto close; + } + if (rc != len) { + PDEBUG(DCC, DEBUG_ERROR, "short write, please fix handling!\n"); + abort(); + } + /* close socket after sending release/reject message */ + if (msg->type == OSMO_CC_MSG_REL_REQ || msg->type == OSMO_CC_MSG_REJ_REQ) { + PDEBUG(DCC, DEBUG_DEBUG, "closing socket because we sent a release or reject message.\n"); + close_conn(conn, 0); + return work; /* conn removed */ + } + /* free message after sending */ + ml = conn->write_list; + conn->write_list = ml->next; + osmo_cc_free_msg(msg); + free(ml); + } + + /* start TX keepalive timeer, if not already + * because we stop at every message above, we actually restart the timer here. + * only if there is no message for the amount of time, the timer fires. + */ + if (!timer_running(&conn->tx_keepalive_timer)) + timer_start(&conn->tx_keepalive_timer, OSMO_CC_SOCKET_TX_KEEPALIVE); + + return work; + +close: + PDEBUG(DCC, DEBUG_NOTICE, "OsmoCC-Socket failed.\n"); + close_conn(conn, socket_cause); + return work; /* conn removed */ +} + +/* handle all sockets of a socket interface + * return 1 if work was done. + */ +int osmo_cc_handle_socket(osmo_cc_socket_t *os) +{ + struct sockaddr_storage sa; + socklen_t slen = sizeof(sa); + int sock; + osmo_cc_conn_t *conn; + osmo_cc_msg_list_t *ml, **mlp; + int flags; + struct addrinfo *result, *rp; + int rc; + int work = 0; + + /* handle messages in send queue */ + while ((ml = os->write_list)) { + work = 1; + /* detach list entry */ + os->write_list = ml->next; + ml->next = NULL; + /* search for socket connection */ + for (conn = os->conn_list; conn; conn=conn->next) { + if (conn->callref == ml->callref) + break; + } + if (conn) { + /* attach to list */ + mlp = &conn->write_list; + while (*mlp) + mlp = &((*mlp)->next); + *mlp = ml; + /* done with message */ + continue; + } + + /* reject and release are ignored */ + if (ml->msg->type == OSMO_CC_MSG_REJ_REQ + || ml->msg->type == OSMO_CC_MSG_REL_REQ) { + /* drop message */ + osmo_cc_free_msg(ml->msg); + free(ml); + /* done with message */ + continue; + } + + /* reject, if this is not a setup message */ + if (ml->msg->type != OSMO_CC_MSG_SETUP_REQ + && ml->msg->type != OSMO_CC_MSG_ATTACH_REQ) { + PDEBUG(DCC, DEBUG_ERROR, "Message with unknown callref.\n"); + rej_msg(os, ml->callref, 0, OSMO_CC_ISDN_CAUSE_INVAL_CALLREF, 0); + /* drop message */ + osmo_cc_free_msg(ml->msg); + free(ml); + /* done with message */ + continue; + } + /* connect to remote */ + rc = _getaddrinfo(ml->host, ml->port, &result); + if (rc < 0) { + rej_msg(os, ml->callref, OSMO_CC_SOCKET_CAUSE_FAILED, 0, 0); + /* drop message */ + osmo_cc_free_msg(ml->msg); + free(ml); + /* done with message */ + continue; + } + for (rp = result; rp; rp = rp->ai_next) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sock < 0) + continue; + /* set nonblocking io */ + flags = fcntl(sock, F_GETFL); + flags |= O_NONBLOCK; + fcntl(sock, F_SETFL, flags); + /* connect */ + rc = connect(sock, rp->ai_addr, rp->ai_addrlen); + if (rc == 0 || errno == EINPROGRESS) + break; + close(sock); + } + freeaddrinfo(result); + if (rp == NULL) { + PDEBUG(DCC, DEBUG_ERROR, "Failed to connect to given host %s port %d.\n", ml->host, ml->port); + rej_msg(os, ml->callref, OSMO_CC_SOCKET_CAUSE_FAILED, 0, 0); + /* drop message */ + osmo_cc_free_msg(ml->msg); + free(ml); + /* done with message */ + continue; + } + /* create connection */ + conn = open_conn(os, sock, ml->callref, 0); + /* attach to list */ + conn->write_list = ml; + /* done with (setup) message */ + } + + /* handle new socket connection */ + while ((sock = accept(os->socket, (struct sockaddr *)&sa, &slen)) > 0) { + work = 1; + /* set nonblocking io */ + flags = fcntl(sock, F_GETFL); + flags |= O_NONBLOCK; + fcntl(sock, F_SETFL, flags); + /* create connection */ + open_conn(os, sock, 0, 1); + } + + /* start with list after each read/write, because while handling (the message), one or more connections may be destroyed */ + for (conn = os->conn_list; conn; conn=conn->next) { + /* check for rx */ + work = receive_conn(conn); + /* if "change" is set, connection list might have changed, so we restart processing the list */ + if (work) + break; + /* check for tx */ + work = transmit_conn(conn); + /* if "change" is set, connection list might have changed, so we restart processing the list */ + if (work) + break; + } + + return work; +} + -- cgit v1.2.3