| /* 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 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_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 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 <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 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) |
| { |
| DSUA = area; |
| } |