| /* OsmoS1GW (S1AP Gateway) test suite in TTCN-3 |
| * |
| * (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 |
| */ |
| |
| module S1GW_Tests { |
| |
| import from General_Types all; |
| import from Osmocom_Types all; |
| import from Native_Functions all; |
| import from IPL4asp_Types all; |
| import from Misc_Helpers all; |
| |
| import from S1AP_CodecPort all; |
| import from S1AP_CodecPort_CtrlFunct all; |
| import from S1AP_Types all; |
| import from S1AP_Templates all; |
| import from S1AP_PDU_Descriptions all; |
| import from S1AP_IEs all; |
| import from S1AP_PDU_Contents all; |
| import from S1AP_Constants all; |
| |
| import from SCTP_Templates all; |
| import from S1AP_Server all; |
| |
| modulepar { |
| charstring mp_s1gw_enb_ip; /* eNB facing address of the S1GW */ |
| charstring mp_s1gw_mme_ip; /* MME facing address of the S1GW */ |
| charstring mp_mme_bind_ip; /* MME address on which we get connections from S1GW */ |
| } |
| |
| private type record of ConnHdlr ConnHdlrList; |
| |
| type component ConnHdlr extends S1APSRV_ConnHdlr { |
| port S1AP_CODEC_PT S1AP_ENB; |
| var ConnectionId g_s1ap_conn_id := -1; |
| }; |
| |
| type component test_CT { |
| timer g_Tguard; |
| var S1AP_Server_CT vc_S1APSRV; |
| }; |
| |
| private altstep as_Tguard() runs on test_CT { |
| [] g_Tguard.timeout { |
| Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Tguard timeout"); |
| } |
| } |
| |
| template Global_ENB_ID |
| ts_Global_ENB_ID(integer enb_id := 0, |
| OCT3 plmn_id := '00f110'O) := { |
| pLMNidentity := plmn_id, |
| eNB_ID := { |
| macroENB_ID := int2bit(enb_id, 20) |
| }, |
| iE_Extensions := omit |
| } |
| |
| function f_init(float Tval := 20.0) runs on test_CT { |
| g_Tguard.start(Tval); |
| activate(as_Tguard()); |
| } |
| |
| function f_init_s1ap_srv() runs on test_CT { |
| var S1APSRV_ConnParams cpars := { |
| local_ip := mp_mme_bind_ip, |
| local_port := 36412 |
| }; |
| |
| vc_S1APSRV := S1AP_Server_CT.create("S1APSRV-" & testcasename()); |
| vc_S1APSRV.start(S1AP_Server.main(cpars)); |
| } |
| |
| type record ConnHdlrPars { |
| Global_ENB_ID genb_id |
| }; |
| |
| template (value) ConnHdlrPars |
| t_ConnHdlrPars(integer enb_id := 0) := { |
| genb_id := ts_Global_ENB_ID(enb_id) |
| } |
| |
| type function void_fn(ConnHdlrPars pars) runs on ConnHdlr; |
| |
| function f_ConnHdlr_spawn(void_fn fn, ConnHdlrPars pars) |
| runs on test_CT return ConnHdlr { |
| var ConnHdlr vc_conn; |
| |
| vc_conn := ConnHdlr.create("ConnHdlr-" & testcasename()); |
| if (vc_S1APSRV.running) { |
| connect(vc_conn:S1AP_CONN, vc_S1APSRV:S1AP_CLIENT); |
| connect(vc_conn:S1AP_PROC, vc_S1APSRV:S1AP_PROC); |
| } |
| vc_conn.start(derefers(fn)(pars)); |
| |
| return vc_conn; |
| } |
| |
| function f_ConnHdlr_connect() runs on ConnHdlr { |
| var Result res; |
| timer T; |
| |
| map(self:S1AP_ENB, system:S1AP_CODEC_PT); |
| |
| /* initiate SCTP connection establishment */ |
| res := S1AP_CodecPort_CtrlFunct.f_IPL4_connect(S1AP_ENB, |
| mp_s1gw_enb_ip, 36412, |
| "0.0.0.0", 0, -1, |
| { 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; |
| |
| /* wait for the establishment confirmation */ |
| T.start(2.0); |
| alt { |
| [] S1AP_ENB.receive(tr_SctpAssocChange(SCTP_COMM_UP, g_s1ap_conn_id)) { |
| log("eNB connection established"); |
| } |
| [] S1AP_ENB.receive(PortEvent:{sctpEvent := ?}) { repeat; } |
| [] T.timeout { |
| setverdict(fail, "eNB connection establishment timeout"); |
| self.stop; |
| } |
| } |
| } |
| |
| function f_ConnHdlr_disconnect() runs on ConnHdlr { |
| var Result res; |
| |
| S1AP_CodecPort_CtrlFunct.f_IPL4_close(S1AP_ENB, g_s1ap_conn_id, |
| { sctp := c_SctpTuple_S1AP }); |
| g_s1ap_conn_id := -1; |
| unmap(self:S1AP_ENB, system:S1AP_CODEC_PT); |
| |
| S1AP_CONN.receive(S1APSRV_Event:S1APSRV_EVENT_CONN_DOWN); |
| |
| log("eNB connection closed"); |
| } |
| |
| function f_ConnHdlr_expect_shutdown() runs on ConnHdlr { |
| S1AP_ENB.receive(tr_SctpShutDownEvent(g_s1ap_conn_id)); |
| S1AP_ENB.receive(tr_SctpAssocChange(SCTP_SHUTDOWN_COMP, g_s1ap_conn_id)); |
| S1AP_ENB.receive(PortEvent:{connClosed := ?}); |
| } |
| |
| function f_ConnHdlr_tx_s1ap_from_enb(template (value) S1AP_PDU pdu) |
| runs on ConnHdlr { |
| S1AP_ENB.send(t_S1AP_Send(g_s1ap_conn_id, pdu)); |
| } |
| |
| function f_ConnHdlr_tx_s1ap_from_mme(template (value) S1AP_PDU pdu) |
| runs on ConnHdlr { |
| S1AP_CONN.send(pdu); |
| } |
| |
| function f_ConnHdlr_rx_s1ap_from_enb(out S1AP_PDU pdu, |
| template (present) S1AP_PDU tr_pdu := ?, |
| float Tval := 0.5) |
| runs on ConnHdlr { |
| timer T := Tval; |
| |
| T.start; |
| alt { |
| [] S1AP_CONN.receive(tr_pdu) -> value pdu { |
| setverdict(pass); |
| T.stop; |
| } |
| [] S1AP_CONN.receive(S1AP_PDU:?) -> value pdu { |
| setverdict(fail, "Rx unexpected S1AP PDU from eNB: ", pdu); |
| T.stop; |
| } |
| [] T.timeout { |
| setverdict(fail, "Timeout waiting for S1AP PDU from eNB: ", tr_pdu); |
| } |
| } |
| } |
| |
| function f_ConnHdlr_rx_s1ap_from_mme(out S1AP_PDU pdu, |
| template (present) S1AP_PDU tr_pdu := ?, |
| float Tval := 0.5) |
| runs on ConnHdlr { |
| var S1AP_RecvFrom recv; |
| timer T := Tval; |
| |
| T.start; |
| alt { |
| [] S1AP_ENB.receive(t_S1AP_RecvFrom(tr_pdu)) -> value recv { |
| pdu := recv.msg; |
| setverdict(pass); |
| T.stop; |
| } |
| [] S1AP_ENB.receive(t_S1AP_RecvFrom(?)) -> value recv { |
| pdu := recv.msg; |
| setverdict(fail, "Rx unexpected S1AP PDU from MME: ", pdu); |
| T.stop; |
| } |
| [] T.timeout { |
| setverdict(fail, "Timeout waiting for S1AP PDU from MME: ", tr_pdu); |
| } |
| } |
| } |
| |
| function f_ConnHdlr_setup(Global_ENB_ID genb_id) runs on ConnHdlr { |
| var S1AP_PDU pdu; |
| timer T; |
| |
| var SupportedTAs supported_tas_dummy := { |
| { |
| tAC := '0000'O, |
| broadcastPLMNs := { '00f000'O }, |
| iE_Extensions := omit |
| } |
| }; |
| |
| f_ConnHdlr_tx_s1ap_from_enb(ts_S1AP_SetupReq(genb_id, |
| supported_tas_dummy, |
| v32)); |
| T.start(1.0); |
| alt { |
| [] S1AP_CONN.receive(S1APSRV_Event:S1APSRV_EVENT_CONN_UP) { repeat; } |
| [] S1AP_CONN.receive(tr_S1AP_SetupReq) { |
| setverdict(pass); |
| T.stop; |
| } |
| [] S1AP_CONN.receive(S1AP_PDU:?) -> value pdu { |
| setverdict(fail, "Rx unexpected S1AP PDU: ", pdu); |
| T.stop; |
| } |
| [] T.timeout { |
| setverdict(fail, "Timeout waiting for S1AP SetupReq"); |
| } |
| } |
| } |
| |
| |
| function f_TC_setup(ConnHdlrPars pars) runs on ConnHdlr { |
| f_ConnHdlr_register(pars.genb_id); |
| |
| f_ConnHdlr_connect(); |
| f_ConnHdlr_setup(pars.genb_id); |
| f_sleep(0.5); /* keep the connection idle for some time */ |
| f_ConnHdlr_disconnect(); |
| |
| f_ConnHdlr_unregister(pars.genb_id); |
| } |
| testcase TC_setup() runs on test_CT { |
| var ConnHdlrPars pars := valueof(t_ConnHdlrPars); |
| var ConnHdlr vc_conn; |
| |
| f_init(); |
| f_init_s1ap_srv(); |
| |
| vc_conn := f_ConnHdlr_spawn(refers(f_TC_setup), pars); |
| vc_conn.done; |
| } |
| testcase TC_setup_multi() runs on test_CT { |
| var ConnHdlrList vc_conns := { }; |
| |
| f_init(); |
| f_init_s1ap_srv(); |
| |
| for (var integer i := 0; i < 42; i := i + 1) { |
| var ConnHdlrPars pars := valueof(t_ConnHdlrPars(i)); |
| var ConnHdlr vc_conn := f_ConnHdlr_spawn(refers(f_TC_setup), pars); |
| vc_conns := vc_conns & { vc_conn }; |
| } |
| |
| for (var integer i := 0; i < 42; i := i + 1) { |
| vc_conns[i].done; |
| } |
| } |
| |
| |
| /* MME terminates connection, expect S1GW to terminate the eNB connection */ |
| function f_TC_conn_term_by_mme(ConnHdlrPars pars) runs on ConnHdlr { |
| f_ConnHdlr_register(pars.genb_id); |
| |
| f_ConnHdlr_connect(); |
| f_ConnHdlr_setup(pars.genb_id); |
| f_sleep(0.5); /* keep the connection idle for some time */ |
| |
| /* MME (S1AP_Server_CT) terminates connection */ |
| f_ConnHdlr_close_conn(pars.genb_id); |
| /* expect our eNB connection to be released gracefully */ |
| f_ConnHdlr_expect_shutdown(); |
| |
| f_ConnHdlr_unregister(pars.genb_id); |
| } |
| testcase TC_conn_term_by_mme() runs on test_CT { |
| var ConnHdlrPars pars := valueof(t_ConnHdlrPars); |
| var ConnHdlr vc_conn; |
| |
| f_init(); |
| f_init_s1ap_srv(); |
| |
| vc_conn := f_ConnHdlr_spawn(refers(f_TC_conn_term_by_mme), pars); |
| vc_conn.done; |
| } |
| |
| |
| /* MME is not available, expect S1GW to terminate the eNB connection */ |
| function f_TC_conn_term_mme_unavail(ConnHdlrPars pars) runs on ConnHdlr { |
| /* establish an eNB connection to the S1GW */ |
| f_ConnHdlr_connect(); |
| /* expect our eNB connection to be released gracefully */ |
| f_ConnHdlr_expect_shutdown(); |
| setverdict(pass); |
| } |
| testcase TC_conn_term_mme_unavail() runs on test_CT { |
| var ConnHdlrPars pars := valueof(t_ConnHdlrPars); |
| var ConnHdlr vc_conn; |
| |
| f_init(); |
| f_init_s1ap_srv(); |
| vc_S1APSRV.stop; |
| |
| vc_conn := f_ConnHdlr_spawn(refers(f_TC_conn_term_mme_unavail), pars); |
| vc_conn.done; |
| } |
| |
| function f_TC_e_rab_setup(ConnHdlrPars pars) runs on ConnHdlr { |
| const integer mme_id := 7; |
| const integer enb_id := 9; |
| const integer erab_id := 6; |
| var S1AP_PDU pdu; |
| |
| f_ConnHdlr_register(pars.genb_id); |
| f_ConnHdlr_connect(); |
| f_ConnHdlr_setup(pars.genb_id); |
| |
| log("eNB -> [S1GW] -> MME: E-RAB SETUP REQUEST"); |
| var template (value) E_RABToBeSetupListBearerSUReq items_req; |
| var E_RABToBeSetupItemBearerSUReq item_req := { |
| e_RAB_ID := erab_id, |
| e_RABlevelQoSParameters := { |
| qCI := 5, |
| allocationRetentionPriority := { |
| priorityLevel := 1, |
| pre_emptionCapability := shall_not_trigger_pre_emption, |
| pre_emptionVulnerability := not_pre_emptable, |
| iE_Extensions := omit |
| }, |
| gbrQosInformation := omit, |
| iE_Extensions := omit |
| }, |
| transportLayerAddress := -, |
| gTP_TEID := f_rnd_octstring(4), |
| nAS_PDU := ''O, |
| iE_Extensions := omit |
| }; |
| |
| /* eNB -> S1GW */ |
| item_req.transportLayerAddress := oct2bit(f_inet_addr("1.2.3.4")); |
| items_req := ts_S1AP_RABToBeSetupListBearerSUReq(item_req); |
| f_ConnHdlr_tx_s1ap_from_enb(ts_S1AP_RABSetupReq(mme_id, enb_id, items_req)); |
| |
| /* S1GW -> MME */ |
| item_req.transportLayerAddress := oct2bit(f_inet_addr(mp_s1gw_mme_ip)); |
| items_req := ts_S1AP_RABToBeSetupListBearerSUReq(item_req); |
| f_ConnHdlr_rx_s1ap_from_enb(pdu, tr_S1AP_RABSetupReq(mme_id, enb_id, items_req)); |
| |
| |
| log("eNB <- [S1GW] <- MME: E-RAB SETUP RESPONSE"); |
| var template (value) E_RABSetupListBearerSURes items_res; |
| var E_RABSetupItemBearerSURes item_res := { |
| e_RAB_ID := erab_id, |
| transportLayerAddress := -, |
| gTP_TEID := f_rnd_octstring(4), |
| iE_Extensions := omit |
| }; |
| |
| /* MME -> S1GW */ |
| item_res.transportLayerAddress := oct2bit(f_inet_addr("4.3.2.1")); |
| items_res := ts_S1AP_RABSetupListBearerSURes(item_res); |
| f_ConnHdlr_tx_s1ap_from_mme(ts_S1AP_RABSetupRsp(mme_id, enb_id, items_res)); |
| |
| /* S1GW -> eNB */ |
| item_res.transportLayerAddress := oct2bit(f_inet_addr(mp_s1gw_enb_ip)); |
| items_res := ts_S1AP_RABSetupListBearerSURes(item_res); |
| f_ConnHdlr_rx_s1ap_from_mme(pdu, tr_S1AP_RABSetupRsp(mme_id, enb_id, items_res)); |
| |
| f_ConnHdlr_disconnect(); |
| f_ConnHdlr_unregister(pars.genb_id); |
| } |
| testcase TC_e_rab_setup() runs on test_CT { |
| var ConnHdlrPars pars := valueof(t_ConnHdlrPars); |
| var ConnHdlr vc_conn; |
| |
| f_init(); |
| f_init_s1ap_srv(); |
| |
| vc_conn := f_ConnHdlr_spawn(refers(f_TC_e_rab_setup), pars); |
| vc_conn.done; |
| } |
| |
| control { |
| execute( TC_setup() ); |
| execute( TC_setup_multi() ); |
| execute( TC_conn_term_by_mme() ); |
| execute( TC_conn_term_mme_unavail() ); |
| execute( TC_e_rab_setup() ); |
| } |
| |
| } |