diff --git a/configure.ac b/configure.ac index 49c5f7ee..361ddc7b 100644 --- a/configure.ac +++ b/configure.ac @@ -13,9 +13,11 @@ dnl checks for programs AC_PROG_MAKE_SET AC_PROG_CC AC_PROG_INSTALL -AC_PROG_RANLIB +LT_INIT +AC_PROG_LIBTOOL PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.3.0) +PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 0.0.6.1) # The following test is taken from WebKit's webkit.m4 saved_CFLAGS="$CFLAGS" @@ -33,7 +35,8 @@ AC_OUTPUT( libosmo-mtp.pc include/sccp/Makefile include/mtp/Makefile - include/sigtran/Makefile + include/osmocom/Makefile + include/osmocom/sigtran/Makefile include/Makefile src/Makefile tests/Makefile diff --git a/include/Makefile.am b/include/Makefile.am index 8045946b..7cd4b57f 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -1 +1 @@ -SUBDIRS = sccp mtp sigtran +SUBDIRS = sccp mtp osmocom diff --git a/include/osmocom/Makefile.am b/include/osmocom/Makefile.am new file mode 100644 index 00000000..7e86a4b4 --- /dev/null +++ b/include/osmocom/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = sigtran diff --git a/include/osmocom/sigtran/Makefile.am b/include/osmocom/sigtran/Makefile.am new file mode 100644 index 00000000..538a2497 --- /dev/null +++ b/include/osmocom/sigtran/Makefile.am @@ -0,0 +1,5 @@ +sigtran_HEADERS = m3ua_types.h xua_types.h xua_msg.h m2ua_types.h sccp_sap.h sua.h +sigtrandir = $(includedir)/osmocom/sigtran + +sigtran_prot_HEADERS = protocol/sua.h +sigtran_protdir = $(includedir)/osmocom/sigtran/protocol diff --git a/include/sigtran/m2ua_types.h b/include/osmocom/sigtran/m2ua_types.h similarity index 100% rename from include/sigtran/m2ua_types.h rename to include/osmocom/sigtran/m2ua_types.h diff --git a/include/sigtran/m3ua_types.h b/include/osmocom/sigtran/m3ua_types.h similarity index 100% rename from include/sigtran/m3ua_types.h rename to include/osmocom/sigtran/m3ua_types.h diff --git a/include/osmocom/sigtran/protocol/sua.h b/include/osmocom/sigtran/protocol/sua.h new file mode 100644 index 00000000..864a2dfc --- /dev/null +++ b/include/osmocom/sigtran/protocol/sua.h @@ -0,0 +1,120 @@ +/* RFC 3868 SUA SCCP User Adaption */ + +/* (C) 2012 by Harald Welte + * + * 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 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 . + */ + +#pragma once +#include + +#define SUA_PPID 4 +#define SUA_PORT 14001 + +/* 3.1.2 Message Classes */ +#define SUA_MSGC_MGMT 0 +#define SUA_MSGC_SNM 2 +#define SUA_MSGC_ASPSM 3 +#define SUA_MSGC_ASPTM 4 +#define SUA_MSGC_CL 7 +#define SUA_MSGC_CO 8 +#define SUA_MSGC_RKM 9 + +/* 3.1.3 Message Types */ +#define SUA_MGMT_ERR 0 +#define SUA_MGMT_NTFY 1 + +#define SUA_SNM_DUNA 1 +#define SUA_SNM_DAVA 2 +#define SUA_SNM_DAUD 3 +#define SUA_SNM_SCON 4 +#define SUA_SNM_DUPU 5 +#define SUA_SNM_DRST 6 + +#define SUA_ASPSM_UP 1 +#define SUA_ASPSM_DOWN 2 +#define SUA_ASPSM_BEAT 3 +#define SUA_ASPSM_UP_ACK 4 +#define SUA_ASPSM_DOWN_ACK 5 +#define SUA_ASPSM_BEAT_ACK 6 + +#define SUA_ASPTM_ACTIVE 1 +#define SUA_ASPTM_INACTIVE 2 +#define SUA_ASPTM_ACTIVE_ACK 3 +#define SUA_ASPTM_INACTIVE_ACK 4 + +#define SUA_RKM_REG_REQ 1 +#define SUA_RKM_REG_RSP 2 +#define SUA_RKM_DEREG_REQ 3 +#define SUA_RKM_DEREG_RSP 4 + +#define SUA_CL_CLDT 1 +#define SUA_CL_CLDR 2 + +#define SUA_CO_CORE 1 +#define SUA_CO_COAK 2 +#define SUA_CO_COREF 3 +#define SUA_CO_RELRE 4 +#define SUA_CO_RELCO 5 +#define SUA_CO_RESCO 6 +#define SUA_CO_RESRE 7 +#define SUA_CO_CODT 8 +#define SUA_CO_CODA 9 +#define SUA_CO_COERR 10 +#define SUA_CO_COIT 11 + +#define SUA_IEI_ROUTE_CTX 0x0006 +#define SUA_IEI_CORR_ID 0x0013 +#define SUA_IEI_REG_RESULT 0x0014 +#define SUA_IEI_DEREG_RESULT 0x0015 + +/* 3.10 SUA specific parameters */ + +#define SUA_IEI_S7_HOP_CTR 0x0101 +#define SUA_IEI_SRC_ADDR 0x0102 +#define SUA_IEI_DEST_ADDR 0x0103 +#define SUA_IEI_SRC_REF 0x0104 +#define SUA_IEI_DEST_REF 0x0105 +#define SUA_IEI_CAUSE 0x0106 +#define SUA_IEI_SEQ_NR 0x0107 +#define SUA_IEI_RX_SEQ_NR 0x0108 +#define SUA_IEI_ASP_CAPA 0x0109 +#define SUA_IEI_CREDIT 0x010A +#define SUA_IEI_DATA 0x010B +#define SUA_IEI_USER_CAUSE 0x010C +#define SUA_IEI_NET_APPEARANCE 0x010D +#define SUA_IEI_ROUTING_KEY 0x010E +#define SUA_IEI_DRN 0x010F +#define SUA_IEI_TID 0x0110 +#define SUA_IEI_SMI 0x0112 +#define SUA_IEI_IMPORTANCE 0x0113 +#define SUA_IEI_MSG_PRIO 0x0114 +#define SUA_IEI_PROTO_CLASS 0x0115 +#define SUA_IEI_SEQ_CTRL 0x0116 +#define SUA_IEI_SEGMENTATION 0x0117 +#define SUA_IEI_CONG_LEVEL 0x0118 + +#define SUA_IEI_GT 0x8001 +#define SUA_IEI_PC 0x8002 +#define SUA_IEI_SSN 0x8003 +#define SUA_IEI_IPv4 0x8004 +#define SUA_IEI_HOST 0x8005 +#define SUA_IEI_IPv6 0x8006 + +#define SUA_RI_GT 1 +#define SUA_RI_SSN_PC 2 +#define SUA_RI_HOST 3 +#define SUA_RI_SSN_IP 4 diff --git a/include/osmocom/sigtran/sccp_sap.h b/include/osmocom/sigtran/sccp_sap.h new file mode 100644 index 00000000..fb42ceec --- /dev/null +++ b/include/osmocom/sigtran/sccp_sap.h @@ -0,0 +1,199 @@ +#pragma once + +/* SCCP User SAP description */ + +/* (C) 2015 by Harald Welte + * 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 . + * + */ + +#include +#include + +#include + +/* FIXME: properly define this based on libosmocore */ +#define SCCP_SAP_USER 0x2342 + +/* detailed coding of primitives at the SAP_SCCP_USER */ + +/*! \brief SCCP-User primitives as per Q.711 */ +enum osmo_scu_prim_type { + /* connection oriented, 6.1.1 */ + OSMO_SCU_PRIM_N_CONNECT, + OSMO_SCU_PRIM_N_DATA, + OSMO_SCU_PRIM_N_EXPEDITED_DATA, + OSMO_SCU_PRIM_N_DISCONNECT, + OSMO_SCU_PRIM_N_RESET, + OSMO_SCU_PRIM_N_INFORM, + /* connectionless, 6.2.2 */ + OSMO_SCU_PRIM_N_UNITDATA, + OSMO_SCU_PRIM_N_NOTICE, + /* management */ + OSMO_SCU_PRIM_N_COORD, + OSMO_SCU_PRIM_N_STATE, + OSMO_SCU_PRIM_N_PCSTATE, +}; + +#define OSMO_SCCP_ADDR_T_GT 0x0001 +#define OSMO_SCCP_ADDR_T_PC 0x0002 +#define OSMO_SCCP_ADDR_T_SSN 0x0004 +#define OSMO_SCCP_ADDR_T_IPv4 0x0008 +#define OSMO_SCCP_ADDR_T_IPv6 0x0010 + +/* Q.713 3.4.1 + RFC 3868 3.10.2.3 */ +enum osmo_sccp_routing_ind { + OSMO_SCCP_RI_GT, + OSMO_SCCP_RI_SSN_PC, + OSMO_SCCP_RI_SSN_IP, +}; + +/* Q.713 3.4.1 + RFC 3868 3.10.2.3 */ +enum osmo_sccp_gti { + OSMO_SCCP_GTI_NO_GT, + OSMO_SCCP_GTI_NAI_ONLY, + OSMO_SCCP_GTI_TT_ONLY, + OSMO_SCCP_GTI_TT_NPL_ENC, + OSMO_SCCP_GTI_TT_NPL_ENC_NAI, +}; + +/* RFC 3868 3.10.2.3 */ +enum osmo_sccp_npi { + OSMO_SCCP_NPI_UNKNOWN = 0, + OSMO_SCCP_NPI_E164_ISDN = 1, + OSMO_SCCP_NPI_GEERIC = 2, + OSMO_SCCP_NPI_X121_DATA = 3, + OSMO_SCCP_NPI_F69_TELEX = 4, + OSMO_SCCP_NPI_E210_MARITIME = 5, + OSMO_SCCP_NPI_E212_LAND = 6, + OSMO_SCCP_NPI_E214_ISDN_MOBILE = 7, + OSMO_SCCP_NPI_PRIVATE = 14, +}; + +/* Q.713 3.4.2.3.1 + RFC 3868 3.10.2.3 */ +enum osmo_sccp_nai { + OSMO_SCCP_NAI_UNKNOWN = 0, + OSMO_SCCP_NAI_SUBSCR = 1, + OSMO_SCCP_NAI_RES_NAT_USE = 2, + OSMO_SCCP_NAI_NATL = 3, + OSMO_SCCP_NAI_INTL = 4, + /* 5.. 255: Spare */ +}; + +/* Q.713 3.4.2.2 */ +enum osmo_sccp_ssn { + OSMO_SCCP_SSN_MGMT = 1, + OSMO_SCCP_SSN_ISUP = 3, + OSMO_SCCP_SSN_OMAP = 4, + OSMO_SCCP_SSN_MAP = 5, + OSMO_SCCP_SSN_HLR = 6, + OSMO_SCCP_SSN_VLR = 7, + OSMO_SCCP_SSN_MSC = 8, + OSMO_SCCP_SSN_EIR = 9, + OSMO_SCCP_SSN_AUC = 0x0a, + OSMO_SCCP_SSN_ISDN_SS = 0x0b, + OSMO_SCCP_SSN_RES_INTL = 0x0c, + OSMO_SCCP_SSN_BISDN = 0x0d, + OSMO_SCCP_SSN_TC_TEST = 0x0e, +}; + +struct osmo_sccp_gt { + uint8_t gti; + uint8_t nr_digits; + uint8_t tt; + uint32_t npi; + uint32_t nai; + uint8_t digits[32]; +}; + +struct osmo_sccp_addr { + uint32_t presence; + struct osmo_sccp_gt gt; + uint32_t pc; + uint32_t ssn; + union { + struct in_addr v4; + struct in6_addr v6; + } ip; + /* we don't do hostnames */ +}; + +/* OSMO_SCU_PRIM_N_CONNECT */ +struct osmo_scu_connect_param { + struct osmo_sccp_addr called_addr; + struct osmo_sccp_addr calling_addr; + struct osmo_sccp_addr responding_addr; + //struct osmo_sccp_qos_pars qos_pars; + uint32_t sccp_class; + uint32_t importance; + uint32_t conn_id; + /* user data */ +}; + +/* OSMO_SCU_PRIM_N_DATA / OSMO_SCU_PRIM_N_EXPEDITED_DATA */ +struct osmo_scu_data_param { + uint32_t conn_id; + uint32_t importance; + /* user data */ +}; + +enum osmo_sccp_originator { + OSMO_SCCP_ORIG_NS_PROVIDER, + OSMO_SCCP_ORIG_NS_USER, + OSMO_SCCP_ORIG_UNDEFINED, +}; + +/* OSMO_SCU_PRIM_N_DISCONNECT */ +struct osmo_scu_disconn_param { + enum osmo_sccp_originator originator; + struct osmo_sccp_addr repsonding_addr; + uint32_t cause; + uint32_t conn_id; + uint32_t importance; + /* user data */ +}; + +/* OSMO_SCU_PRIM_N_RESET */ +struct osmo_scu_reset_param { + enum osmo_sccp_originator originator; + uint32_t cause; + uint32_t conn_id; +}; + +/* OSMO_SCU_PRIM_N_UNITDATA */ +struct osmo_scu_unitdata_param { + struct osmo_sccp_addr called_addr; + struct osmo_sccp_addr calling_addr; + uint32_t in_sequence_control; + uint32_t return_option; + uint32_t importance; + /* user data */ +}; + +struct osmo_scu_prim { + struct osmo_prim_hdr oph; + union { + struct osmo_scu_connect_param connect; + struct osmo_scu_data_param data; + struct osmo_scu_disconn_param disconnect; + struct osmo_scu_reset_param reset; + struct osmo_scu_unitdata_param unitdata; + } u; +}; + +#define msgb_scu_prim(msg) ((struct osmo_scu_prim *)(msg)->l1h) + +char *osmo_scu_prim_name(struct osmo_prim_hdr *oph); diff --git a/include/osmocom/sigtran/sua.h b/include/osmocom/sigtran/sua.h new file mode 100644 index 00000000..2aa99f44 --- /dev/null +++ b/include/osmocom/sigtran/sua.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +struct osmo_sua_user; +struct osmo_sua_link; + +void osmo_sua_set_log_area(int area); + +struct osmo_sua_user *osmo_sua_user_create(void *ctx, osmo_prim_cb prim_cb); +void osmo_sua_user_destroy(struct osmo_sua_user *user); + +int osmo_sua_server_listen(struct osmo_sua_user *user, const char *hostname, uint16_t port); + +int osmo_sua_client_connect(struct osmo_sua_user *user, const char *hostname, uint16_t port); +struct osmo_sua_link *osmo_sua_client_get_link(struct osmo_sua_user *user); + +/* user hands us a SCCP-USER SAP primitive down into the stack */ +int osmo_sua_user_link_down(struct osmo_sua_link *link, struct osmo_prim_hdr *oph); + diff --git a/include/sigtran/xua_msg.h b/include/osmocom/sigtran/xua_msg.h similarity index 100% rename from include/sigtran/xua_msg.h rename to include/osmocom/sigtran/xua_msg.h diff --git a/include/sigtran/xua_types.h b/include/osmocom/sigtran/xua_types.h similarity index 100% rename from include/sigtran/xua_types.h rename to include/osmocom/sigtran/xua_types.h diff --git a/include/sigtran/Makefile.am b/include/sigtran/Makefile.am deleted file mode 100644 index aa0f95ca..00000000 --- a/include/sigtran/Makefile.am +++ /dev/null @@ -1,2 +0,0 @@ -sigtran_HEADERS = m3ua_types.h xua_types.h xua_msg.h m2ua_types.h -sigtrandir = $(includedir)/osmocom/sigtran diff --git a/src/Makefile.am b/src/Makefile.am index fa47e850..ba551624 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,9 +1,29 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) +# Legacy static libs + sccpdir = $(libdir) sccp_LIBRARIES = libsccp.a libmtp.a libxua.a libsccp_a_SOURCES = sccp.c -libmtp_a_SOURCES = mtp_pcap.c +libmtp_a_SOURCES = mtp_pcap.c libxua_a_SOURCES = xua_msg.c +# ensure that the file for the static lib is built with different C +# flags, working around automake complaining that xua_msg.o is built +# both with libtool (below) and without (here) +libxua_a_CPPFLAGS = $(AM_CPPFLAGS) -DDUMMY -UDUMMY + + +# New shared lib + +lib_LTLIBRARIES = libosmo-sigtran.la + +# This is _NOT_ the library release version, it's an API version. +# Please read Chapter 6 "Library interface versions" of the libtool +# documentation before making any modification +LIBVERSION=0:0:0 + +libosmo_sigtran_la_SOURCES = sccp_sap.c sua.c xua_msg.c +libosmo_sigtran_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined -export-symbols-regex '^osmo_' +libosmo_sigtran_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMONETIF_LIBS) $(LIBSCTP_LIBS) diff --git a/src/sccp_sap.c b/src/sccp_sap.c new file mode 100644 index 00000000..51598b07 --- /dev/null +++ b/src/sccp_sap.c @@ -0,0 +1,36 @@ +#include +#include +#include + +#include + +const struct value_string osmo_scu_prim_names[] = { + { OSMO_SCU_PRIM_N_CONNECT, "N-CONNECT" }, + { OSMO_SCU_PRIM_N_DATA, "N-DATA" }, + { OSMO_SCU_PRIM_N_EXPEDITED_DATA, "N-EXPEDITED-DATA" }, + { OSMO_SCU_PRIM_N_DISCONNECT, "N-DISCONNECT" }, + { OSMO_SCU_PRIM_N_RESET, "N-RESET" }, + { OSMO_SCU_PRIM_N_INFORM, "N-INFORM" }, + { OSMO_SCU_PRIM_N_UNITDATA, "N-UNITDATA" }, + { OSMO_SCU_PRIM_N_NOTICE, "N-NOTICE" }, + /* management */ + { OSMO_SCU_PRIM_N_COORD, "N-COORD" }, + { OSMO_SCU_PRIM_N_STATE, "N-STATE" }, + { OSMO_SCU_PRIM_N_PCSTATE, "N-PCSATE" }, + { 0, NULL } +}; + +static char prim_name_buf[128]; + +char *osmo_scu_prim_name(struct osmo_prim_hdr *oph) +{ + const char *name = get_value_string(osmo_scu_prim_names, oph->primitive); + + prim_name_buf[0] = '\0'; + strncpy(prim_name_buf, name, sizeof(prim_name_buf)-1); + prim_name_buf[sizeof(prim_name_buf)-1] = '\0'; + name = get_value_string(osmo_prim_op_names, oph->operation); + strncat(prim_name_buf, name, sizeof(prim_name_buf)-strlen(prim_name_buf)-2); + + return prim_name_buf; +} diff --git a/src/sua.c b/src/sua.c new file mode 100644 index 00000000..6e187d80 --- /dev/null +++ b/src/sua.c @@ -0,0 +1,1432 @@ +/* Minimal implementation of RFC 3868 - SCCP User Adaptation Layer */ + +/* (C) 2015 by Harald Welte + * 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 . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#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 osmo_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 osmo_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 osmo_sua_link *sua_link_new(struct osmo_sua_user *user, int is_server) +{ + struct osmo_sua_link *link; + + link = talloc_zero(user, struct osmo_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 osmo_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 osmo_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 osmo_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 osmo_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 osmo_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 osmo_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 osmo_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 osmo_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 osmo_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 osmo_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_sua_user_link_down(struct osmo_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_scu_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 osmo_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(¶m->called_addr, xua, SUA_IEI_DEST_ADDR); + sua_parse_addr(¶m->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 osmo_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 osmo_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(¶m->called_addr, &conn->called_addr, + sizeof(param->called_addr)); + memcpy(¶m->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 osmo_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(¶m->called_addr, &conn->called_addr, + sizeof(param->called_addr)); + memcpy(¶m->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 osmo_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(¶m->called_addr, &conn->called_addr, + sizeof(param->called_addr)); + memcpy(¶m->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 osmo_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(¶m->called_addr, &conn->called_addr, + sizeof(param->called_addr)); + memcpy(¶m->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 osmo_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(¶m->called_addr, &conn->called_addr, + sizeof(param->called_addr)); + memcpy(¶m->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 osmo_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 osmo_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 osmo_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 +#include + +/* 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 osmo_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 osmo_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 osmo_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 osmo_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 osmo_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 osmo_sua_client_connect(struct osmo_sua_user *user, const char *hostname, uint16_t port) +{ + struct osmo_stream_cli *cli; + struct osmo_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 osmo_sua_link *osmo_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 osmo_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) +{ + xua_set_log_area(area); + DSUA = area; +} diff --git a/src/xua_msg.c b/src/xua_msg.c index e10b942a..0ced1f91 100644 --- a/src/xua_msg.c +++ b/src/xua_msg.c @@ -16,7 +16,7 @@ * */ -#include +#include #include #include diff --git a/tests/m2ua/m2ua_test.c b/tests/m2ua/m2ua_test.c index e432f96f..d4bc120e 100644 --- a/tests/m2ua/m2ua_test.c +++ b/tests/m2ua/m2ua_test.c @@ -15,8 +15,8 @@ * */ -#include -#include +#include +#include #include #include