/* Connection Handler of HNBGW test suite in TTCN-3 * (C) 2021-2024 by sysmocom - s.f.m.c. GmbH * 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 * * This test suite tests OsmoHNBGW while emulating the hNodeB as well as MSC, SGSN, MGW * See README for more details. */ module ConnHdlr { import from Misc_Helpers all; import from General_Types all; import from GSM_Types all; import from Osmocom_Types all; import from IPL4asp_Types all; import from Native_Functions all; import from Osmocom_CTRL_Functions all; import from Osmocom_CTRL_Types all; import from Osmocom_CTRL_Adapter all; import from StatsD_Types all; import from StatsD_CodecPort all; import from StatsD_CodecPort_CtrlFunct all; import from StatsD_Checker all; import from Osmocom_VTY_Functions all; import from TELNETasp_PortType all; import from HNBAP_Templates all; import from HNBAP_IEs all; import from HNBAP_PDU_Descriptions all; import from RUA_IEs all; import from RUA_Templates all; import from RUA_Emulation all; import from Iuh_Emulation all; import from RANAP_Types all; import from RANAP_PDU_Descriptions all; import from RANAP_PDU_Contents all; import from RANAP_IEs all; import from RANAP_Templates all; import from RANAP_CodecPort all; import from RAN_Adapter all; import from RAN_Emulation all; import from MGCP_Emulation all; import from MGCP_Types all; import from MGCP_Templates all; import from MGCP_CodecPort all; import from SDP_Types all; import from SDP_Templates all; import from PFCP_Types all; import from PFCP_Emulation all; import from PFCP_Templates all; import from PFCP_CodecPort all; import from TCCConversion_Functions all; import from MobileL3_Types all; import from MobileL3_CommonIE_Types all; import from MobileL3_GMM_SM_Types all; import from L3_Templates all; import from L3_Common all; import from SCCPasp_Types all; import from SCCP_Templates all; import from Mutex all; /*********************************************************************** * code running inside per-UE ConnHdlr ***********************************************************************/ /* We extend: * RUA_ConnHdlr (for the Iuh side, emulating the HNB) * RAN_ConnHdlr (for the Iu side, emulating the MSC) * MGCP_ConnHdlr (for the MGCP side, emulating the MGW) * StatsD_ConnHdlr (for the statsd interface, verifying counters) */ type component ConnHdlr extends RAN_ConnHdlr, MGCP_ConnHdlr, RUA_ConnHdlr, PFCP_ConnHdlr, StatsD_ConnHdlr, MutexCT { var integer g_sccp_conn_id; var TestHdlrParams g_pars; timer g_Tguard; port TELNETasp_PT HNBGWVTY; } type record of ConnHdlr ConnHdlrList; type record MgwResponse { integer resp, HostName mgw_rtp_ip, /* if set, used after first received MDCX: */ HostName mgw_rtp_ip_mdcx optional, PortNumber mgw_rtp_port, MgcpConnectionId mgcp_connection_id } type record MgcpParameters { integer got_crcx_count, integer got_dlcx_count, MgcpCallId mgcp_call_id optional, MgcpEndpoint mgcp_ep, MgwResponse mgw_conn_ran, MgwResponse mgw_conn_cn, uint7_t rtp_payload_type, charstring rtp_sdp_format, HostName hnb_rtp_ip, PortNumber hnb_rtp_port, HostName cn_rtp_ip, PortNumber cn_rtp_port, boolean use_osmux, integer got_osmux_count } private function f_mgcp_endp_str(integer endp_idx := 1) return charstring { return "rtpbridge/" & int2str(endp_idx) & "@mgw"; } template (value) MgcpParameters t_MgcpParams(integer endp_idx := 1) := { got_crcx_count := 0, got_dlcx_count := 0, mgcp_call_id := omit, mgcp_ep := f_mgcp_endp_str(endp_idx), mgw_conn_ran := { resp := 1, mgw_rtp_ip := "127.1.2.1", mgw_rtp_ip_mdcx := omit, mgw_rtp_port := 10000, mgcp_connection_id := '11111'H }, mgw_conn_cn := { resp := 1, mgw_rtp_ip := "127.1.2.2", mgw_rtp_ip_mdcx := omit, mgw_rtp_port := 20000, mgcp_connection_id := '22222'H }, rtp_payload_type := 112, rtp_sdp_format := "VND.3GPP.IUFP", hnb_rtp_ip := "127.1.1.1", hnb_rtp_port := 10001, cn_rtp_ip := "127.1.3.1", cn_rtp_port := 20001, use_osmux := false, got_osmux_count := 0 } type record FTeid { HostName addr, OCT4 teid } type record FTeids { FTeid local, FTeid remote } /* 'local' and 'remote' refer to the GTP information from the UPF's point of view: * HNB UPF CN * access.remote <---> access.local | core.local <---> core.remote */ type record GtpParameters { FTeids core, FTeids access } /* HNB UPF CN * access.remote <---> access.local | core.local <---> core.remote * 127.0.0.4 127.0.0.3 127.0.0.2 127.0.0.1 * 0x44004400 0x30303030 0x22002200 0x10101010 */ template (value) GtpParameters t_GtpParams := { core := { local := { addr := "127.0.0.2", teid := '22002200'O }, remote := { addr := "127.0.0.1", teid := '10101010'O } }, access := { local := { addr := "127.0.0.3", teid := '30303030'O }, remote := { addr := "127.0.0.4", teid := '44004400'O } } } const OCT8 c_SEID0 := '0000000000000000'O; type record PfcpParameters { /* whether we expect HNBGW to manage a UPF through PFCP: */ boolean pfcp_enabled, charstring pfcp_local_addr, Node_ID upf_node_id, F_SEID upf_f_seid, F_SEID hnbgw_f_seid optional } template (value) PfcpParameters t_PfcpParams(boolean pfcp_enabled := false, charstring pfcp_local_addr := "127.0.0.1", charstring pfcp_upf_node_id := "\07osmocom\03org", uint64_t upf_f_seid := 1) := { pfcp_enabled := pfcp_enabled, pfcp_local_addr := pfcp_local_addr, upf_node_id := valueof(ts_PFCP_Node_ID_fqdn(pfcp_upf_node_id)), upf_f_seid := ts_PFCP_F_SEID_ipv4(f_inet_addr(pfcp_local_addr), int2oct(upf_f_seid, 8)), hnbgw_f_seid := omit } type record HnbConfig { LocationAreaIdentification lai, integer sac } type record TestHdlrParams { integer hnb_idx, /* cn_idx indicates which CN link from g_cn[] to connect to the test component. * See also f_cn_idx(). */ integer cn_idx, hexstring imsi, boolean ps_domain, MgcpParameters mgcp_pars, GtpParameters gtp_pars, HnbConfig hnb optional, boolean expect_separate_sccp_cr, integer tx_sccp_cr_data_len, boolean expect_compl_l3_success, PfcpParameters pfcp_pars, octetstring nas_pdu optional, /* local and remote SCCP addresses, used to transmit conectionless * (unitdata) messages: */ SCCP_PAR_Address sccp_addr_msc optional, SCCP_PAR_Address sccp_addr_hnbgw optional, /* RAB release cause */ RANAP_IEs.Cause rab_rel_cause, integer hnbgw_timer_x31, IuSignallingConnectionIdentifier sigc_id, float t_guard } template (value) TestHdlrParams t_pars(integer imsi_suffix, boolean ps_domain := false, integer hnb_idx := 0, boolean expect_separate_sccp_cr := false, integer tx_sccp_cr_data_len := 0, boolean expect_compl_l3_success := true, integer cn_idx := 0, template (value) MgcpParameters mgcp_pars := t_MgcpParams(), template (value) PfcpParameters pfcp_pars := t_PfcpParams(), template (value) RANAP_IEs.Cause rab_rel_cause := ts_RanapCause_nas_normal, integer hnbgw_timer_x31 := 5, float t_guard := 20.0) := { hnb_idx := hnb_idx, cn_idx := cn_idx, imsi := f_gen_imsi(imsi_suffix), ps_domain := ps_domain, mgcp_pars := mgcp_pars, gtp_pars := t_GtpParams, hnb := omit, /* filled in later */ expect_separate_sccp_cr := expect_separate_sccp_cr, tx_sccp_cr_data_len := tx_sccp_cr_data_len, expect_compl_l3_success := expect_compl_l3_success, pfcp_pars := pfcp_pars, nas_pdu := omit, sccp_addr_msc := omit, sccp_addr_hnbgw := omit, rab_rel_cause := rab_rel_cause, hnbgw_timer_x31 := hnbgw_timer_x31, sigc_id := int2bit(f_rnd_int(1000), 24), t_guard := t_guard } function f_create_ranap_exp(octetstring l3_enc) runs on ConnHdlr { BSSAP_PROC.call(RAN_register:{l3_enc, self}) { [] BSSAP_PROC.getreply(RAN_register:{?, ?}) {}; } } type function void_fn(charstring id) runs on ConnHdlr; /* first function inside ConnHdlr component; sets g_pars + starts function */ function f_handler_init(void_fn fn, charstring id, TestHdlrParams pars) runs on ConnHdlr { /* make parameters available via component variable */ g_pars := pars; /* start guard timer and activate it as default */ g_Tguard.start(g_pars.t_guard); activate(as_Tguard_ConnHdlr()); map(self:HNBGWVTY, system:HNBGWVTY); f_vty_set_prompts(HNBGWVTY); f_vty_transceive(HNBGWVTY, "enable"); /* TODO: CTRL? */ f_sleep(1.0); fn.apply(id); } /* global altstep for global guard timer; */ private altstep as_Tguard_ConnHdlr() runs on ConnHdlr { [] g_Tguard.timeout { setverdict(fail, "Timeout of T_guard"); mtc.stop; } } function f_bssap_expect(template (present) RANAP_PDU exp_rx) runs on ConnHdlr return RANAP_PDU { var RANAP_PDU rx; timer T := 5.0; T.start; alt { [] BSSAP.receive(exp_rx) -> value rx { setverdict(pass); } [] BSSAP.receive(RANAP_PDU:?) { setverdict(fail, "Got an unexpected RANAP message on BSSAP port, was waiting for ", exp_rx); mtc.stop; } [] T.timeout { setverdict(fail, "Timeout waiting for RANAP on BSSAP port: ", exp_rx); mtc.stop; } } T.stop; return rx; } /* send RANAP on Iuh and expect it to show up on Iu */ function f_iuh2iu(template (present) RANAP_PDU tx, template RANAP_PDU exp_rx := omit) runs on ConnHdlr return RANAP_PDU { var RANAP_PDU rx; timer T := 5.0; if (istemplatekind(exp_rx, "omit")) { exp_rx := tx; } RUA.send(tx); return f_bssap_expect(exp_rx); } /* send RANAP on Iu and expect it to show up on Iuh */ function f_iu2iuh(template (present) RANAP_PDU tx, template RANAP_PDU exp_rx := omit) runs on ConnHdlr return RANAP_PDU { if (istemplatekind(exp_rx, "omit")) { exp_rx := tx; } BSSAP.send(tx); return f_rua_expect(exp_rx) } /* expect to receive a specific RUA message on Iuh */ function f_rua_expect(template (present) RANAP_PDU exp_rx) runs on ConnHdlr return RANAP_PDU { var RANAP_PDU rx; timer T := 5.0; T.start; alt { [] RUA.receive(exp_rx) -> value rx { setverdict(pass); } [] RUA.receive(RANAP_PDU:?) { setverdict(fail, "Got an unexpected RUA message, was waiting for ", exp_rx); mtc.stop; } [] T.timeout { setverdict(fail, "Timeout waiting for Iuh ", exp_rx); mtc.stop; } } T.stop; return rx; } /* send RANAP on Iuh and expect it to show up on Iu */ function f_iuh2iu_connect(template (value) RANAP_PDU tx, template RANAP_PDU exp_rx := omit) runs on ConnHdlr return RANAP_PDU { var RANAP_PDU rx; timer T := 5.0; if (istemplatekind(exp_rx, "omit")) { exp_rx := tx; } /* create an expect on the Iu side for the random NAS portion */ if (g_pars.expect_separate_sccp_cr) { f_ran_register_sccp_cr_without_payload(); } else { var template (omit) octetstring nas := f_ranap_extract_l3(valueof(tx)); f_ran_register_exp(valueof(nas)); } /* send it via Iuh (creating a RUA connection) */ RUA.send(RUA_Conn_Req:{g_pars.ps_domain, tx}); if (g_pars.expect_separate_sccp_cr) { /* Acknowledge the empty SCCP CR. RAN_Emulation does the confirmation, no need to respond. */ BSSAP.receive(tr_RANAP_Conn_Req()); } /* expect to receive it on the Iu side */ return f_bssap_expect(exp_rx); } /* 3GPP TS 48.006 9.2 Connection release: * * The MSC sends a SCCP released message. This message shall not contain * any user data field. * * So what we expect normally is: * * HNBGW MSC * RUA --id-Disconnect-------> | ---Data-Form-1(!)---> | Iu-ReleaseComplete * | <--Released---------- | (no data) * * This function tests osmo-hnbgw behavior if the CN fails to send a RLSD: * after some timeout, osmo-hnbgw should send a RLSD to the CN. */ function f_iuh2iu_disconnect(template (present) RANAP_PDU tx, RUA_IEs.Cause cause, template RANAP_PDU exp_rx := omit) runs on ConnHdlr return RANAP_PDU { var RANAP_PDU rx timer T := int2float(g_pars.hnbgw_timer_x31) + 1.0; if (istemplatekind(exp_rx, "omit")) { exp_rx := tx; } /* send it via Iuh (creating a RUA connection) */ RUA.send(RUA_Disc_Req:{cause, tx}); /* expect to receive it on the Iu side */ rx := f_bssap_expect(exp_rx); return rx; } private function f_build_initial_ue_with_nas(TestHdlrParams pars, octetstring nas) return RANAP_PDU { var RANAP_IEs.LAI lai := { pLMNidentity := hex2oct(pars.hnb.lai.mcc_mnc), lAC := int2oct(pars.hnb.lai.lac, 2), iE_Extensions := omit }; var RANAP_IEs.SAI sai := { pLMNidentity := lai.pLMNidentity, lAC := lai.lAC, sAC := int2oct(pars.hnb.sac, 2), iE_Extensions := omit } var GlobalRNC_ID grnc_id := { pLMNidentity := lai.pLMNidentity, rNC_ID := 2342 } var template RANAP_PDU ret; if (pars.ps_domain) { var RANAP_IEs.RAC rac := '00'O; ret := ts_RANAP_initialUE_PS(lai, rac, sai, nas, pars.sigc_id, grnc_id); } else { ret := ts_RANAP_initialUE_CS(lai, sai, nas, pars.sigc_id, grnc_id); } return valueof(ret); } /* build a RANAP InitialUE based on the TestHdlrParams */ function f_build_initial_ue(TestHdlrParams pars) return RANAP_PDU { var octetstring nas; if (pars.tx_sccp_cr_data_len == 0) { nas := f_rnd_octstring(10); } else { /* The test asks for an exact number of Optional Data bytes. */ /* First see what size the RANAP part of the payload data is, * to adjust the NAS PDU size to the size requested by the test (pars.tx_sccp_cr_data_len). */ var RANAP_PDU initial_ue := f_build_initial_ue_with_nas(pars, '00'O); var octetstring ranap_plus_one_byte_nas := enc_RANAP_PDU(initial_ue); var integer ranap_length := lengthof(ranap_plus_one_byte_nas) - 1; log("ranap_plus_one_byte_nas = ", lengthof(ranap_plus_one_byte_nas), " bytes, ", initial_ue, " = ", ranap_plus_one_byte_nas); log("ranap_length = ", ranap_length); /* SCCP CR has a payload length limit of 130 bytes. To trigger this limit, the RANAP + NAS PDU has to be * > 130 bytes. It doesn't need to be 131 bytes in the NAS PDU alone, but let's just make it definitely * large enough. To test for this limit, pars.tx_sccp_cr_data_len asks for a specific amount of data len. */ nas := f_rnd_octstring(pars.tx_sccp_cr_data_len - ranap_length); } var RANAP_PDU ret := f_build_initial_ue_with_nas(pars, nas); if (pars.tx_sccp_cr_data_len != 0) { for (var integer attempts := 0; attempts < 2; attempts := attempts + 1) { var octetstring check_len := enc_RANAP_PDU(ret); log("final RANAP PDU length = ", lengthof(check_len)); if (lengthof(check_len) == pars.tx_sccp_cr_data_len) { return ret; } nas := f_rnd_octstring(lengthof(nas) + (pars.tx_sccp_cr_data_len - lengthof(check_len))); log("that was off, changed NAS length to ", lengthof(nas), " and trying again"); ret := f_build_initial_ue_with_nas(pars, nas); } setverdict(fail, "Ended up with wrong Optional Data length"); mtc.stop; } return ret; } /* build a RANAP RAB AssignmentResponse based on the TestHdlrParams */ function f_build_rab_ass_resp(TestHdlrParams pars) return RANAP_PDU { var template RAB_SetupOrModifiedList rab_sml; rab_sml := ts_RAB_SMdL(t_RAB_id(23), f_ts_RAB_TLA("192.168.1.23"), t_RAB_binding_port(1234)); return valueof(ts_RANAP_RabAssResp(rab_sml)); } /* HNB <- CN: Iu-ReleaseCmd, HNB -> CN: Iu-ReleaseCompl */ function f_cn_iu_release_procedure(boolean mgcp_teardown := false, boolean pfcp_teardown := false) runs on ConnHdlr { var RANAP_PDU tx; timer T; tx := valueof(ts_RANAP_IuReleaseCommand(g_pars.rab_rel_cause)); f_iu2iuh(tx); if (mgcp_teardown) { T.start(5.0); alt { [] as_mgcp_dlcx() {} [] T.timeout { setverdict(fail, "Timeout waiting for DLCX"); } } T.stop; } if (pfcp_teardown) { var PDU_PFCP m; m := f_pfcp_expect(tr_PFCP_Session_Del_Req(g_pars.pfcp_pars.upf_f_seid.seid)); PFCP.send(ts_PFCP_Session_Del_Resp(m.sequence_number, g_pars.pfcp_pars.hnbgw_f_seid.seid)); /* ask PFCPEM to *not* route PDUs with this specific SEID to us */ f_PFCPEM_unsubscribe_seid(g_pars.pfcp_pars.upf_f_seid.seid); } tx := valueof(ts_RANAP_IuReleaseComplete()); f_iuh2iu_disconnect(tx, RUA_IEs.Cause:{radioNetwork:=normal}); /* Once CN receives Iu-ReleaseComplete, it tears down the SCCP conn under Iu: */ BSSAP.send(ts_MSC_CONN_PRIM_DISC_REQ(sccp_par_reason_end_user_originated)); /* There's no signalling from lower layers regarding when RLC is * received, hence only make sure the IUT is not sending an RLSD on its own * after a timeout, which would mean it didn't process our RLSD */ T.start(2.0); alt { [] BSSAP.receive(tr_MSC_CONN_PRIM_DISC_IND) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Rx Unexpected RLSD to our RLSD"); } [] T.timeout { setverdict(pass); } } } /* Reply to a received CRCX with an OK (or the reply configured in cpars), using the given parameters. * Return true when an OK reply was sent, false otherwise. * Count occurrence of Osmux, include Osmux parameters in the reply if necessary. */ function f_handle_crcx(inout MgcpParameters pars, MgcpCommand mgcp_cmd) return template MgcpResponse { var MgwResponse conn := pars.mgw_conn_ran; if (pars.got_crcx_count > 0) { conn := pars.mgw_conn_cn; } pars.got_crcx_count := pars.got_crcx_count + 1; var MgcpMessage mgcp_msg := { command := mgcp_cmd } var template MgcpResponse mgcp_resp; var MgcpOsmuxCID osmux_cid; var MgcpCallId call_id := f_MgcpCmd_extract_call_id(mgcp_cmd); if (ispresent(pars.mgcp_call_id)) { if (pars.mgcp_call_id != call_id) { setverdict(fail, "CRCX contained unexpected call id. Expected:", pars.mgcp_call_id, " got:", call_id); mtc.stop; } } else { pars.mgcp_call_id := call_id; } /* When the endpoint contains a wildcard we keep the endpoint * identifier we have set up in pars. Otherwise we use the * endpoint name that the call agent has supplied */ if (match(mgcp_cmd.line.ep, t_MGCP_EP_wildcard) == false) { pars.mgcp_ep := mgcp_cmd.line.ep; } if (conn.resp == -1) { /* Reply with rror */ var MgcpResponse mgcp_rsp := { line := { code := "542", trans_id := mgcp_cmd.line.trans_id, string := "FORCED_FAIL" }, sdp := omit } var MgcpParameter mgcp_rsp_param := { code := "Z", val := pars.mgcp_ep }; mgcp_rsp.params[0] := mgcp_rsp_param; return mgcp_rsp; } if (conn.resp == 0) { /* Do not reply at all */ return omit; } if (conn.resp != 1) { setverdict(fail, "Unexpected value for pars.mgw_conn_*.resp, expect -1, 0 or 1"); mtc.stop; } var SDP_Message sdp := valueof(ts_SDP(conn.mgw_rtp_ip, conn.mgw_rtp_ip, hex2str(pars.mgcp_call_id), "42", conn.mgw_rtp_port, { int2str(pars.rtp_payload_type) }, { valueof(ts_SDP_rtpmap(pars.rtp_payload_type, pars.rtp_sdp_format)), valueof(ts_SDP_ptime(20)) })); if (f_mgcp_contains_par(mgcp_msg, "X-OSMUX")) { if (not pars.use_osmux) { setverdict(fail, "MSC sent X-Osmux parameter in MGCP, but not expecting any Osmux"); mtc.stop; } pars.got_osmux_count := pars.got_osmux_count + 1; /* we expect MSC to use wildcard here, i.e. osmux_cid == -1 */ osmux_cid := f_MgcpCmd_extract_osmux_cid(mgcp_cmd); log("f_handle_crcx(): got Osmux CID: ", osmux_cid); if (osmux_cid != -1) { setverdict(fail, "MSC using unexpected CID " & int2str(osmux_cid) & " != -1"); mtc.stop; } osmux_cid := 0; mgcp_resp := ts_CRCX_ACK_osmux(mgcp_cmd.line.trans_id, conn.mgcp_connection_id, osmux_cid, sdp); } else { mgcp_resp := ts_CRCX_ACK(mgcp_cmd.line.trans_id, conn.mgcp_connection_id, sdp); } f_mgcp_par_append(mgcp_resp.params, ts_MgcpParSpecEP(pars.mgcp_ep)); return mgcp_resp; } function f_rab_ass_req_cs() runs on ConnHdlr { var MgcpCommand mgcp_cmd; var RANAP_PDU tx; var template RAB_SetupOrModifyList rab_sml; timer T := 5.0; /* This code block cannot be executed by more than one component at a time because the * RANAP RAB Ass Request triggers the IUT to send MGCP CRCX PDU(s), which needs to be * routed to the respective ConnHdlr component (us). This is why we need to ensure that * one ConnHdlr is triggering MGCP CRCX wildcard at the given moment of time. */ f_Mutex_lock(__BFILE__, __LINE__); f_create_mgcp_expect(ExpectCriteria:{omit,omit,omit}); /* Send RAB Assignment Request */ rab_sml := ts_RAB_SML(t_RAB_id(23), f_ts_RAB_TLA(g_pars.mgcp_pars.cn_rtp_ip), t_RAB_binding_port(g_pars.mgcp_pars.cn_rtp_port)); tx := valueof(ts_RANAP_RabAssReq(rab_sml)); BSSAP.send(tx); T.start; /* Handle MGCP CRCX */ alt { [] MGCP.receive(tr_CRCX) -> value mgcp_cmd { log("CRCX1", mgcp_cmd); /* Unregister and unlock the mutex, enabling other components to * establish MGCP sessions after us. */ f_Mutex_unlock(__BFILE__, __LINE__); var template MgcpResponse mgcp_rsp := f_handle_crcx(g_pars.mgcp_pars, mgcp_cmd); MGCP.send(valueof(mgcp_rsp)); } [] T.timeout { /* No need to f_Mutex_unlock since we are exiting anyway... */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for MGCP"); } } T.stop; /* Expect RAB Assignment Request with IP/port from CRCX ACK via Iuh */ rab_sml := ts_RAB_SML(t_RAB_id(23), f_ts_RAB_TLA(g_pars.mgcp_pars.mgw_conn_ran.mgw_rtp_ip), t_RAB_binding_port(g_pars.mgcp_pars.mgw_conn_ran.mgw_rtp_port)); tx := valueof(ts_RANAP_RabAssReq(rab_sml)); f_rua_expect(tx); } friend function f_rab_ass_resp_cs() runs on ConnHdlr { var MgcpCommand mgcp_cmd; var RANAP_PDU tx; var template RAB_SetupOrModifiedList rab_smdl; /* Send back RAB Assignment Response via Iuh */ rab_smdl := ts_RAB_SMdL(t_RAB_id(23), f_ts_RAB_TLA(g_pars.mgcp_pars.hnb_rtp_ip), t_RAB_binding_port(g_pars.mgcp_pars.hnb_rtp_port)); tx := valueof(ts_RANAP_RabAssResp(rab_smdl)); RUA.send(tx); interleave { /* Expect MDCX with IP/port from RAB Assignment Response */ [] MGCP.receive(tr_MDCX(tr_SDP(g_pars.mgcp_pars.hnb_rtp_ip, g_pars.mgcp_pars.hnb_rtp_port))) -> value mgcp_cmd { var HostName mgw_rtp_ip; var boolean exp_rua_rab_reass := false; log("MDCX1", mgcp_cmd); if (ispresent(g_pars.mgcp_pars.mgw_conn_ran.mgw_rtp_ip_mdcx)) { mgw_rtp_ip := g_pars.mgcp_pars.mgw_conn_ran.mgw_rtp_ip_mdcx; if (g_pars.mgcp_pars.mgw_conn_ran.mgw_rtp_ip != g_pars.mgcp_pars.mgw_conn_ran.mgw_rtp_ip_mdcx) { exp_rua_rab_reass := true; } } else { mgw_rtp_ip := g_pars.mgcp_pars.mgw_conn_ran.mgw_rtp_ip; } /* Verify SDP of MDCX */ var SDP_Message sdp := valueof(ts_SDP(mgw_rtp_ip, mgw_rtp_ip, hex2str(g_pars.mgcp_pars.mgcp_call_id), "42", g_pars.mgcp_pars.mgw_conn_ran.mgw_rtp_port, { int2str(g_pars.mgcp_pars.rtp_payload_type) }, { valueof(ts_SDP_rtpmap(g_pars.mgcp_pars.rtp_payload_type, g_pars.mgcp_pars.rtp_sdp_format)), valueof(ts_SDP_ptime(20)) } )); var template MgcpResponse mgcp_rsp := ts_MDCX_ACK(mgcp_cmd.line.trans_id, g_pars.mgcp_pars.mgw_conn_ran.mgcp_connection_id, sdp); MGCP.send(valueof(mgcp_rsp)); /* If IP address changed, we expect HNBGW to Modify the RAB through RAB-Ass-Req: */ if (exp_rua_rab_reass) { var template RAB_SetupOrModifyList rab_sml; /* Expect RAB Assignment Request with IP/port from MDCX ACK via Iuh */ rab_sml := ts_RAB_SML(t_RAB_id(23), f_ts_RAB_TLA(g_pars.mgcp_pars.mgw_conn_ran.mgw_rtp_ip_mdcx), t_RAB_binding_port(g_pars.mgcp_pars.mgw_conn_ran.mgw_rtp_port)); tx := valueof(ts_RANAP_RabAssReq(rab_sml)); f_rua_expect(tx); /* Send back RAB Assignment Response via Iuh */ rab_smdl := ts_RAB_SMdL(t_RAB_id(23), f_ts_RAB_TLA(g_pars.mgcp_pars.hnb_rtp_ip), t_RAB_binding_port(g_pars.mgcp_pars.hnb_rtp_port)); tx := valueof(ts_RANAP_RabAssResp(rab_smdl)); RUA.send(tx); /* This shouldn't trigger any MGCP, since nothing changed on the HNB IP side. Continue below. */ } } /* Handle CRCX for second leg of endpoint, answer with IP/port */ [] MGCP.receive(tr_CRCX(g_pars.mgcp_pars.mgcp_ep, tr_SDP(g_pars.mgcp_pars.cn_rtp_ip, g_pars.mgcp_pars.cn_rtp_port))) -> value mgcp_cmd { log("CRCX2", mgcp_cmd); /* Verify SDP of CRCX */ var template MgcpResponse mgcp_rsp := f_handle_crcx(g_pars.mgcp_pars, mgcp_cmd); MGCP.send(valueof(mgcp_rsp)); } } /* Expect RAB Assignment Response with IP/port from second CRCX ACK */ rab_smdl := ts_RAB_SMdL(t_RAB_id(23), f_ts_RAB_TLA(g_pars.mgcp_pars.mgw_conn_cn.mgw_rtp_ip), t_RAB_binding_port(g_pars.mgcp_pars.mgw_conn_cn.mgw_rtp_port)); tx := valueof(ts_RANAP_RabAssResp(rab_smdl)); f_bssap_expect(tx); } function f_create_rab_cs() runs on ConnHdlr { f_rab_ass_req_cs(); f_rab_ass_resp_cs(); } function f_rab_ass_req_ps_with_pfcp() runs on ConnHdlr { var RANAP_PDU tx; var RANAP_PDU rx; var PDU_PFCP m; /* This code block cannot be executed by more than one component at a time because * the RANAP RAB Ass Request triggers the IUT to send PFCP Session Establishment * Request PDU(s), which needs to be routed to the respective ConnHdlr component (us). * As per 3GPP TS 29.244, section 7.2.2.4.2, these PFCP PDUs shall all have SEID=0, * so that the PFCPEM component cannot route them unambiguously. This is why we * need to ensure that only one ConnHdlr is triggering PFCP session establishment * at the given moment of time. */ f_Mutex_lock(__BFILE__, __LINE__); /* Subscribe for PFCP Session Establishment Request PDU(s), which are * expected to have SEID set to 0, as per 3GPP TS 29.244, section 7.2.2.4.2. */ f_PFCPEM_subscribe_seid(c_SEID0); /* Send RAB Assignment Request */ var template RAB_SetupOrModifyList rab_sml; rab_sml := ts_RAB_SML_ps(t_RAB_id(23), f_ts_RAB_TLA(g_pars.gtp_pars.core.remote.addr), g_pars.gtp_pars.core.remote.teid); tx := valueof(ts_RANAP_RabAssReq(rab_sml)); BSSAP.send(tx); /* Expect PFCP Session Establishment Request. */ m := f_pfcp_expect(tr_PFCP_Session_Est_Req()); /* Ask PFCPEM to route PDUs with to be indicated F-SEID to us. */ f_PFCPEM_subscribe_seid(g_pars.pfcp_pars.upf_f_seid.seid); /* We're done establishing PFCP sessions, so at this point we no longer expect to * receive Session Establishment Request PDUs with SEID=0. Unregister and unlock * the mutex, enabling other components to establish PFCP sessions after us. */ f_PFCPEM_unsubscribe_seid(c_SEID0); f_Mutex_unlock(__BFILE__, __LINE__); var PFCP_Session_Establishment_Request serq := m.message_body.pfcp_session_establishment_request; /* Store HNBGW F-SEID for later: */ g_pars.pfcp_pars.hnbgw_f_seid := serq.CP_F_SEID; /* Acting as UPF, invent a new PFCP SEID to send to HNBGW. Respond to the Session Establishment. * The PFCP response must have the same sequence_number as the request. */ var template F_TEID f_teid1 := ts_PFCP_F_TEID_ipv4(g_pars.gtp_pars.core.local.teid, f_inet_addr(g_pars.gtp_pars.core.local.addr)); var template F_TEID f_teid2 := ts_PFCP_F_TEID_ipv4(g_pars.gtp_pars.access.local.teid, f_inet_addr(g_pars.gtp_pars.access.local.addr)); var template Created_PDR pdr1 := ts_PFCP_Created_PDR(pdr_id := serq.create_PDR_list[0].grouped_ie.pdr_id, local_F_TEID := f_teid1); var template Created_PDR pdr2 := ts_PFCP_Created_PDR(pdr_id := serq.create_PDR_list[1].grouped_ie.pdr_id, local_F_TEID := f_teid2); var template PDU_PFCP r := ts_PFCP_Session_Est_Resp(seq_nr := m.sequence_number, node_id := g_pars.pfcp_pars.upf_node_id, seid := g_pars.pfcp_pars.hnbgw_f_seid.seid, f_seid := g_pars.pfcp_pars.upf_f_seid, created_pdr := {pdr1, pdr2}); PFCP.send(r); /* Expect on Iuh: RAB Assignment Request with IP/port from PFCP Session Est Resp */ rab_sml := ts_RAB_SML_ps(t_RAB_id(23), f_ts_RAB_TLA(g_pars.gtp_pars.access.local.addr), g_pars.gtp_pars.access.local.teid); rx := valueof(ts_RANAP_RabAssReq(rab_sml)); f_rua_expect(rx); } function f_rab_ass_resp_ps_with_pfcp() runs on ConnHdlr { var RANAP_PDU tx; var RANAP_PDU rx; var template PDU_PFCP r; var PDU_PFCP m; /* Send back RAB Assignment Response via Iuh */ var template RAB_SetupOrModifiedList rab_smdl; rab_smdl := ts_RAB_SMdL_ps(t_RAB_id(23), f_ts_RAB_TLA(g_pars.gtp_pars.access.remote.addr), g_pars.gtp_pars.access.remote.teid); tx := valueof(ts_RANAP_RabAssResp(rab_smdl)); RUA.send(tx); m := f_pfcp_expect(tr_PFCP_Session_Mod_Req(g_pars.pfcp_pars.upf_f_seid.seid)); r := ts_PFCP_Session_Mod_Resp(m.sequence_number, g_pars.pfcp_pars.hnbgw_f_seid.seid); PFCP.send(r); rab_smdl := ts_RAB_SMdL_ps(t_RAB_id(23), f_ts_RAB_TLA(g_pars.gtp_pars.core.local.addr), g_pars.gtp_pars.core.local.teid); f_bssap_expect(tr_RANAP_RabAssResp(rab_smdl)); } function f_create_rab_ps_with_pfcp() runs on ConnHdlr { f_rab_ass_req_ps_with_pfcp(); f_rab_ass_resp_ps_with_pfcp(); } function f_rab_ass_req_ps_without_pfcp() runs on ConnHdlr { var RANAP_PDU tx; var RANAP_PDU rx; /* ask PFCPEM to route all PDUs to us */ f_PFCPEM_subscribe_bcast(); activate(as_disallow_pfcp()); var template RAB_SetupOrModifyList rab_sml; rab_sml := ts_RAB_SML_ps(t_RAB_id(23), f_ts_RAB_TLA(g_pars.gtp_pars.core.remote.addr), g_pars.gtp_pars.core.remote.teid); tx := valueof(ts_RANAP_RabAssReq(rab_sml)); BSSAP.send(tx); /* Expect on Iuh: unmodified RAB Assignment Request */ rx := valueof(ts_RANAP_RabAssReq(rab_sml)); f_rua_expect(rx); } function f_rab_ass_resp_ps_without_pfcp() runs on ConnHdlr { var RANAP_PDU tx; var RANAP_PDU rx; /* Send back RAB Assignment Response via Iuh */ var template RAB_SetupOrModifiedList rab_smdl; rab_smdl := ts_RAB_SMdL_ps(t_RAB_id(23), f_ts_RAB_TLA(g_pars.gtp_pars.access.remote.addr), g_pars.gtp_pars.access.remote.teid); tx := valueof(ts_RANAP_RabAssResp(rab_smdl)); RUA.send(tx); /* Expect on IuPS: unmodified RAB Assignment Response */ f_bssap_expect(tr_RANAP_RabAssResp(rab_smdl)); } function f_create_rab_ps_without_pfcp() runs on ConnHdlr { f_rab_ass_req_ps_without_pfcp(); f_rab_ass_resp_ps_without_pfcp(); } function f_create_rab_ps() runs on ConnHdlr { if (g_pars.pfcp_pars.pfcp_enabled) { f_create_rab_ps_with_pfcp(); } else { f_create_rab_ps_without_pfcp(); } } altstep as_mgcp_dlcx() runs on ConnHdlr { var MgcpCommand mgcp_cmd; [] MGCP.receive(tr_DLCX(g_pars.mgcp_pars.mgcp_ep)) -> value mgcp_cmd { log("DLCX", mgcp_cmd); MGCP.send(ts_DLCX_ACK2(mgcp_cmd.line.trans_id)); g_pars.mgcp_pars.got_dlcx_count := g_pars.mgcp_pars.got_dlcx_count + 1; if (g_pars.mgcp_pars.got_dlcx_count != g_pars.mgcp_pars.got_crcx_count) { repeat; } setverdict(pass); } } function f_pfcp_expect(template (present) PDU_PFCP exp_rx, float wait_time := 5.0) runs on ConnHdlr return PDU_PFCP { var PDU_PFCP rx; timer T := wait_time; T.start; alt { [] PFCP.receive(exp_rx) -> value rx { setverdict(pass); } [] PFCP.receive(PDU_PFCP:?) { setverdict(fail, "Got an unexpected PFCP message, was waiting for ", exp_rx); mtc.stop; } [] T.timeout { setverdict(fail, "Timeout waiting for PFCP ", exp_rx); mtc.stop; } } T.stop; return rx; } altstep as_disallow_pfcp() runs on ConnHdlr { [] PFCP.receive(PDU_PFCP:?) { setverdict(fail, "Received PFCP message, but no PFCP communication expected"); mtc.stop; } } function f_perform_compl_l3(octetstring nas, boolean do_clear := true, boolean expect_success := true) runs on ConnHdlr { timer T := 10.0; if (expect_success) { /* create an expect on the Iu side for the random NAS portion */ if (g_pars.expect_separate_sccp_cr) { f_ran_register_sccp_cr_without_payload(); } else { f_ran_register_exp(nas); } } /* send Connect via Iuh (creating a RUA connection) */ var RANAP_PDU tx := f_build_initial_ue_with_nas(g_pars, nas); RUA.send(RUA_Conn_Req:{g_pars.ps_domain, tx}); if (expect_success) { /* Expect same message to arrive at CN */ f_bssap_expect(tx); } else { RUA.receive(RUA_Disc_Ind:?); } } } /* module BSC_ConnectionHandler */