fixeria has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/37184?usp=email )
Change subject: s1gw: add S1AP Server (dispatcher) component ......................................................................
s1gw: add S1AP Server (dispatcher) component
Change-Id: Ib8a14c1a76c980f4d8a153461c9b63d205574573 Related: SYS#6772 --- A s1gw/S1AP_Server.ttcn 1 file changed, 444 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-ttcn3-hacks refs/changes/84/37184/1
diff --git a/s1gw/S1AP_Server.ttcn b/s1gw/S1AP_Server.ttcn new file mode 100644 index 0000000..fe0c8f3 --- /dev/null +++ b/s1gw/S1AP_Server.ttcn @@ -0,0 +1,434 @@ +module S1AP_Server { + +/* S1AP Server, runs on top of S1AP_CodecPort, accepting the S1AP + * connections and forwarding S1AP PDUs to/from the S1APSRV_CONN_PT components. + * + * (C) 2024 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * Author: Vadim Yanitskiy vyanitskiy@sysmocom.de + * + * All rights reserved. + * + * Released under the terms of GNU General Public License, Version 2 or + * (at your option) any later version. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import from General_Types all; +import from Osmocom_Types all; +import from IPL4asp_Types all; + +import from S1AP_CodecPort all; +import from S1AP_CodecPort_CtrlFunct all; +import from S1AP_Types all; +import from S1AP_Constants all; +import from S1AP_PDU_Contents all; +import from S1AP_PDU_Descriptions all; +import from S1AP_IEs all; +import from S1AP_Templates all; + +type enumerated S1APSRV_Event { + S1APSRV_EVENT_CONN_UP, + S1APSRV_EVENT_CONN_DOWN +}; + +type port S1APSRV_CONN_PT message { + inout S1AP_PDU, S1APSRV_Event; +} with { extension "internal" }; + +type component S1APSRV_ConnHdlr { + port S1APSRV_CONN_PT S1AP_CONN; + port S1APSRV_PROC_PT S1AP_PROC; +}; + +type record S1APSRV_ConnParams { + HostName local_ip, + PortNumber local_port +}; + +type component S1AP_Server_CT { + /* port facing to the SUT */ + port S1AP_CODEC_PT S1AP; + /* all S1APSRV_ConnHdlr S1AP ports connect here */ + port S1APSRV_CONN_PT S1AP_CLIENT; + /* procedure based port to register for incoming connections */ + port S1APSRV_PROC_PT S1AP_PROC; + + /* active eNB connections */ + var ConnList g_conn_list; + /* registered ConnHdlr */ + var ConnHdlrList g_conn_hdlr_list; + + var S1APSRV_ConnParams g_cpars; + var ConnectionId g_s1ap_conn_id := -1; +}; + + +/* represents a single eNB connection */ +private type record ConnData { + ConnectionId conn_id, + Global_ENB_ID genb_id /* can be unbound */ +}; +private type record of ConnData ConnList; + +/* represents a single ConnHdlr item */ +private type record ConnHdlrData { + S1APSRV_ConnHdlr vc_conn, + Global_ENB_ID genb_id, + ConnectionId conn_id /* can be -1 */ +}; +private type record of ConnHdlrData ConnHdlrList; + +private template (present) S1AP_RecvFrom +tr_S1AP_RecvFrom_R(template (present) S1AP_PDU msg := ?, + template (present) ConnectionId conn_id := ?) := { + connId := conn_id, + remName := ?, + remPort := ?, + locName := ?, + locPort := ?, + msg := msg +}; + +template (value) SctpTuple ts_SCTP(template (omit) integer ppid := 18) := { + sinfo_stream := omit, + sinfo_ppid := ppid, + remSocks := omit, + assocId := omit +}; + +/*********************************************************************************** + * Connection management API + **********************************************************************************/ + +/* find a connection [index] by a connection ID */ +private function f_conn_find_by_conn_id(ConnectionId conn_id) +runs on S1AP_Server_CT return integer { + for (var integer i := 0; i < lengthof(g_conn_list); i := i + 1) { + if (g_conn_list[i].conn_id == conn_id) { + return i; + } + } + + return -1; +} + +/* find a connection [index] by a global eNB ID */ +private function f_conn_find_by_genb_id(Global_ENB_ID genb_id) +runs on S1AP_Server_CT return integer { + for (var integer i := 0; i < lengthof(g_conn_list); i := i + 1) { + if (isbound(g_conn_list[i].genb_id) and + g_conn_list[i].genb_id == genb_id) { + return i; + } + } + + return -1; +} + +/* add a new connection, return its index */ +private function f_conn_add(ConnectionId conn_id) +runs on S1AP_Server_CT return integer { + var ConnData conn := { conn_id, - }; + var integer idx; + + if (f_conn_find_by_conn_id(conn_id) != -1) { + setverdict(fail, "Connection (id=", conn_id, ") is already added"); + mtc.stop; + } + + idx := lengthof(g_conn_list); + g_conn_list := g_conn_list & { conn }; + log("Connection (id=", conn_id, ") is registered"); + + return idx; +} + +/* del an existing connection */ +private function f_conn_del(ConnectionId conn_id) +runs on S1AP_Server_CT { + var ConnList conn_list := { }; + + for (var integer i := 0; i < lengthof(g_conn_list); i := i + 1) { + if (g_conn_list[i].conn_id == conn_id) { + if (isbound(g_conn_list[i].genb_id)) { + f_ConnHdlr_update(g_conn_list[i].genb_id, -1); + } + } else { + conn_list := conn_list & { g_conn_list[i] }; + } + } + + if (lengthof(conn_list) == lengthof(g_conn_list)) { + setverdict(fail, "Connection (id=", conn_id, ") is not known"); + mtc.stop; + } + + g_conn_list := conn_list; + log("Connection (id=", conn_id, ") is deleted"); +} + +/* add a new connection, return its index */ +private function f_conn_set_genb_id(ConnectionId conn_id, Global_ENB_ID genb_id) +runs on S1AP_Server_CT { + var integer idx; + + if (f_conn_find_by_genb_id(genb_id) != -1) { + setverdict(fail, "Duplicate Global eNB ID ", genb_id); + mtc.stop; + } + + idx := f_conn_find_by_conn_id(conn_id); + if (idx == -1) { + setverdict(fail, "Connection (id=", conn_id, ") is not known"); + mtc.stop; + } + + g_conn_list[idx].genb_id := genb_id; + + f_ConnHdlr_update(genb_id, conn_id); +} + +private function f_conn_close(ConnectionId conn_id) +runs on S1AP_Server_CT { + log("Closing an eNB connection (id=", conn_id, ")"); + S1AP_CodecPort_CtrlFunct.f_IPL4_close(S1AP, conn_id, + { sctp := valueof(ts_SCTP) }); + f_conn_del(conn_id); +} + +private function f_conn_close_by_genb_id(Global_ENB_ID genb_id) +runs on S1AP_Server_CT { + var integer idx; + + idx := f_conn_find_by_genb_id(genb_id); + if (idx == -1) { + setverdict(fail, "There is no connection for Global eNB ID ", genb_id); + mtc.stop; + } + + f_conn_close(g_conn_list[idx].conn_id); +} + +/*********************************************************************************** + * ConnHdlr management API + **********************************************************************************/ + +/* find a ConnHdlr [index] by a connection ID */ +private function f_ConnHdlr_find_by_conn_id(ConnectionId conn_id) +runs on S1AP_Server_CT return integer { + for (var integer i := 0; i < lengthof(g_conn_hdlr_list); i := i + 1) { + if (g_conn_hdlr_list[i].conn_id == conn_id) { + return i; + } + } + + return -1; +} + +/* find a ConnHdlr [index] by a global eNB ID */ +private function f_ConnHdlr_find_by_genb_id(Global_ENB_ID genb_id) +runs on S1AP_Server_CT return integer { + for (var integer i := 0; i < lengthof(g_conn_hdlr_list); i := i + 1) { + if (g_conn_hdlr_list[i].genb_id == genb_id) { + return i; + } + } + + return -1; +} + +/* find a ConnHdlr [index] by a component reference */ +private function f_ConnHdlr_find_by_vc_conn(S1APSRV_ConnHdlr vc_conn) +runs on S1AP_Server_CT return integer { + for (var integer i := 0; i < lengthof(g_conn_hdlr_list); i := i + 1) { + if (g_conn_hdlr_list[i].vc_conn == vc_conn) { + return i; + } + } + + return -1; +} + +private function f_ConnHdlr_add(S1APSRV_ConnHdlr vc_conn, Global_ENB_ID genb_id) +runs on S1AP_Server_CT { + var ConnectionId conn_id := -1; + var integer idx; + + if (f_ConnHdlr_find_by_genb_id(genb_id) != -1) { + setverdict(fail, "Global eNB ID ", genb_id, " is already registered"); + mtc.stop; + } + + idx := f_conn_find_by_genb_id(genb_id); + if (idx != -1) { + conn_id := g_conn_list[idx].conn_id; + } + + g_conn_hdlr_list := g_conn_hdlr_list & { {vc_conn, genb_id, conn_id} }; + log("Global eNB ID ", genb_id, " has been registered"); +} + +private function f_ConnHdlr_del(integer idx) +runs on S1AP_Server_CT { + var ConnHdlrList conn_hdlr_list := { }; + + for (var integer i := 0; i < lengthof(g_conn_hdlr_list); i := i + 1) { + if (i != idx) { + conn_hdlr_list := conn_hdlr_list & { g_conn_hdlr_list[i] }; + } + } + + g_conn_hdlr_list := conn_hdlr_list; +} + +private function f_ConnHdlr_del_by_genb_id(Global_ENB_ID genb_id) +runs on S1AP_Server_CT { + var integer idx; + + idx := f_ConnHdlr_find_by_genb_id(genb_id); + if (idx == -1) { + setverdict(fail, "Global eNB ID ", genb_id, " is not registered"); + mtc.stop; + } + + f_ConnHdlr_del(idx); + log("Global eNB ID ", genb_id, " has been unregistered"); +} + +private function f_ConnHdlr_update(Global_ENB_ID genb_id, ConnectionId conn_id) +runs on S1AP_Server_CT { + for (var integer i := 0; i < lengthof(g_conn_hdlr_list); i := i + 1) { + if (g_conn_hdlr_list[i].genb_id == genb_id) { + g_conn_hdlr_list[i].conn_id := conn_id; + /* notify the ConnHdlr about connection state */ + var S1APSRV_Event ev; + if (conn_id == -1) { + ev := S1APSRV_EVENT_CONN_DOWN; + } else { + ev := S1APSRV_EVENT_CONN_UP; + } + S1AP_CLIENT.send(ev) to g_conn_hdlr_list[i].vc_conn; + } + } +} + +signature S1APSRV_register(in S1APSRV_ConnHdlr vc_conn, in Global_ENB_ID genb_id); +signature S1APSRV_unregister(in S1APSRV_ConnHdlr vc_conn, in Global_ENB_ID genb_id); +signature S1APSRV_close_conn(in S1APSRV_ConnHdlr vc_conn, in Global_ENB_ID genb_id); + +type port S1APSRV_PROC_PT procedure { + inout S1APSRV_register; + inout S1APSRV_unregister; + inout S1APSRV_close_conn; +} with { extension "internal" }; + +function f_ConnHdlr_register(Global_ENB_ID genb_id) +runs on S1APSRV_ConnHdlr { + S1AP_PROC.call(S1APSRV_register:{self, genb_id}) { + [] S1AP_PROC.getreply(S1APSRV_register:{?, ?}) { }; + } +} + +function f_ConnHdlr_unregister(Global_ENB_ID genb_id) +runs on S1APSRV_ConnHdlr { + S1AP_PROC.call(S1APSRV_unregister:{self, genb_id}) { + [] S1AP_PROC.getreply(S1APSRV_unregister:{?, ?}) { }; + } +} + +function f_ConnHdlr_close_conn(Global_ENB_ID genb_id) +runs on S1APSRV_ConnHdlr { + S1AP_PROC.call(S1APSRV_close_conn:{self, genb_id}) { + [] S1AP_PROC.getreply(S1APSRV_close_conn:{?, ?}) { }; + } +} + +function main(S1APSRV_ConnParams cpars) runs on S1AP_Server_CT { + var Result res; + + g_cpars := cpars; + g_conn_list := { }; + g_conn_hdlr_list := { }; + + map(self:S1AP, system:S1AP_CODEC_PT); + res := S1AP_CodecPort_CtrlFunct.f_IPL4_listen(S1AP, + cpars.local_ip, cpars.local_port, + { sctp := valueof(ts_SCTP) }); + if (not ispresent(res.connId)) { + setverdict(fail, "Could not create an S1AP socket, check your configuration"); + mtc.stop; + } + g_s1ap_conn_id := res.connId; + + log("SCTP server listening on ", cpars.local_ip, ":", cpars.local_port); + + while (true) { + var S1APSRV_ConnHdlr vc_conn; + var S1AP_RecvFrom mrf; + var S1AP_PDU msg; + + var Global_ENB_ID genb_id; + var PortEvent pev; + + alt { + /* S1AP PDU from a peer (eNB) */ + [] S1AP.receive(tr_S1AP_RecvFrom_R) -> value mrf { + if (match(mrf.msg, tr_S1AP_SetupReq)) { + genb_id := mrf.msg.initiatingMessage.value_.S1SetupRequest.protocolIEs[0].value_.Global_ENB_ID; + f_conn_set_genb_id(mrf.connId, genb_id); + } + var integer idx := f_ConnHdlr_find_by_conn_id(mrf.connId); + if (idx != -1) { + S1AP_CLIENT.send(mrf.msg) to g_conn_hdlr_list[idx].vc_conn; + } /* else: no ConnHdlr, drop PDU */ + } + /* S1AP PDU from a ConnHdlr: pass on transparently */ + [] S1AP_CLIENT.receive(S1AP_PDU:?) -> value msg sender vc_conn { + var integer idx := f_ConnHdlr_find_by_vc_conn(vc_conn); + if (idx == -1) { + setverdict(fail, "Component ", vc_conn, " is not registered"); + mtc.stop; + } + if (g_conn_hdlr_list[idx].conn_id == -1) { + setverdict(fail, "S1AP connection is not up"); + mtc.stop; + } + S1AP.send(t_S1AP_Send(g_conn_hdlr_list[idx].conn_id, msg)); + } + + /* connection opened/closed events */ + [] S1AP.receive(PortEvent:{connOpened := ?}) -> value pev { + log("eNB connection (id=", pev.connOpened.connId, ", ", + pev.connOpened.remName, ":", pev.connOpened.remPort, ") ", + "established"); + f_conn_add(pev.connOpened.connId); + } + [] S1AP.receive(PortEvent:{connClosed := ?}) -> value pev { + log("eNB connection (id=", pev.connClosed.connId, ", ", + pev.connClosed.remName, ":", pev.connClosed.remPort, ") ", + "closed"); + f_conn_del(pev.connClosed.connId); + } + + /* SCTP events we don't care about */ + [] S1AP.receive(PortEvent:{sctpEvent := ?}) { } + + /* ConnHdlr registration/unregistration */ + [] S1AP_PROC.getcall(S1APSRV_register:{?, ?}) -> param(vc_conn, genb_id) { + f_ConnHdlr_add(vc_conn, genb_id); + S1AP_PROC.reply(S1APSRV_register:{vc_conn, genb_id}) to vc_conn; + } + [] S1AP_PROC.getcall(S1APSRV_unregister:{?, ?}) -> param(vc_conn, genb_id) { + f_ConnHdlr_del_by_genb_id(genb_id); + S1AP_PROC.reply(S1APSRV_unregister:{vc_conn, genb_id}) to vc_conn; + } + [] S1AP_PROC.getcall(S1APSRV_close_conn:{?, ?}) -> param(vc_conn, genb_id) { + f_conn_close_by_genb_id(genb_id); + S1AP_PROC.reply(S1APSRV_close_conn:{vc_conn, genb_id}) to vc_conn; + } + } + } +} + +}