| 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 <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; |
| } |
| } |
| } |
| } |
| |
| } |