aboutsummaryrefslogtreecommitdiffstats
path: root/sigtran/sua.c
diff options
context:
space:
mode:
Diffstat (limited to 'sigtran/sua.c')
-rw-r--r--sigtran/sua.c1431
1 files changed, 1431 insertions, 0 deletions
diff --git a/sigtran/sua.c b/sigtran/sua.c
new file mode 100644
index 0000000..e7326cb
--- /dev/null
+++ b/sigtran/sua.c
@@ -0,0 +1,1431 @@
+/* Minimal implementation of RFC 3868 - SCCP User Adaptation Layer */
+
+/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/timer.h>
+
+#include <osmocom/netif/stream.h>
+#include <osmocom/sigtran/xua_msg.h>
+
+#include "sccp_sap.h"
+#include "proto_sua.h"
+#include "sua.h"
+
+#define SUA_MSGB_SIZE 1500
+
+/* Appendix C.4 of Q.714 (all in milliseconds) */
+#define CONNECTION_TIMER ( 1 * 60 * 100)
+#define TX_INACT_TIMER ( 7 * 60 * 100) /* RFC 3868 Ch. 8. */
+#define RX_INACT_TIMER (15 * 60 * 100) /* RFC 3868 Ch. 8. */
+#define RELEASE_TIMER ( 10 * 100)
+#define RELEASE_REP_TIMER ( 10 * 100)
+#define INT_TIMER ( 1 * 60 * 100)
+#define GUARD_TIMER (23 * 60 * 100)
+#define RESET_TIMER ( 10 * 100)
+
+static int DSUA = -1;
+
+struct osmo_sua_user {
+ /* global list of SUA users? */
+ struct llist_head list;
+ /* set if we are a server */
+ struct osmo_stream_srv_link *server;
+ struct osmo_stream_cli *client;
+ struct llist_head links;
+ /* user call-back function in case of incoming primitives */
+ osmo_prim_cb prim_cb;
+};
+
+struct sua_link {
+ /* list of SUA links per sua_user */
+ struct llist_head list;
+ /* sua user to which we belong */
+ struct osmo_sua_user *user;
+ /* local list of (SCCP) connections in this link */
+ struct llist_head connections;
+ /* next connection local reference */
+ uint32_t next_id;
+ int is_server;
+ void *data;
+};
+
+enum sua_connection_state {
+ S_IDLE,
+ S_CONN_PEND_IN,
+ S_CONN_PEND_OUT,
+ S_ACTIVE,
+ S_DISCONN_PEND,
+ S_RESET_IN,
+ S_RESET_OUT,
+ S_BOTHWAY_RESET,
+ S_WAIT_CONN_CONF,
+};
+
+static const struct value_string conn_state_names[] = {
+ { S_IDLE, "IDLE" },
+ { S_CONN_PEND_IN, "CONN_PEND_IN" },
+ { S_CONN_PEND_OUT, "CONN_PEND_OUT" },
+ { S_ACTIVE, "ACTIVE" },
+ { S_DISCONN_PEND, "DISCONN_PEND" },
+ { S_RESET_IN, "RESET_IN" },
+ { S_RESET_OUT, "RESET_OUT" },
+ { S_BOTHWAY_RESET, "BOTHWAY_RESET" },
+ { S_WAIT_CONN_CONF, "WAIT_CONN_CONF" },
+ { 0, NULL }
+};
+
+struct sua_connection {
+ struct llist_head list;
+ struct sua_link *link;
+ struct osmo_sccp_addr calling_addr;
+ struct osmo_sccp_addr called_addr;
+ uint32_t conn_id;
+ uint32_t remote_ref;
+ enum sua_connection_state state;
+ struct osmo_timer_list timer;
+ /* inactivity timers */
+ struct osmo_timer_list tias;
+ struct osmo_timer_list tiar;
+};
+
+
+/***********************************************************************
+ * Message encoding helper functions
+ ***********************************************************************/
+
+#define XUA_HDR(class, type) ((struct xua_common_hdr) { .spare = 0, .msg_class = (class), .msg_type = (type) })
+
+static int msgb_t16l16vp_put(struct msgb *msg, uint16_t tag, uint16_t len, const uint8_t *data)
+{
+ uint8_t *cur;
+ unsigned int rest;
+ unsigned int tlv_len = 4 + len + (4 - (len % 4));
+
+ if (msgb_tailroom(msg) < tlv_len)
+ return -ENOMEM;
+
+ /* tag */
+ msgb_put_u16(msg, tag);
+ /* length */
+ msgb_put_u16(msg, len + 4);
+ /* value */
+ cur = msgb_put(msg, len);
+ memcpy(cur, data, len);
+ /* padding */
+ rest = (4 - (len % 4)) & 0x3;
+ if (rest > 0) {
+ cur = msgb_put(msg, rest);
+ memset(cur, 0, rest);
+ }
+
+ return 0;
+}
+
+static int msgb_t16l16vp_put_u32(struct msgb *msg, uint16_t tag, uint32_t val)
+{
+ uint32_t val_n = htonl(val);
+
+ return msgb_t16l16vp_put(msg, tag, sizeof(val_n), (uint8_t *)&val_n);
+}
+
+static int xua_msg_add_u32(struct xua_msg *xua, uint16_t iei, uint32_t val)
+{
+ uint32_t val_n = htonl(val);
+ return xua_msg_add_data(xua, iei, sizeof(val_n), (uint8_t *) &val_n);
+}
+
+static uint32_t xua_msg_get_u32(struct xua_msg *xua, uint16_t iei)
+{
+ struct xua_msg_part *part = xua_msg_find_tag(xua, iei);
+ uint32_t rc = 0;
+ if (part)
+ rc = ntohl(*(uint32_t *)part->dat);
+ return rc;
+}
+
+static int xua_msg_add_sccp_addr(struct xua_msg *xua, uint16_t iei, const struct osmo_sccp_addr *addr)
+{
+ struct msgb *tmp = msgb_alloc(128, "SCCP Address");
+ int rc;
+
+ if (!tmp)
+ return -ENOMEM;
+
+ msgb_put_u16(tmp, 2); /* route on SSN + PC */
+ msgb_put_u16(tmp, 7); /* always put all addresses on SCCP side */
+
+ if (addr->presence & OSMO_SCCP_ADDR_T_GT) {
+ /* FIXME */
+ }
+ if (addr->presence & OSMO_SCCP_ADDR_T_PC) {
+ msgb_t16l16vp_put_u32(tmp, SUA_IEI_PC, addr->pc);
+ }
+ if (addr->presence & OSMO_SCCP_ADDR_T_SSN) {
+ msgb_t16l16vp_put_u32(tmp, SUA_IEI_SSN, addr->ssn);
+ }
+ if (addr->presence & OSMO_SCCP_ADDR_T_IPv4) {
+ /* FIXME: IPv4 address */
+ } else if (addr->presence & OSMO_SCCP_ADDR_T_IPv6) {
+ /* FIXME: IPv6 address */
+ }
+ rc = xua_msg_add_data(xua, iei, msgb_length(tmp), tmp->data);
+ msgb_free(tmp);
+
+ return rc;
+}
+
+
+/***********************************************************************
+ * SUA Link and Connection handling
+ ***********************************************************************/
+
+static struct sua_link *sua_link_new(struct osmo_sua_user *user, int is_server)
+{
+ struct sua_link *link;
+
+ link = talloc_zero(user, struct sua_link);
+ if (!link)
+ return NULL;
+
+ link->user = user;
+ link->is_server = is_server;
+ INIT_LLIST_HEAD(&link->connections);
+
+ llist_add_tail(&link->list, &user->links);
+
+ return link;
+}
+
+static void conn_destroy(struct sua_connection *conn);
+
+static void sua_link_destroy(struct sua_link *link)
+{
+ struct sua_connection *conn;
+
+ llist_for_each_entry(conn, &link->connections, list)
+ conn_destroy(conn);
+
+ llist_del(&link->list);
+
+ /* FIXME: do we need to cleanup the sccp link? */
+
+ talloc_free(link);
+}
+
+static int sua_link_send(struct sua_link *link, struct msgb *msg)
+{
+ msgb_sctp_ppid(msg) = SUA_PPID;
+
+ if (link->is_server)
+ osmo_stream_srv_send(link->data, msg);
+ else
+ osmo_stream_cli_send(link->data, msg);
+
+ return 0;
+}
+
+static struct sua_connection *conn_find_by_id(struct sua_link *link, uint32_t id)
+{
+ struct sua_connection *conn;
+
+ llist_for_each_entry(conn, &link->connections, list) {
+ if (conn->conn_id == id)
+ return conn;
+ }
+ return NULL;
+}
+
+static void tx_inact_tmr_cb(void *data)
+{
+ struct sua_connection *conn = data;
+ struct xua_msg *xua = xua_msg_alloc();
+ struct msgb *outmsg;
+
+ /* encode + send the CLDT */
+ xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COIT);
+ xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, 0); /* FIXME */
+ xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, 2);
+ xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id);
+ xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &conn->called_addr);
+ /* optional: sequence number; credit (both class 3 only) */
+
+ outmsg = xua_to_msg(1, xua);
+ xua_msg_free(xua);
+
+ sua_link_send(conn->link, outmsg);
+}
+
+static void rx_inact_tmr_cb(void *data)
+{
+ struct sua_connection *conn = data;
+
+ /* FIXME: release connection */
+ /* Send N-DISCONNECT.ind to local user */
+ /* Send RLSD to peer */
+ /* enter disconnect pending state with release timer pending */
+}
+
+
+static struct sua_connection *conn_create_id(struct sua_link *link, uint32_t conn_id)
+{
+ struct sua_connection *conn = talloc_zero(link, struct sua_connection);
+
+ conn->conn_id = conn_id;
+ conn->link = link;
+ conn->state = S_IDLE;
+
+ llist_add_tail(&conn->list, &link->connections);
+
+ conn->tias.cb = tx_inact_tmr_cb;
+ conn->tias.data = conn;
+ conn->tiar.cb = rx_inact_tmr_cb;
+ conn->tiar.data = conn;
+
+ return conn;
+}
+
+static struct sua_connection *conn_create(struct sua_link *link)
+{
+ uint32_t conn_id;
+
+ do {
+ conn_id = link->next_id++;
+ } while (conn_find_by_id(link, conn_id));
+
+ return conn_create_id(link, conn_id);
+}
+
+static void conn_destroy(struct sua_connection *conn)
+{
+ /* FIXME: do some cleanup; inform user? */
+ osmo_timer_del(&conn->tias);
+ osmo_timer_del(&conn->tiar);
+ llist_del(&conn->list);
+ talloc_free(conn);
+}
+
+static void conn_state_set(struct sua_connection *conn,
+ enum sua_connection_state state)
+{
+ DEBUGP(DSUA, "(%u) state chg %s->", conn->conn_id,
+ get_value_string(conn_state_names, conn->state));
+ DEBUGPC(DSUA, "%s\n",
+ get_value_string(conn_state_names, state));
+ conn->state = state;
+}
+
+static void conn_restart_tx_inact_timer(struct sua_connection *conn)
+{
+ osmo_timer_schedule(&conn->tias, TX_INACT_TIMER / 100,
+ (TX_INACT_TIMER % 100) * 10);
+}
+
+static void conn_restart_rx_inact_timer(struct sua_connection *conn)
+{
+ osmo_timer_schedule(&conn->tiar, RX_INACT_TIMER / 100,
+ (RX_INACT_TIMER % 100) * 10);
+}
+
+static void conn_start_inact_timers(struct sua_connection *conn)
+{
+ conn_restart_tx_inact_timer(conn);
+ conn_restart_rx_inact_timer(conn);
+}
+
+
+static struct msgb *sua_msgb_alloc(void)
+{
+ return msgb_alloc(SUA_MSGB_SIZE, "SUA Primitive");
+}
+
+
+/***********************************************************************
+ * Handling of messages from the User SAP
+ ***********************************************************************/
+
+/* user program sends us a N-CONNNECT.req to initiate a new connection */
+static int sua_connect_req(struct sua_link *link, struct osmo_scu_prim *prim)
+{
+ struct osmo_scu_connect_param *par = &prim->u.connect;
+ struct xua_msg *xua = xua_msg_alloc();
+ struct sua_connection *conn;
+ struct msgb *outmsg;
+
+ if (par->sccp_class != 2) {
+ LOGP(DSUA, LOGL_ERROR, "N-CONNECT.req for unsupported "
+ "SCCP class %u\n", par->sccp_class);
+ /* FIXME: Send primitive to user */
+ return -EINVAL;
+ }
+
+ conn = conn_create_id(link, par->conn_id);
+ if (!conn) {
+ /* FIXME: Send primitive to user */
+ return -EINVAL;
+ }
+
+ memcpy(&conn->called_addr, &par->called_addr,
+ sizeof(conn->called_addr));
+ memcpy(&conn->calling_addr, &par->calling_addr,
+ sizeof(conn->calling_addr));
+
+ /* encode + send the CLDT */
+ xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CORE);
+ xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, 0); /* FIXME */
+ xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, par->sccp_class);
+ xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id);
+ xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &par->called_addr);
+ xua_msg_add_u32(xua, SUA_IEI_SEQ_CTRL, 0); /* FIXME */
+ /* sequence number */
+ if (par->calling_addr.presence)
+ xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &par->calling_addr);
+ /* optional: hop count; importance; priority; credit */
+ if (msgb_l2(prim->oph.msg))
+ xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg),
+ msgb_l2(prim->oph.msg));
+
+ outmsg = xua_to_msg(1, xua);
+ xua_msg_free(xua);
+
+ /* FIXME: Start CONNECTION_TIMER */
+ conn_state_set(conn, S_CONN_PEND_OUT);
+
+ return sua_link_send(link, outmsg);
+}
+
+/* user program sends us a N-CONNNECT.resp, presumably against a
+ * N-CONNECT.ind */
+static int sua_connect_resp(struct sua_link *link, struct osmo_scu_prim *prim)
+{
+ struct osmo_scu_connect_param *par = &prim->u.connect;
+ struct xua_msg *xua = xua_msg_alloc();
+ struct sua_connection *conn;
+ struct msgb *outmsg;
+
+ /* check if we already know a connection for this conn_id */
+ conn = conn_find_by_id(link, par->conn_id);
+ if (!conn) {
+ LOGP(DSUA, LOGL_ERROR, "N-CONNECT.resp for unknown "
+ "connection ID %u\n", par->conn_id);
+ /* FIXME: Send primitive to user */
+ return -ENODEV;
+ }
+
+ if (conn->state != S_CONN_PEND_IN) {
+ LOGP(DSUA, LOGL_ERROR, "N-CONNECT.resp in wrong state %s\n",
+ get_value_string(conn_state_names, conn->state));
+ /* FIXME: Send primitive to user */
+ return -EINVAL;
+ }
+
+ /* encode + send the COAK message */
+ xua = xua_msg_alloc();
+ xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COAK);
+ xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, 0); /* FIXME */
+ xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, par->sccp_class);
+ xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref);
+ xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id);
+ xua_msg_add_u32(xua, SUA_IEI_SEQ_CTRL, 0); /* FIXME */
+ /* sequence number */
+ if (par->calling_addr.presence)
+ xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &par->calling_addr);
+ /* optional: hop count; importance; priority */
+ /* FIXME: destination address will be present in case the CORE
+ * message conveys the source address parameter */
+ if (par->called_addr.presence)
+ xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &par->called_addr);
+ if (msgb_l2(prim->oph.msg))
+ xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg),
+ msgb_l2(prim->oph.msg));
+
+ outmsg = xua_to_msg(1, xua);
+ xua_msg_free(xua);
+
+ conn_state_set(conn, S_ACTIVE);
+ conn_start_inact_timers(conn);
+
+ return sua_link_send(link, outmsg);
+}
+
+/* user wants to send connection-oriented data */
+static int sua_data_req(struct sua_link *link, struct osmo_scu_prim *prim)
+{
+ struct osmo_scu_data_param *par = &prim->u.data;
+ struct xua_msg *xua;
+ struct sua_connection *conn;
+ struct msgb *outmsg;
+
+ /* check if we know about this conncetion, and obtain reference */
+ conn = conn_find_by_id(link, par->conn_id);
+ if (!conn) {
+ LOGP(DSUA, LOGL_ERROR, "N-DATA.req for unknown "
+ "connection ID %u\n", par->conn_id);
+ /* FIXME: Send primitive to user */
+ return -ENODEV;
+ }
+
+ if (conn->state != S_ACTIVE) {
+ LOGP(DSUA, LOGL_ERROR, "N-DATA.req in wrong state %s\n",
+ get_value_string(conn_state_names, conn->state));
+ /* FIXME: Send primitive to user */
+ return -EINVAL;
+ }
+
+ conn_restart_tx_inact_timer(conn);
+
+ /* encode + send the CODT message */
+ xua = xua_msg_alloc();
+ xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CODT);
+ xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, 0); /* FIXME */
+ /* Sequence number only in expedited data */
+ xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref);
+ /* optional: priority; correlation id */
+ xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg),
+ msgb_l2(prim->oph.msg));
+
+ outmsg = xua_to_msg(1, xua);
+ xua_msg_free(xua);
+
+ return sua_link_send(link, outmsg);
+}
+
+/* user wants to disconnect a connection */
+static int sua_disconnect_req(struct sua_link *link, struct osmo_scu_prim *prim)
+{
+ struct osmo_scu_disconn_param *par = &prim->u.disconnect;
+ struct xua_msg *xua;
+ struct sua_connection *conn;
+ struct msgb *outmsg;
+
+ /* resolve reference of connection */
+ conn = conn_find_by_id(link, par->conn_id);
+ if (!conn) {
+ LOGP(DSUA, LOGL_ERROR, "N-DISCONNECT.resp for unknown "
+ "connection ID %u\n", par->conn_id);
+ /* FIXME: Send primitive to user */
+ return -ENODEV;
+ }
+
+ /* encode + send the RELRE */
+ xua = xua_msg_alloc();
+ xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELRE);
+ xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, 0); /* FIXME */
+ xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref);
+ xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id);
+ xua_msg_add_u32(xua, SUA_IEI_CAUSE, par->cause);
+ /* optional: importance */
+ if (msgb_l2(prim->oph.msg))
+ xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg),
+ msgb_l2(prim->oph.msg));
+
+ outmsg = xua_to_msg(1, xua);
+ xua_msg_free(xua);
+
+ conn_state_set(conn, S_DISCONN_PEND);
+
+ return sua_link_send(link, outmsg);
+}
+
+/* user wants to send connectionless data */
+static int sua_unitdata_req(struct sua_link *link, struct osmo_scu_prim *prim)
+{
+ struct osmo_scu_unitdata_param *par = &prim->u.unitdata;
+ struct xua_msg *xua = xua_msg_alloc();
+ struct msgb *outmsg;
+
+ /* encode + send the CLDT */
+ xua->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT);
+ xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, 0); /* FIXME */
+ xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, 0);
+ xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &par->calling_addr);
+ xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &par->called_addr);
+ xua_msg_add_u32(xua, SUA_IEI_SEQ_CTRL, par->in_sequence_control);
+ /* optional: importance, ... correlation id? */
+ xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg),
+ msgb_l2(prim->oph.msg));
+
+ outmsg = xua_to_msg(1, xua);
+ xua_msg_free(xua);
+
+ return sua_link_send(link, outmsg);
+}
+
+/* user hands us a SCCP-USER SAP primitive down into the stack */
+int osmo_osmo_sua_user_link_down(struct sua_link *link, struct osmo_prim_hdr *oph)
+{
+ struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph;
+ struct msgb *msg = prim->oph.msg;
+ int rc = 0;
+
+ LOGP(DSUA, LOGL_DEBUG, "Received SCCP User Primitive (%s)\n",
+ osmo_sccp_prim_name(&prim->oph));
+
+ switch (OSMO_PRIM_HDR(&prim->oph)) {
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST):
+ rc = sua_connect_req(link, prim);
+ break;
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_RESPONSE):
+ rc = sua_connect_resp(link, prim);
+ break;
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST):
+ rc = sua_data_req(link, prim);
+ break;
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_REQUEST):
+ rc = sua_disconnect_req(link, prim);
+ break;
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_REQUEST):
+ rc = sua_unitdata_req(link, prim);
+ break;
+ default:
+ rc = -1;
+ }
+
+ if (rc != 1)
+ msgb_free(msg);
+
+ return rc;
+}
+
+
+/***********************************************************************
+ * Mandatory IE checking
+ ***********************************************************************/
+
+#define MAND_IES(msgt, ies) [msgt] = (ies)
+
+static const uint16_t cldt_mand_ies[] = {
+ SUA_IEI_ROUTE_CTX, SUA_IEI_PROTO_CLASS, SUA_IEI_SRC_ADDR,
+ SUA_IEI_DEST_ADDR, SUA_IEI_SEQ_CTRL, SUA_IEI_DATA, 0
+};
+
+static const uint16_t cldr_mand_ies[] = {
+ SUA_IEI_ROUTE_CTX, SUA_IEI_CAUSE, SUA_IEI_SRC_ADDR,
+ SUA_IEI_DEST_ADDR, 0
+};
+
+static const uint16_t codt_mand_ies[] = {
+ SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, SUA_IEI_DATA, 0
+};
+
+static const uint16_t coda_mand_ies[] = {
+ SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, 0
+};
+
+static const uint16_t core_mand_ies[] = {
+ SUA_IEI_ROUTE_CTX, SUA_IEI_PROTO_CLASS, SUA_IEI_SRC_REF,
+ SUA_IEI_DEST_ADDR, SUA_IEI_SEQ_CTRL, 0
+};
+
+static const uint16_t coak_mand_ies[] = {
+ SUA_IEI_ROUTE_CTX, SUA_IEI_PROTO_CLASS, SUA_IEI_DEST_REF,
+ SUA_IEI_SRC_REF, SUA_IEI_SEQ_CTRL, 0
+};
+
+static const uint16_t coref_mand_ies[] = {
+ SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, SUA_IEI_CAUSE, 0
+};
+
+static const uint16_t relre_mand_ies[] = {
+ SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, SUA_IEI_SRC_REF,
+ SUA_IEI_CAUSE, 0
+};
+
+static const uint16_t relco_mand_ies[] = {
+ SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, 0
+};
+
+static const uint16_t resre_mand_ies[] = {
+ SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, SUA_IEI_SRC_REF,
+ SUA_IEI_CAUSE, 0
+};
+
+static const uint16_t resco_mand_ies[] = {
+ SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, 0
+};
+
+static const uint16_t coerr_mand_ies[] = {
+ SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, SUA_IEI_CAUSE, 0
+};
+
+static const uint16_t coit_mand_ies[] = {
+ SUA_IEI_ROUTE_CTX, SUA_IEI_PROTO_CLASS, SUA_IEI_SRC_REF,
+ SUA_IEI_DEST_REF, 0
+};
+
+static const uint16_t *mand_ies_cl[256] = {
+ MAND_IES(SUA_CL_CLDT, cldt_mand_ies),
+ MAND_IES(SUA_CL_CLDR, cldr_mand_ies),
+};
+
+static const uint16_t *mand_ies_co[256] = {
+ MAND_IES(SUA_CO_CODT, codt_mand_ies),
+ MAND_IES(SUA_CO_CODA, coda_mand_ies),
+ MAND_IES(SUA_CO_CORE, core_mand_ies),
+ MAND_IES(SUA_CO_COAK, coak_mand_ies),
+ MAND_IES(SUA_CO_COREF, coref_mand_ies),
+ MAND_IES(SUA_CO_RELRE, relre_mand_ies),
+ MAND_IES(SUA_CO_RELCO, relco_mand_ies),
+ MAND_IES(SUA_CO_RESRE, resre_mand_ies),
+ MAND_IES(SUA_CO_RESCO, resco_mand_ies),
+ MAND_IES(SUA_CO_COERR, coerr_mand_ies),
+ MAND_IES(SUA_CO_COIT, coit_mand_ies),
+};
+
+static int check_all_mand_ies(const uint16_t **mand_ies, struct xua_msg *xua)
+{
+ uint8_t msg_type = xua->hdr.msg_type;
+ const uint16_t *ies = mand_ies[msg_type];
+ uint16_t ie;
+
+ for (ie = *ies; ie; ie = *ies++) {
+ if (!xua_msg_find_tag(xua, ie)) {
+ LOGP(DSUA, LOGL_ERROR, "SUA Message %u:%u should "
+ "contain IE 0x%04x, but doesn't\n",
+ xua->hdr.msg_class, msg_type, ie);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+
+/***********************************************************************
+ * Receiving SUA messsages from SCTP
+ ***********************************************************************/
+
+static int sua_parse_addr(struct osmo_sccp_addr *out,
+ struct xua_msg *xua,
+ uint16_t iei)
+{
+ const struct xua_msg_part *param = xua_msg_find_tag(xua, iei);
+
+ if (!param)
+ return -ENODEV;
+
+ /* FIXME */
+ return 0;
+}
+
+static int sua_rx_cldt(struct sua_link *link, struct xua_msg *xua)
+{
+ struct osmo_scu_prim *prim;
+ struct osmo_scu_unitdata_param *param;
+ struct xua_msg_part *data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA);
+ struct msgb *upmsg = sua_msgb_alloc();
+ uint32_t protocol_class;
+
+ /* fill primitive */
+ prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim));
+ param = &prim->u.unitdata;
+ osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+ OSMO_SCU_PRIM_N_UNITDATA,
+ PRIM_OP_INDICATION, upmsg);
+ sua_parse_addr(&param->called_addr, xua, SUA_IEI_DEST_ADDR);
+ sua_parse_addr(&param->calling_addr, xua, SUA_IEI_SRC_ADDR);
+ param->in_sequence_control = xua_msg_get_u32(xua, SUA_IEI_SEQ_CTRL);
+ protocol_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS);
+ param->return_option = protocol_class & 0x80;
+ param->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE);
+
+ /* copy data */
+ upmsg->l2h = msgb_put(upmsg, data_ie->len);
+ memcpy(upmsg->l2h, data_ie->dat, data_ie->len);
+
+ /* send to user SAP */
+ link->user->prim_cb(&prim->oph, link);
+
+ return 0;
+}
+
+
+/* connectioness messages received from socket */
+static int sua_rx_cl(struct sua_link *link,
+ struct xua_msg *xua, struct msgb *msg)
+{
+ int rc = -1;
+
+ if (!check_all_mand_ies(mand_ies_cl, xua))
+ return -1;
+
+ switch (xua->hdr.msg_type) {
+ case SUA_CL_CLDT:
+ rc = sua_rx_cldt(link, xua);
+ break;
+ case SUA_CL_CLDR:
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+/* RFC 3868 3.3.3 / SCCP CR */
+static int sua_rx_core(struct sua_link *link, struct xua_msg *xua)
+{
+ struct osmo_scu_prim *prim;
+ struct osmo_scu_connect_param *param;
+ struct xua_msg_part *data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA);
+ struct msgb *upmsg;
+ struct sua_connection *conn;
+ uint8_t *cur;
+
+ /* fill conn */
+ conn = conn_create(link);
+ sua_parse_addr(&conn->called_addr, xua, SUA_IEI_DEST_ADDR);
+ sua_parse_addr(&conn->calling_addr, xua, SUA_IEI_SRC_ADDR);
+ conn->remote_ref = xua_msg_get_u32(xua, SUA_IEI_SRC_REF);
+
+ /* fill primitive */
+ upmsg = sua_msgb_alloc();
+ prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim));
+ param = &prim->u.connect;
+ osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+ OSMO_SCU_PRIM_N_CONNECT,
+ PRIM_OP_INDICATION, upmsg);
+ param->conn_id = conn->conn_id;
+ memcpy(&param->called_addr, &conn->called_addr,
+ sizeof(param->called_addr));
+ memcpy(&param->calling_addr, &conn->calling_addr,
+ sizeof(param->calling_addr));
+ //param->in_sequence_control;
+ param->sccp_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS) & 3;
+ param->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE);
+
+ if (data_ie) {
+ /* copy data */
+ upmsg->l2h = msgb_put(upmsg, data_ie->len);
+ memcpy(upmsg->l2h, data_ie->dat, data_ie->len);
+ }
+
+ conn_state_set(conn, S_CONN_PEND_IN);
+
+ /* send to user SAP */
+ link->user->prim_cb(&prim->oph, link);
+
+ return 0;
+}
+
+/* RFC 3868 3.3.4 / SCCP CC */
+static int sua_rx_coak(struct sua_link *link, struct xua_msg *xua)
+{
+ struct osmo_scu_prim *prim;
+ struct sua_connection *conn;
+ struct osmo_scu_connect_param *param;
+ struct xua_msg_part *data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA);
+ struct msgb *upmsg;
+ uint32_t conn_id = xua_msg_get_u32(xua, SUA_IEI_DEST_REF);
+
+ /* resolve conn */
+ conn = conn_find_by_id(link, conn_id);
+ if (!conn) {
+ LOGP(DSUA, LOGL_ERROR, "COAK for unknwon reference %u\n",
+ conn_id);
+ /* FIXME: error message? */
+ return -1;
+ }
+ conn_restart_rx_inact_timer(conn);
+
+ if (conn->state != S_CONN_PEND_OUT) {
+ LOGP(DSUA, LOGL_ERROR, "COAK in wrong state %s\n",
+ get_value_string(conn_state_names, conn->state));
+ /* FIXME: error message? */
+ return -EINVAL;
+ }
+
+ /* track remote reference */
+ conn->remote_ref = xua_msg_get_u32(xua, SUA_IEI_SRC_REF);
+
+ /* fill primitive */
+ upmsg = sua_msgb_alloc();
+ prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim));
+ param = &prim->u.connect;
+ osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+ OSMO_SCU_PRIM_N_CONNECT,
+ PRIM_OP_CONFIRM, upmsg);
+ param->conn_id = conn->conn_id;
+ memcpy(&param->called_addr, &conn->called_addr,
+ sizeof(param->called_addr));
+ memcpy(&param->calling_addr, &conn->calling_addr,
+ sizeof(param->calling_addr));
+ //param->in_sequence_control;
+ param->sccp_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS) & 3;
+ param->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE);
+
+ if (data_ie) {
+ /* copy data */
+ upmsg->l2h = msgb_put(upmsg, data_ie->len);
+ memcpy(upmsg->l2h, data_ie->dat, data_ie->len);
+ }
+
+ conn_state_set(conn, S_ACTIVE);
+ conn_start_inact_timers(conn);
+
+ /* send to user SAP */
+ link->user->prim_cb(&prim->oph, link);
+
+ return 0;
+}
+
+/* RFC 3868 3.3.5 / SCCP CREF */
+static int sua_rx_coref(struct sua_link *link, struct xua_msg *xua)
+{
+ struct osmo_scu_prim *prim;
+ struct sua_connection *conn;
+ struct osmo_scu_connect_param *param;
+ struct xua_msg_part *data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA);
+ struct msgb *upmsg;
+ uint32_t conn_id = xua_msg_get_u32(xua, SUA_IEI_DEST_REF);
+ uint32_t cause;
+
+ /* resolve conn */
+ conn = conn_find_by_id(link, conn_id);
+ if (!conn) {
+ LOGP(DSUA, LOGL_ERROR, "COREF for unknwon reference %u\n",
+ conn_id);
+ /* FIXME: error message? */
+ return -1;
+ }
+ conn_restart_rx_inact_timer(conn);
+
+ /* fill primitive */
+ upmsg = sua_msgb_alloc();
+ prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim));
+ param = &prim->u.connect;
+ osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+ OSMO_SCU_PRIM_N_DISCONNECT,
+ PRIM_OP_INDICATION, upmsg);
+ param->conn_id = conn_id;
+ memcpy(&param->called_addr, &conn->called_addr,
+ sizeof(param->called_addr));
+ memcpy(&param->calling_addr, &conn->calling_addr,
+ sizeof(param->calling_addr));
+ //param->in_sequence_control;
+ cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE);
+ /* optional: src addr */
+ /* optional: dest addr */
+ param->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE);
+ if (data_ie) {
+ /* copy data */
+ upmsg->l2h = msgb_put(upmsg, data_ie->len);
+ memcpy(upmsg->l2h, data_ie->dat, data_ie->len);
+ }
+
+ /* send to user SAP */
+ link->user->prim_cb(&prim->oph, link);
+
+ conn_state_set(conn, S_IDLE);
+ conn_destroy(conn);
+
+ return 0;
+}
+
+/* RFC 3868 3.3.6 / SCCP RLSD */
+static int sua_rx_relre(struct sua_link *link, struct xua_msg *xua)
+{
+ struct osmo_scu_prim *prim;
+ struct sua_connection *conn;
+ struct osmo_scu_connect_param *param;
+ struct xua_msg_part *data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA);
+ struct msgb *upmsg;
+ uint32_t conn_id = xua_msg_get_u32(xua, SUA_IEI_DEST_REF);
+ uint32_t cause;
+
+ /* resolve conn */
+ conn = conn_find_by_id(link, conn_id);
+ if (!conn) {
+ LOGP(DSUA, LOGL_ERROR, "RELRE for unknwon reference %u\n",
+ conn_id);
+ /* FIXME: error message? */
+ return -1;
+ }
+
+ /* fill primitive */
+ upmsg = sua_msgb_alloc();
+ prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim));
+ param = &prim->u.connect;
+ osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+ OSMO_SCU_PRIM_N_DISCONNECT,
+ PRIM_OP_INDICATION, upmsg); /* what primitive? */
+
+ param->conn_id = conn_id;
+ /* source reference */
+ cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE);
+ param->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE);
+ if (data_ie) {
+ /* copy data */
+ upmsg->l2h = msgb_put(upmsg, data_ie->len);
+ memcpy(upmsg->l2h, data_ie->dat, data_ie->len);
+ }
+
+ memcpy(&param->called_addr, &conn->called_addr,
+ sizeof(param->called_addr));
+ memcpy(&param->calling_addr, &conn->calling_addr,
+ sizeof(param->calling_addr));
+
+ /* send to user SAP */
+ link->user->prim_cb(&prim->oph, link);
+
+ conn_state_set(conn, S_IDLE);
+ conn_destroy(conn);
+
+ return 0;
+}
+
+/* RFC 3868 3.3.7 / SCCP RLC */
+static int sua_rx_relco(struct sua_link *link, struct xua_msg *xua)
+{
+ struct osmo_scu_prim *prim;
+ struct sua_connection *conn;
+ struct osmo_scu_connect_param *param;
+ struct msgb *upmsg;
+ uint32_t conn_id = xua_msg_get_u32(xua, SUA_IEI_DEST_REF);
+
+ /* resolve conn */
+ conn = conn_find_by_id(link, conn_id);
+ if (!conn) {
+ LOGP(DSUA, LOGL_ERROR, "RELCO for unknwon reference %u\n",
+ conn_id);
+ /* FIXME: error message? */
+ return -1;
+ }
+ conn_restart_rx_inact_timer(conn);
+
+ /* fill primitive */
+ upmsg = sua_msgb_alloc();
+ prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim));
+ param = &prim->u.connect;
+ osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+ OSMO_SCU_PRIM_N_DISCONNECT,
+ PRIM_OP_CONFIRM, upmsg); /* what primitive? */
+
+ param->conn_id = conn_id;
+ /* source reference */
+ param->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE);
+
+ memcpy(&param->called_addr, &conn->called_addr,
+ sizeof(param->called_addr));
+ memcpy(&param->calling_addr, &conn->calling_addr,
+ sizeof(param->calling_addr));
+
+ /* send to user SAP */
+ link->user->prim_cb(&prim->oph, link);
+
+ conn_destroy(conn);
+
+ return 0;
+
+}
+
+/* RFC3868 3.3.1 / SCCP DT1 */
+static int sua_rx_codt(struct sua_link *link, struct xua_msg *xua)
+{
+ struct osmo_scu_prim *prim;
+ struct sua_connection *conn;
+ struct osmo_scu_data_param *param;
+ struct xua_msg_part *data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA);
+ struct msgb *upmsg;
+ uint32_t conn_id = xua_msg_get_u32(xua, SUA_IEI_DEST_REF);
+ uint8_t *cur;
+
+ /* resolve conn */
+ conn = conn_find_by_id(link, conn_id);
+ if (!conn) {
+ LOGP(DSUA, LOGL_ERROR, "DT1 for unknwon reference %u\n",
+ conn_id);
+ /* FIXME: error message? */
+ return -1;
+ }
+
+ if (conn->state != S_ACTIVE) {
+ LOGP(DSUA, LOGL_ERROR, "DT1 in invalid state %s\n",
+ get_value_string(conn_state_names, conn->state));
+ /* FIXME: error message? */
+ return -1;
+ }
+
+ conn_restart_rx_inact_timer(conn);
+
+ /* fill primitive */
+ upmsg = sua_msgb_alloc();
+ prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim));
+ param = &prim->u.data;
+ osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+ OSMO_SCU_PRIM_N_DATA,
+ PRIM_OP_INDICATION, upmsg);
+ param->conn_id = conn_id;
+ param->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE);
+
+ /* copy data */
+ upmsg->l2h = msgb_put(upmsg, data_ie->len);
+ memcpy(upmsg->l2h, data_ie->dat, data_ie->len);
+
+ /* send to user SAP */
+ link->user->prim_cb(&prim->oph, link);
+
+ return 0;
+}
+
+
+/* connection-oriented messages received from socket */
+static int sua_rx_co(struct sua_link *link,
+ struct xua_msg *xua, struct msgb *msg)
+{
+ int rc = -1;
+
+ if (!check_all_mand_ies(mand_ies_co, xua))
+ return -1;
+
+ switch (xua->hdr.msg_type) {
+ case SUA_CO_CORE:
+ rc = sua_rx_core(link, xua);
+ break;
+ case SUA_CO_COAK:
+ rc = sua_rx_coak(link, xua);
+ break;
+ case SUA_CO_COREF:
+ rc = sua_rx_coref(link, xua);
+ break;
+ case SUA_CO_RELRE:
+ rc = sua_rx_relre(link, xua);
+ break;
+ case SUA_CO_RELCO:
+ rc = sua_rx_relco(link, xua);
+ break;
+ case SUA_CO_CODT:
+ rc = sua_rx_codt(link, xua);
+ break;
+ case SUA_CO_RESCO:
+ case SUA_CO_RESRE:
+ case SUA_CO_CODA:
+ case SUA_CO_COERR:
+ case SUA_CO_COIT:
+ /* FIXME */
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+/* process SUA message received from socket */
+static int sua_rx_msg(struct sua_link *link, struct msgb *msg)
+{
+ struct xua_msg *xua;
+ int rc = -1;
+
+ xua = xua_from_msg(1, msgb_length(msg), msg->data);
+ if (!xua) {
+ LOGP(DSUA, LOGL_ERROR, "Unable to parse incoming "
+ "SUA message\n");
+ return -EIO;
+ }
+
+ LOGP(DSUA, LOGL_DEBUG, "Received SUA Message (%u:%u)\n",
+ xua->hdr.msg_class, xua->hdr.msg_type);
+
+ switch (xua->hdr.msg_class) {
+ case SUA_MSGC_CL:
+ rc = sua_rx_cl(link, xua, msg);
+ break;
+ case SUA_MSGC_CO:
+ rc = sua_rx_co(link, xua, msg);
+ break;
+ case SUA_MSGC_MGMT:
+ case SUA_MSGC_SNM:
+ case SUA_MSGC_ASPSM:
+ case SUA_MSGC_ASPTM:
+ case SUA_MSGC_RKM:
+ /* FIXME */
+ default:
+ break;
+ }
+
+ xua_msg_free(xua);
+
+ return rc;
+}
+
+/***********************************************************************
+ * libosmonetif integration
+ ***********************************************************************/
+
+#include <osmocom/netif/stream.h>
+#include <netinet/sctp.h>
+
+/* netif code tells us we can read something from the socket */
+static int sua_srv_conn_cb(struct osmo_stream_srv *conn)
+{
+ struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn);
+ struct sua_link *link = osmo_stream_srv_get_data(conn);
+ struct msgb *msg = msgb_alloc(SUA_MSGB_SIZE, "SUA Server Rx");
+ struct sctp_sndrcvinfo sinfo;
+ unsigned int ppid;
+ int flags = 0;
+ int rc;
+
+ if (!msg)
+ return -ENOMEM;
+
+ /* read SUA message from socket and process it */
+ rc = sctp_recvmsg(ofd->fd, msgb_data(msg), msgb_tailroom(msg),
+ NULL, NULL, &sinfo, &flags);
+ if (rc < 0) {
+ close(ofd->fd);
+ osmo_fd_unregister(ofd);
+ ofd->fd = -1;
+ return rc;
+ } else if (rc == 0) {
+ close(ofd->fd);
+ osmo_fd_unregister(ofd);
+ ofd->fd = -1;
+ } else {
+ msgb_put(msg, rc);
+ }
+
+ if (flags & MSG_NOTIFICATION) {
+ msgb_free(msg);
+ return 0;
+ }
+
+ ppid = ntohl(sinfo.sinfo_ppid);
+ msgb_sctp_ppid(msg) = ppid;
+ msgb_sctp_stream(msg) = ntohl(sinfo.sinfo_stream);
+ msg->dst = link;
+
+ switch (ppid) {
+ case SUA_PPID:
+ rc = sua_rx_msg(link, msg);
+ break;
+ default:
+ LOGP(DSUA, LOGL_NOTICE, "SCTP chunk for unknown PPID %u "
+ "received\n", ppid);
+ rc = 0;
+ break;
+ }
+
+ msgb_free(msg);
+ return rc;
+}
+
+static int sua_srv_conn_closed_cb(struct osmo_stream_srv *srv)
+{
+ struct sua_link *sual = osmo_stream_srv_get_data(srv);
+ struct sua_connection *conn;
+
+ LOGP(DSUA, LOGL_INFO, "SCTP connection closed\n");
+
+ /* remove from per-user list of sua links */
+ llist_del(&sual->list);
+
+ llist_for_each_entry(conn, &sual->connections, list) {
+ /* FIXME: send RELEASE request */
+ }
+ talloc_free(sual);
+ osmo_stream_srv_set_data(srv, NULL);
+
+ return 0;
+}
+
+static int sua_accept_cb(struct osmo_stream_srv_link *link, int fd)
+{
+ struct osmo_sua_user *user = osmo_stream_srv_link_get_data(link);
+ struct osmo_stream_srv *srv;
+ struct sua_link *sual;
+
+ LOGP(DSUA, LOGL_INFO, "New SCTP connection accepted\n");
+
+ srv = osmo_stream_srv_create(user, link, fd,
+ sua_srv_conn_cb,
+ sua_srv_conn_closed_cb, NULL);
+ if (!srv)
+ close(fd);
+
+ /* create new SUA link and connect both data structures */
+ sual = sua_link_new(user, 1);
+ if (!sual) {
+ osmo_stream_srv_destroy(srv);
+ return -1;
+ }
+ sual->data = srv;
+ osmo_stream_srv_set_data(srv, sual);
+
+ return 0;
+}
+
+int sua_server_listen(struct osmo_sua_user *user, const char *hostname, uint16_t port)
+{
+ int rc;
+
+ if (user->server)
+ osmo_stream_srv_link_close(user->server);
+ else {
+ user->server = osmo_stream_srv_link_create(user);
+ osmo_stream_srv_link_set_data(user->server, user);
+ osmo_stream_srv_link_set_accept_cb(user->server, sua_accept_cb);
+ }
+
+ osmo_stream_srv_link_set_addr(user->server, hostname);
+ osmo_stream_srv_link_set_port(user->server, port);
+ osmo_stream_srv_link_set_proto(user->server, IPPROTO_SCTP);
+
+ rc = osmo_stream_srv_link_open(user->server);
+ if (rc < 0) {
+ osmo_stream_srv_link_destroy(user->server);
+ user->server = NULL;
+ return rc;
+ }
+
+ return 0;
+}
+
+/* netif code tells us we can read something from the socket */
+static int sua_cli_conn_cb(struct osmo_stream_cli *conn)
+{
+ struct osmo_fd *ofd = osmo_stream_cli_get_ofd(conn);
+ struct sua_link *link = osmo_stream_cli_get_data(conn);
+ struct msgb *msg = msgb_alloc(SUA_MSGB_SIZE, "SUA Client Rx");
+ struct sctp_sndrcvinfo sinfo;
+ unsigned int ppid;
+ int flags = 0;
+ int rc;
+
+ if (!msg)
+ return -ENOMEM;
+
+ /* read SUA message from socket and process it */
+ rc = sctp_recvmsg(ofd->fd, msgb_data(msg), msgb_tailroom(msg),
+ NULL, NULL, &sinfo, &flags);
+ if (rc < 0) {
+ close(ofd->fd);
+ osmo_fd_unregister(ofd);
+ ofd->fd = -1;
+ return rc;
+ } else if (rc == 0) {
+ close(ofd->fd);
+ osmo_fd_unregister(ofd);
+ ofd->fd = -1;
+ } else {
+ msgb_put(msg, rc);
+ }
+
+ if (flags & MSG_NOTIFICATION) {
+ msgb_free(msg);
+ return 0;
+ }
+
+ ppid = ntohl(sinfo.sinfo_ppid);
+ msgb_sctp_ppid(msg) = ppid;
+ msgb_sctp_stream(msg) = ntohl(sinfo.sinfo_stream);
+ msg->dst = link;
+
+ switch (ppid) {
+ case SUA_PPID:
+ rc = sua_rx_msg(link, msg);
+ break;
+ default:
+ LOGP(DSUA, LOGL_NOTICE, "SCTP chunk for unknown PPID %u "
+ "received\n", ppid);
+ rc = 0;
+ break;
+ }
+
+ msgb_free(msg);
+ return rc;
+}
+
+int sua_client_connect(struct osmo_sua_user *user, const char *hostname, uint16_t port)
+{
+ struct osmo_stream_cli *cli;
+ struct sua_link *sual;
+ int rc;
+
+ cli = osmo_stream_cli_create(user);
+ if (!cli)
+ return -1;
+ osmo_stream_cli_set_addr(cli, hostname);
+ osmo_stream_cli_set_port(cli, port);
+ osmo_stream_cli_set_proto(cli, IPPROTO_SCTP);
+ osmo_stream_cli_set_reconnect_timeout(cli, 5);
+ osmo_stream_cli_set_read_cb(cli, sua_cli_conn_cb);
+
+ /* create SUA link and associate it with stream_cli */
+ sual = sua_link_new(user, 0);
+ if (!sual) {
+ osmo_stream_cli_destroy(cli);
+ return -1;
+ }
+ sual->data = cli;
+ osmo_stream_cli_set_data(cli, sual);
+
+ rc = osmo_stream_cli_open(cli);
+ if (rc < 0) {
+ sua_link_destroy(sual);
+ osmo_stream_cli_destroy(cli);
+ return rc;
+ }
+ user->client = cli;
+
+ return 0;
+}
+
+struct sua_link *sua_client_get_link(struct osmo_sua_user *user)
+{
+ return osmo_stream_cli_get_data(user->client);
+}
+
+static LLIST_HEAD(sua_users);
+
+struct osmo_sua_user *osmo_sua_user_create(void *ctx, osmo_prim_cb prim_cb)
+{
+ struct osmo_sua_user *user = talloc_zero(ctx, struct osmo_sua_user);
+
+ user->prim_cb = prim_cb;
+ INIT_LLIST_HEAD(&user->links);
+
+ llist_add_tail(&user->list, &sua_users);
+
+ return user;
+}
+
+void osmo_sua_user_destroy(struct osmo_sua_user *user)
+{
+ struct sua_link *link;
+
+ llist_del(&user->list);
+
+ llist_for_each_entry(link, &user->links, list)
+ sua_link_destroy(link);
+
+ talloc_free(user);
+}
+
+void osmo_sua_set_log_area(int area)
+{
+ DSUA = area;
+}