module S1AP_Server { /* S1AP Server, runs on top of S1AP_CodecPort, accepting the S1AP * connections and forwarding S1AP PDUs to/from the ConnHdlr components. * A ConnHdlr component may subscribe for one or more S1AP connections * using the Global_ENB_ID value, which is sent in S1AP SetupReq. * * (C) 2024 by sysmocom - s.f.m.c. GmbH * Author: Vadim Yanitskiy * * 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 }; const SctpTuple c_SctpTuple_S1AP := { sinfo_stream := omit, sinfo_ppid := 18, /* S1AP */ 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 := c_SctpTuple_S1AP }); 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_s1ap_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_s1ap_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_s1ap_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 := c_SctpTuple_S1AP }); 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; } } } } }