From 19b8d909e56cc998818e1b846e76759aac91e1f0 Mon Sep 17 00:00:00 2001 From: Daniel Willmann Date: Wed, 5 Jan 2022 09:12:34 +0100 Subject: [PATCH] Add hnbgw tests Some tests for osmo-hnbgw with mgw support: * hnb register * ranap connect/disconnect * rab assignment/release Change-Id: I0a2fb795aec83337eda8d9972e6ff264ead61076 --- hnbgw/HNBGW_Tests.cfg | 18 + hnbgw/HNBGW_Tests.default | 28 + hnbgw/HNBGW_Tests.ttcn | 1019 ++++++++++++++++++++++++++++++++++++ hnbgw/gen_links.sh | 104 ++++ hnbgw/osmo-hnbgw.cfg | 35 ++ hnbgw/osmo-stp.cfg | 52 ++ hnbgw/regen_makefile.sh | 40 ++ library/RUA_Emulation.ttcn | 400 ++++++++++++++ 8 files changed, 1696 insertions(+) create mode 100644 hnbgw/HNBGW_Tests.cfg create mode 100644 hnbgw/HNBGW_Tests.default create mode 100644 hnbgw/HNBGW_Tests.ttcn create mode 100755 hnbgw/gen_links.sh create mode 100644 hnbgw/osmo-hnbgw.cfg create mode 100644 hnbgw/osmo-stp.cfg create mode 100755 hnbgw/regen_makefile.sh create mode 100644 library/RUA_Emulation.ttcn diff --git a/hnbgw/HNBGW_Tests.cfg b/hnbgw/HNBGW_Tests.cfg new file mode 100644 index 000000000..8cf004815 --- /dev/null +++ b/hnbgw/HNBGW_Tests.cfg @@ -0,0 +1,18 @@ +[ORDERED_INCLUDE] +# Common configuration, shared between test suites +"../Common.cfg" +# testsuite specific configuration, not expected to change +"./HNBGW_Tests.default" + +# Local configuration below + +[LOGGING] + +[TESTPORT_PARAMETERS] + +[MODULE_PARAMETERS] + +[MAIN_CONTROLLER] + +[EXECUTE] +HNBGW_Tests.control diff --git a/hnbgw/HNBGW_Tests.default b/hnbgw/HNBGW_Tests.default new file mode 100644 index 000000000..02cbcb13c --- /dev/null +++ b/hnbgw/HNBGW_Tests.default @@ -0,0 +1,28 @@ +[LOGGING] +mtc.FileMask := LOG_ALL | TTCN_DEBUG | TTCN_MATCHING | DEBUG_ENCDEC; + +[TESTPORT_PARAMETERS] +*.HNBGWVTY.CTRL_MODE := "client" +*.HNBGWVTY.CTRL_HOSTNAME := "127.0.0.1" +*.HNBGWVTY.CTRL_PORTNUM := "4261" +*.HNBGWVTY.CTRL_LOGIN_SKIPPED := "yes" +*.HNBGWVTY.CTRL_DETECT_SERVER_DISCONNECTED := "yes" +*.HNBGWVTY.CTRL_READMODE := "buffered" +*.HNBGWVTY.CTRL_CLIENT_CLEANUP_LINEFEED := "yes" +*.HNBGWVTY.CTRL_DETECT_CONNECTION_ESTABLISHMENT_RESULT := "yes" +*.HNBGWVTY.PROMPT1 := "OsmoHNBGW> " +*.STATSVTY.CTRL_MODE := "client" +*.STATSVTY.CTRL_HOSTNAME := "127.0.0.1" +*.STATSVTY.CTRL_PORTNUM := "4262" +*.STATSVTY.CTRL_LOGIN_SKIPPED := "yes" +*.STATSVTY.CTRL_DETECT_SERVER_DISCONNECTED := "yes" +*.STATSVTY.CTRL_READMODE := "buffered" +*.STATSVTY.CTRL_CLIENT_CLEANUP_LINEFEED := "yes" +*.STATSVTY.CTRL_DETECT_CONNECTION_ESTABLISHMENT_RESULT := "yes" +*.STATSVTY.PROMPT1 := "OsmoHNBGW> " +*.LLSK.socket_type := "SEQPACKET" + +[MODULE_PARAMETERS] +Osmocom_VTY_Functions.mp_prompt_prefix := "OsmoHNBGW"; + +[EXECUTE] diff --git a/hnbgw/HNBGW_Tests.ttcn b/hnbgw/HNBGW_Tests.ttcn new file mode 100644 index 000000000..aea7de825 --- /dev/null +++ b/hnbgw/HNBGW_Tests.ttcn @@ -0,0 +1,1019 @@ +module HNBGW_Tests { + +/* Integration Tests for OsmoHNBGW + * (C) 2021 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. + */ + +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 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_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 RAN_Adapter 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; + +modulepar { + /* IP address at which the HNodeB can be reached */ + charstring mp_hnodeb_ip := "127.0.0.1"; + integer mp_hnodeb_port := -1; + + /* IP address at which the test binds */ + charstring mp_hnbgw_ip := "127.0.0.1"; + integer mp_hnbgw_iuh_port := 29169; + + charstring mp_mgw_ip := "127.0.0.1"; + integer mp_mgw_port := 2427; + + RAN_Configuration mp_msc_cfg := { + transport := RANAP_TRANSPORT_IuCS, + sccp_service_type := "mtp3_itu", + sctp_addr := { 23905, "127.0.0.1", 2905, "127.0.0.1" }, + own_pc := 188, /* 0.23.4 first MSC emulation */ + own_ssn := 142, + peer_pc := 189, /* 0.23.5 osmo-hnbgw */ + peer_ssn := 142, + sio := '83'O, + rctx := 1 + }; + RAN_Configuration mp_sgsn_cfg := { + transport := RANAP_TRANSPORT_IuCS, + sccp_service_type := "mtp3_itu", + sctp_addr := { 23906, "127.0.0.1", 2905, "127.0.0.1" }, + own_pc := 185, /* 0.23.1 first SGSN emulation */ + own_ssn := 142, + peer_pc := 189, /* 0.23.5 osmo-hnbgw */ + peer_ssn := 142, + sio := '83'O, + rctx := 2 + }; +} + +function MSC_UnitdataCallback(RANAP_PDU ranap) runs on RAN_Emulation_CT return template RANAP_PDU { + // TODO: Actually implement unitdata handling + return ts_RANAP_Reset(ts_RanapCause_om_intervention, cs_domain); +} + +const RanOps MSC_RanOps := { + ranap_create_cb := refers(RAN_Emulation.RanapExpectedCreateCallback), + ranap_unitdata_cb := refers(MSC_UnitdataCallback), + ps_domain := false, + decode_dtap := false, + role_ms := false, + protocol := RAN_PROTOCOL_RANAP, + transport := RANAP_TRANSPORT_IuCS, + use_osmux := false, + sccp_addr_local := omit, + sccp_addr_peer := omit +} + +type record CrcxResponse { + integer resp, + HostName mgw_rtp_ip, + 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, + CrcxResponse mgw_conn_1, + CrcxResponse mgw_conn_2, + 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 +} + +template (value) MgcpParameters t_MgcpParams := { + got_crcx_count := 0, + got_dlcx_count := 0, + mgcp_call_id := omit, + mgcp_ep := "rtpbridge/1@mgw", + mgw_conn_1 := { + resp := 1, + mgw_rtp_ip := "127.1.2.1", + mgw_rtp_port := 10000, + mgcp_connection_id := '11111'H + }, + mgw_conn_2 := { + resp := 1, + mgw_rtp_ip := "127.1.2.2", + mgw_rtp_port := 20000, + mgcp_connection_id := '22222'H + }, + rtp_payload_type := 23, + rtp_sdp_format := "FOO", + 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 TestHdlrParams { + integer hnb_idx, + hexstring imsi, + boolean ps_domain, + MgcpParameters mgcp_pars optional, + HnbConfig hnb optional +} + +/* 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) + */ +type component ConnHdlr extends RAN_ConnHdlr, MGCP_ConnHdlr, RUA_ConnHdlr { + var integer g_sccp_conn_id; + var TestHdlrParams g_pars; + timer g_Tguard; +} + + +const MGCPOps MSC_MGCPOps := { + create_cb := refers(MGCP_Emulation.ExpectedCreateCallback), + unitdata_cb := refers(MGCP_Emulation.DummyUnitdataCallback) +} + +function f_create_ranap_exp(octetstring l3_enc) runs on ConnHdlr { + BSSAP_PROC.call(RAN_register:{l3_enc, self}) { + [] BSSAP_PROC.getreply(RAN_register:{?, ?}) {}; + } +} + + +const integer NUM_HNB := 1; + +const hexstring ranap_cm_service_req := '001340400000060003400100000f40060000f11028b6003a40080000f110ffffffff0010400e0d052411035758a605f44e9d4aef004f400300001c0056400500f1100017'H; +const hexstring ranap_auth_req := '00144032000002001040262505120217dc146aeac56cb5ff6d5fb51f47f19220108ca5a6d0c8110000b9e9272498872764003b400100'H; +const hexstring ranap_auth_resp := '001440140000010010400d0c0554ccbdd0302104002f3ae4'H; +const hexstring ranap_paging := '000e401e0000030003400100001740095000010100000000f10040400500b6cf4773'H; +const hexstring ranap_rab_ass_req := '0000005900000100364052000001003500487824cd80102fa7201a2c0000f44c080a028000514000272028140067400000222814003c40000000503d02000227c03500010a0901a200000000000000000000000000401f4a0000400100'H; +const hexstring iu_release_compl := '20010003000000'H; + +type record HnbConfig { + LocationAreaIdentification lai, + integer sac +} + +type component test_CT extends CTRL_Adapter_CT { + var boolean g_initialized := false; + + /********************* Iu side */ + var RAN_Adapter g_msc; + var RAN_Adapter g_sgsn; + /* SGSN IuPS missing */ + + /********************* Iuh side */ + var HnbConfig g_hnb_cfg[NUM_HNB]; + var Iuh_Emulation_CT vc_Iuh[NUM_HNB]; + var RUA_Emulation_CT vc_RUA[NUM_HNB]; + port HNBAP_PT HNBAP[NUM_HNB]; + + var MGCP_Emulation_CT vc_MGCP; + port TELNETasp_PT HNBGWVTY; + /* global test case guard timer (actual timeout value is set in f_init()) */ + timer T_guard := 30.0; +} + +/* global altstep for global guard timer; */ +altstep as_Tguard() runs on test_CT { + [] T_guard.timeout { + setverdict(fail, "Timeout of T_guard"); + mtc.stop; + } +} + +friend function f_logp(TELNETasp_PT pt, charstring log_msg) +{ + // log on TTCN3 log output + log(log_msg); + // log in stderr log + f_vty_transceive(pt, "logp lglobal notice TTCN3 f_logp(): " & log_msg); +} + +function f_init_vty(charstring id := "foo") runs on test_CT { + if (HNBGWVTY.checkstate("Mapped")) { + /* skip initialization if already executed once */ + return; + } + map(self:HNBGWVTY, system:HNBGWVTY); + f_vty_set_prompts(HNBGWVTY); + f_vty_transceive(HNBGWVTY, "enable"); +} + +function f_init_mgcp(charstring id) runs on test_CT { + id := id & "-MGCP"; + var MGCPOps ops := { + create_cb := refers(MGCP_Emulation.ExpectedCreateCallback), + unitdata_cb := refers(MGCP_Emulation.DummyUnitdataCallback) + } + var MGCP_conn_parameters pars := { + callagent_ip := mp_hnbgw_ip, + callagent_udp_port := -1, + mgw_ip := mp_mgw_ip, + mgw_udp_port := mp_mgw_port, + multi_conn_mode := false + } + + vc_MGCP := MGCP_Emulation_CT.create(id); + map(vc_MGCP:MGCP, system:MGCP_CODEC_PT); + vc_MGCP.start(MGCP_Emulation.main(ops, pars, id)); +} + +function f_init_hnodeb(charstring id, integer hnb_idx, RuaOps rua_ops) runs on test_CT { + id := id & "-Iuh" & int2str(hnb_idx); + + /* Iuh lower layer (RUA/HNBAP demux) */ + var Iuh_conn_parameters iuh_pars; + iuh_pars.remote_ip := mp_hnbgw_ip; + iuh_pars.remote_sctp_port := mp_hnbgw_iuh_port; + iuh_pars.local_ip := mp_hnodeb_ip; + iuh_pars.local_sctp_port := mp_hnodeb_port + hnb_idx; + vc_Iuh[hnb_idx] := Iuh_Emulation_CT.create(id); + connect(self:HNBAP[hnb_idx], vc_Iuh[hnb_idx]:HNBAP); + + vc_RUA[hnb_idx] := RUA_Emulation_CT.create(id & "-RUA"); + connect(vc_RUA[hnb_idx]:RUA, vc_Iuh[hnb_idx]:RUA); + + /* Start Iuh side components */ + vc_Iuh[hnb_idx].start(Iuh_Emulation.main(iuh_pars, id)); + vc_RUA[hnb_idx].start(RUA_Emulation.main(rua_ops, id & "-RUA")); +} + +/* global initialization function */ +function f_init(charstring id := "HBGW", float guard_timeout := 30.0) runs on test_CT { + + g_hnb_cfg[0] := { + lai := { + mcc_mnc := '00101'H, + lac := 2342 + }, + sac := 55 + } + T_guard.start(guard_timeout); + activate(as_Tguard()); + + /* RUA/RANAP emulation on top of lower-layer Iuh */ + var RuaOps rua_ops := { + create_cb := refers(IuhRanapCreateCallback), + unitdata_cb := refers(IuhRanapUnitdataCallback) + }; + for (var integer i := 0; i < NUM_HNB; i := i+1) { + f_init_hnodeb(testcasename(), i, rua_ops); + } + + /* MSC emulation */ + var RanOps ranops := { + ranap_create_cb := refers(RAN_Emulation.RanapExpectedCreateCallback), + ranap_unitdata_cb := omit, + ps_domain := false, + decode_dtap := false, + role_ms := false, + protocol := RAN_PROTOCOL_RANAP, + transport := RANAP_TRANSPORT_IuCS, + use_osmux := false, + sccp_addr_local := omit, + sccp_addr_peer := omit + }; + f_ran_adapter_init(g_msc, mp_msc_cfg, "HNBGW_Test", ranops); + f_ran_adapter_start(g_msc); + + /* SGSN emulation */ + ranops.ps_domain := true; + f_ran_adapter_init(g_sgsn, mp_sgsn_cfg, "HNBGW_Test", ranops); + f_ran_adapter_start(g_sgsn); + + f_init_mgcp(id); + f_init_vty("VirtHNBGW"); +} + +friend function f_shutdown_helper() runs on test_CT { + all component.stop; + setverdict(pass); + mtc.stop; +} + +/* helper function to start all of the simulated hNodeBs */ +function f_start_hnbs() runs on test_CT { + for (var integer i:= 0; i < NUM_HNB; i := i+1) { + f_hnbap_register(i); + } +} + +/*********************************************************************** + * code running in test_CT, preparing start of per-UE ConnHdlr + ***********************************************************************/ + +/* inbound RUA connection establishment on Iuh side */ +function IuhRanapCreateCallback(ContextId context_id, RUA_IEs.CN_DomainIndicator domain, charstring id) +runs on RUA_Emulation_CT return RUA_ConnHdlr { + log("CreateCallback"); + return null; +} + +/* inbound RUA connectionless data on Iuh side */ +function IuhRanapUnitdataCallback(RANAP_PDU ranap) +runs on RUA_Emulation_CT return template RANAP_PDU { + log("UnitdataCallback"); + return omit; +} + +private function f_start_handler_create(TestHdlrParams pars) runs on test_CT return ConnHdlr { + var ConnHdlr vc_conn; + var charstring id := testcasename() & int2str(pars.hnb_idx); + + vc_conn := ConnHdlr.create(id); + + /* Iuh RUA part */ + connect(vc_conn:RUA, vc_RUA[pars.hnb_idx]:CLIENT); + + if (pars.ps_domain) { + /* SGSN side */ + connect(vc_conn:BSSAP, g_sgsn.vc_RAN:CLIENT); + connect(vc_conn:BSSAP_PROC, g_sgsn.vc_RAN:PROC); + } else { + /* MSC side */ + connect(vc_conn:BSSAP, g_msc.vc_RAN:CLIENT); + connect(vc_conn:BSSAP_PROC, g_msc.vc_RAN:PROC); + } + /* MGCP part */ + connect(vc_conn:MGCP, vc_MGCP:MGCP_CLIENT); + connect(vc_conn:MGCP_PROC, vc_MGCP:MGCP_PROC); + + return vc_conn; +} + +private function f_start_handler_run(ConnHdlr vc_conn, void_fn fn, TestHdlrParams pars) runs on test_CT { + var charstring id := testcasename(); // & int2str(pars.ran_idx); + /* We cannot use vc_conn.start(f_init_handler(fn, id, pars)); as we cannot have + * a stand-alone 'derefers()' call, see https://www.eclipse.org/forums/index.php/t/1091364/ */ + pars.hnb := g_hnb_cfg[pars.hnb_idx]; + pars.mgcp_pars := valueof(t_MgcpParams); + vc_conn.start(derefers(fn)(id, pars)); +} + +function f_start_handler_with_pars(void_fn fn, template (value) TestHdlrParams pars) +runs on test_CT return ConnHdlr { + var ConnHdlr vc_conn; + vc_conn := f_start_handler_create(valueof(pars)); + f_start_handler_run(vc_conn, fn, valueof(pars)); + return vc_conn; +} + +/*********************************************************************** + * code running inside per-UE ConnHdlr + ***********************************************************************/ + +type function void_fn(charstring id, TestHdlrParams pars) runs on ConnHdlr; + +function f_init_handler(TestHdlrParams pars, float t_guard := 20.0) runs on ConnHdlr { + /* make parameters available via component variable */ + g_pars := pars; + /* start guard timer and activate it as default */ + g_Tguard.start(t_guard); + activate(as_Tguard_ConnHdlr()); + + /* TODO: CTRL? */ + /* TODO: VTY? */ +} + +/* 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; + } +} + +/* 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); + T.start; + + alt { + [] BSSAP.receive(exp_rx) -> value rx { + setverdict(pass); + } + [] T.timeout { + setverdict(fail, "Timeout waiting for Iu ", exp_rx); + } + } + return 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 { + var RANAP_PDU rx; + timer T := 5.0; + + if (istemplatekind(exp_rx, "omit")) { + exp_rx := tx; + } + + BSSAP.send(tx); + T.start; + + alt { + [] RUA.receive(exp_rx) -> value rx { + setverdict(pass); + } + [] T.timeout { + setverdict(fail, "Timeout waiting for Iuh ", exp_rx); + } + } + return rx; +} + +/* send RANAP on Iuh and expect it to show up on Iu */ +function f_iuh2iu_connect(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; + } + + /* create an expect on the Iu side for the random NAS portion */ + 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}); + + /* expect to receive it on the Iu side */ + T.start; + alt { + [] BSSAP.receive(exp_rx) -> value rx { + setverdict(pass); + } + [] T.timeout { + setverdict(fail, "Timeout waiting for Iu ", exp_rx); + } + } + return rx; +} + +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 := 5.0; + + if (istemplatekind(exp_rx, "omit")) { + exp_rx := tx; + } + + /* send it via Iuh (creating a RUA connection) */ + RUA.send(RUA_Disc_Req:{tx, cause}); + + /* expect to receive it on the Iu side */ + T.start; + alt { + [] BSSAP.receive(exp_rx) -> value rx { + } + [] T.timeout { + setverdict(fail, "Timeout waiting for Iu ", exp_rx); + return rx; + } + } + + /* expect disconnect on the Iu side */ + alt { + [] BSSAP.receive(RAN_Conn_Prim:MSC_CONN_PRIM_DISC_IND) { + setverdict(pass); + } + [] T.timeout { + setverdict(fail, "Timeout waiting for Iu disconnect"); + return rx; + } + + } + return rx; +} + +/* build a RANAP InitialUE based on the TestHdlrParams */ +friend function f_build_initial_ue(TestHdlrParams pars) return RANAP_PDU { + var LAI lai := { + pLMNidentity := hex2oct(pars.hnb.lai.mcc_mnc), + lAC := int2oct(pars.hnb.lai.lac, 2), + iE_Extensions := omit + }; + var SAI sai := { + pLMNidentity := lai.pLMNidentity, + lAC := lai.lAC, + sAC := int2oct(pars.hnb.sac, 2), + iE_Extensions := omit + } + var octetstring nas := f_rnd_octstring(10); + var IuSignallingConnectionIdentifier sigc_id := int2bit(f_rnd_int(1000), 24); + var GlobalRNC_ID grnc_id := { + pLMNidentity := lai.pLMNidentity, + rNC_ID := 2342 + } + + if (pars.ps_domain) { + var RAC rac := '00'O; + return valueof(ts_RANAP_initialUE_PS(lai, rac, sai, nas, sigc_id, grnc_id)); + } else { + return valueof(ts_RANAP_initialUE_CS(lai, sai, nas, sigc_id, grnc_id)); + } +} + +/* build a RANAP RAB AssignmentResponse based on the TestHdlrParams */ +friend 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)); +} + + +/*********************************************************************** + * HNBAP Testing + ***********************************************************************/ + + +function f_hnbap_register(integer hnb_idx := 0) runs on test_CT +{ + timer T := 2.0; + + HNBAP[hnb_idx].send(tr_HNBAP_HNBRegisterRequest(char2oct("TTCN3 HNodeB"), + '00F110'O, + int2bit(1 + hnb_idx, 28), + int2oct(2, 2), + int2oct(3, 1), + int2oct(4, 2))); + + T.start; + alt { + [] HNBAP[hnb_idx].receive(tr_HNBAP_HNBRegisterAccept(?)) { + setverdict(pass); + } + [] HNBAP[hnb_idx].receive(IUHEM_Event:?) { + repeat; + } + [] T.timeout { + setverdict(fail, "Timeout waiting for HNB Register Accept"); + } + } +} + +testcase TC_hnb_register() runs on test_CT { + f_init(); + f_hnbap_register(0); + f_shutdown_helper(); +} + +/*********************************************************************** + * RUA / RANAP Testing + ***********************************************************************/ + +private template (value) TestHdlrParams +t_pars(integer imsi_suffix, boolean ps_domain := false,integer hnb_idx := 0) := { + hnb_idx := hnb_idx, + imsi := f_gen_imsi(imsi_suffix), + ps_domain := ps_domain, + hnb := omit /* filled in later */ +} + +/* Create an Iuh connection; send InitialUE; expect it to appear on new SCCP conenction */ +friend function f_tc_initial_ue(charstring id, TestHdlrParams pars) runs on ConnHdlr { + f_init_handler(pars); + var RANAP_PDU tx := f_build_initial_ue(g_pars); + f_iuh2iu_connect(tx); +} +testcase TC_ranap_cs_initial_ue() runs on test_CT { + var ConnHdlr vc_conn; + + f_init(); + f_start_hnbs(); + + vc_conn := f_start_handler_with_pars(refers(f_tc_initial_ue), t_pars(1)); + vc_conn.done; +} +testcase TC_ranap_ps_initial_ue() runs on test_CT { + var ConnHdlr vc_conn; + + f_init(); + f_start_hnbs(); + + vc_conn := f_start_handler_with_pars(refers(f_tc_initial_ue), t_pars(2, true)); + vc_conn.done; +} + +/* 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 CrcxResponse conn := pars.mgw_conn_1; + if (pars.got_crcx_count > 0) { + conn := pars.mgw_conn_2; + } + 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; +} + +friend function f_create_rab(MgcpParameters pars) runs on ConnHdlr { + var MgcpCommand mgcp_cmd; + var RANAP_PDU tx; + var template RAB_SetupOrModifyList rab_sml; + var template RAB_SetupOrModifiedList rab_smdl; + timer T := 5.0; + + /* Send RAB Assignment Request */ + rab_sml := ts_RAB_SML(t_RAB_id(23), f_ts_RAB_TLA(pars.cn_rtp_ip), t_RAB_binding_port(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); + var template MgcpResponse mgcp_rsp := f_handle_crcx(pars, mgcp_cmd); + MGCP.send(valueof(mgcp_rsp)); + } + [] T.timeout { + setverdict(fail, "Timeout waiting for MGCP"); + } + } + + /* 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(pars.mgw_conn_1.mgw_rtp_ip), t_RAB_binding_port(pars.mgw_conn_1.mgw_rtp_port)); + tx := valueof(ts_RANAP_RabAssReq(rab_sml)); + + alt { + [] RUA.receive(tx) { + setverdict(pass); + } + [] T.timeout { + setverdict(fail, "Timeout waiting for Iuh ", tx); + } + } + + /* Send back RAB Assignment Response via Iuh */ + rab_smdl := ts_RAB_SMdL(t_RAB_id(23), f_ts_RAB_TLA(pars.hnb_rtp_ip), t_RAB_binding_port(pars.hnb_rtp_port)); + tx := valueof(ts_RANAP_RabAssResp(rab_smdl)); + RUA.send(tx); + T.start; + + interleave { + /* Expect MDCX with IP/port from RAB Assignment Response */ + [] MGCP.receive(tr_MDCX(tr_SDP(pars.hnb_rtp_ip, pars.hnb_rtp_port))) -> value mgcp_cmd { + log("MDCX1", mgcp_cmd); + /* Verify SDP of MDCX */ + var SDP_Message sdp := valueof(ts_SDP(pars.mgw_conn_1.mgw_rtp_ip, pars.mgw_conn_1.mgw_rtp_ip, hex2str(pars.mgcp_call_id), "42", pars.mgw_conn_1.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)) } )); + var template MgcpResponse mgcp_rsp := ts_MDCX_ACK(mgcp_cmd.line.trans_id, pars.mgw_conn_1.mgcp_connection_id, sdp); + MGCP.send(valueof(mgcp_rsp)); + } + /* Handle CRCX for second leg of endpoint, answer with IP/port */ + [] MGCP.receive(tr_CRCX(pars.mgcp_ep, tr_SDP(pars.cn_rtp_ip, pars.cn_rtp_port))) -> value mgcp_cmd { + log("CRCX2", mgcp_cmd); + /* Verify SDP of CRCX */ + var template MgcpResponse mgcp_rsp := f_handle_crcx(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(pars.mgw_conn_2.mgw_rtp_ip), t_RAB_binding_port(pars.mgw_conn_2.mgw_rtp_port)); + tx := valueof(ts_RANAP_RabAssResp(rab_smdl)); + + alt { + [] BSSAP.receive(tx) { + setverdict(pass); + } + [] T.timeout { + setverdict(fail, "Timeout waiting for Iuh ", tx); + } + } +} + +private altstep as_mgcp_dlcx(inout TestHdlrParams pars) runs on ConnHdlr { + var MgcpCommand mgcp_cmd; + + [] MGCP.receive(tr_DLCX(pars.mgcp_pars.mgcp_ep)) -> value mgcp_cmd { + log("DLCX", mgcp_cmd); + MGCP.send(ts_DLCX_ACK2(mgcp_cmd.line.trans_id)); + pars.mgcp_pars.got_dlcx_count := pars.mgcp_pars.got_dlcx_count + 1; + if (pars.mgcp_pars.got_dlcx_count == 1) { + repeat; + } + setverdict(pass); + } +} + +friend function f_tc_rab_assignment(charstring id, TestHdlrParams pars) runs on ConnHdlr { + var MgcpCommand mgcp_cmd; + var RANAP_PDU tx; + timer T := 5.0; + + f_init_handler(pars); + f_create_mgcp_expect(ExpectCriteria:{omit,omit,omit}); + + tx := f_build_initial_ue(g_pars); + f_iuh2iu_connect(tx); + + f_create_rab(pars.mgcp_pars); + + /* Send Iu Release */ + tx := valueof(ts_RANAP_IuReleaseCommand(ts_RanapCause_om_intervention)); + f_iu2iuh(tx); + + T.start; + alt { + [] as_mgcp_dlcx(pars) {} + [] T.timeout { + setverdict(fail, "Timeout waiting for DLCX"); + } + } + + tx := valueof(ts_RANAP_IuReleaseComplete()); + f_iuh2iu(tx); +} + +testcase TC_rab_assignment() runs on test_CT { + var ConnHdlr vc_conn; + f_init(); + f_start_hnbs(); + + vc_conn := f_start_handler_with_pars(refers(f_tc_rab_assignment), t_pars(3)); + vc_conn.done; +} + +friend function f_tc_rab_release(charstring id, TestHdlrParams pars) runs on ConnHdlr { + var MgcpCommand mgcp_cmd; + var RANAP_PDU tx; + timer T := 15.0; + + f_init_handler(pars); + f_create_mgcp_expect(ExpectCriteria:{omit,omit,omit}); + + tx := f_build_initial_ue(g_pars); + f_iuh2iu_connect(tx); + + f_create_rab(pars.mgcp_pars); + + /* Send RAB Release */ + tx := valueof(ts_RANAP_RabReleaseReq(ts_RAB_RL(t_RAB_id(23), ts_RanapCause_om_intervention))); + BSSAP.send(tx); + + T.start; + + alt { + [] as_mgcp_dlcx(pars) {} + [] T.timeout { + setverdict(fail, "Timeout waiting for DLCX"); + } + } + + alt { + [] RUA.receive(tx) { + setverdict(pass); + } + [] T.timeout { + setverdict(fail, "Timeout waiting for Iuh ", tx); + } + } + +} + +testcase TC_rab_release() runs on test_CT { + var ConnHdlr vc_conn; + f_init(); + f_start_hnbs(); + + vc_conn := f_start_handler_with_pars(refers(f_tc_rab_release), t_pars(5)); + vc_conn.done; +} + +/* Create an Iuh connection; send InitialUE; transceive data both directions */ +friend function f_tc_ranap_bidir(charstring id, TestHdlrParams pars) runs on ConnHdlr { + f_init_handler(pars); + + /* HNB -> MSC: InitialUE */ + f_iuh2iu_connect(f_build_initial_ue(g_pars)); + + /* MSC <- HNB: DirectTransfer */ + f_iu2iuh(ts_RANAP_DirectTransfer(f_rnd_octstring(10))); + /* MSC -> HNB: DirectTransfer */ + f_iuh2iu(ts_RANAP_DirectTransfer(f_rnd_octstring(10))); + + /* HNB <- MSC: CommonID */ + f_iu2iuh(ts_RANAP_CommonId(hex2oct(pars.imsi))); +} +testcase TC_ranap_cs_bidir() runs on test_CT { + var ConnHdlr vc_conn; + + f_init(); + f_start_hnbs(); + + vc_conn := f_start_handler_with_pars(refers(f_tc_ranap_bidir), t_pars(3)); + vc_conn.done; +} +testcase TC_ranap_ps_bidir() runs on test_CT { + var ConnHdlr vc_conn; + + f_init(); + f_start_hnbs(); + + vc_conn := f_start_handler_with_pars(refers(f_tc_ranap_bidir), t_pars(4, true)); + vc_conn.done; +} + + +private function f_tc_ranap_mo_disconnect(charstring id, TestHdlrParams pars) runs on ConnHdlr { + f_init_handler(pars); + + /* HNB -> MSC: InitialUE */ + f_iuh2iu_connect(f_build_initial_ue(g_pars)); + + /* MSC <- HNB: DirectTransfer */ + f_iu2iuh(ts_RANAP_DirectTransfer(f_rnd_octstring(10))); + /* MSC -> HNB: DirectTransfer */ + f_iuh2iu(ts_RANAP_DirectTransfer(f_rnd_octstring(10))); + + /* MSC <- HNB: RUA disconnect */ + f_iuh2iu_disconnect(ts_RANAP_IuReleaseComplete, RUA_IEs.Cause:{misc:=processing_overload}); +} +testcase TC_ranap_cs_mo_disconnect() runs on test_CT { + var ConnHdlr vc_conn; + + f_init(); + f_start_hnbs(); + + vc_conn := f_start_handler_with_pars(refers(f_tc_ranap_mo_disconnect), t_pars(5)); + vc_conn.done; +} +testcase TC_ranap_ps_mo_disconnect() runs on test_CT { + var ConnHdlr vc_conn; + + f_init(); + f_start_hnbs(); + + vc_conn := f_start_handler_with_pars(refers(f_tc_ranap_mo_disconnect), t_pars(6)); + vc_conn.done; +} + + + + +control { + execute(TC_hnb_register()); + execute(TC_ranap_cs_initial_ue()); + execute(TC_ranap_ps_initial_ue()); + execute(TC_ranap_cs_bidir()); + execute(TC_ranap_ps_bidir()); + execute(TC_rab_assignment()); + execute(TC_rab_release()); + execute(TC_ranap_cs_mo_disconnect()); + execute(TC_ranap_ps_mo_disconnect()); +} + +} diff --git a/hnbgw/gen_links.sh b/hnbgw/gen_links.sh new file mode 100755 index 000000000..5224980a4 --- /dev/null +++ b/hnbgw/gen_links.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +BASEDIR=../deps + +. ../gen_links.sh.inc + +#DIR=$BASEDIR/titan.TestPorts.UNIX_DOMAIN_SOCKETasp/src +#FILES="UD_PT.cc UD_PT.hh UD_PortType.ttcn UD_Types.ttcn" +#gen_links $DIR $FILES + +DIR=$BASEDIR/titan.Libraries.TCCUsefulFunctions/src +FILES="TCCInterface_Functions.ttcn TCCConversion_Functions.ttcn TCCConversion.cc TCCInterface.cc TCCInterface_ip.h" +FILES+=" TCCEncoding_Functions.ttcn TCCEncoding.cc " # GSM 7-bit coding +gen_links $DIR $FILES + +DIR=$BASEDIR/titan.TestPorts.Common_Components.Socket-API/src +FILES="Socket_API_Definitions.ttcn" +gen_links $DIR $FILES + +DIR=$BASEDIR/titan.ProtocolModules.MobileL3_v13.4.0/src +FILES="MobileL3_CC_Types.ttcn MobileL3_CommonIE_Types.ttcn MobileL3_GMM_SM_Types.ttcn MobileL3_MM_Types.ttcn MobileL3_RRM_Types.ttcn MobileL3_SMS_Types.ttcn MobileL3_SS_Types.ttcn MobileL3_Types.ttcn" +gen_links $DIR $FILES + +# Required by MGCP and IPA +DIR=$BASEDIR/titan.TestPorts.IPL4asp/src +FILES="IPL4asp_Functions.ttcn IPL4asp_PT.cc IPL4asp_PT.hh IPL4asp_PortType.ttcn IPL4asp_Types.ttcn IPL4asp_discovery.cc IPL4asp_protocol_L234.hh" +gen_links $DIR $FILES + +# required by SCCP Emulation +DIR=$BASEDIR/titan.ProtocolEmulations.M3UA/src +FILES="M3UA_Emulation.ttcn" +gen_links $DIR $FILES + +# required by SCCP Emulation +DIR=$BASEDIR/titan.TestPorts.MTP3asp/src +FILES="MTP3asp_PortType.ttcn MTP3asp_Types.ttcn" +gen_links $DIR $FILES + +DIR=$BASEDIR/titan.ProtocolEmulations.SCCP/src +FILES="SCCP_Emulation.ttcn SCCP_Mapping.ttcnpp SCCP_Types.ttcn SCCPasp_Types.ttcn" +gen_links $DIR $FILES + +DIR=$BASEDIR/titan.ProtocolModules.BSSMAP/src +FILES="BSSAP_Types.ttcn" +gen_links $DIR $FILES + +# required by M3UA_Emulation +DIR=$BASEDIR/titan.ProtocolModules.M3UA/src +FILES="M3UA_Types.ttcn" +gen_links $DIR $FILES + +# required by M3UA_Emulation +DIR=$BASEDIR/titan.TestPorts.SCTPasp/src +FILES="SCTPasp_PT.cc SCTPasp_PT.hh SCTPasp_PortType.ttcn SCTPasp_Types.ttcn" +gen_links $DIR $FILES + +# required by SCCP Emulation +DIR=$BASEDIR/titan.ProtocolEmulations.M3UA/src +FILES="M3UA_Emulation.ttcn" +gen_links $DIR $FILES + +DIR=$BASEDIR/titan.TestPorts.UNIX_DOMAIN_SOCKETasp/src +FILES="UD_PT.cc UD_PT.hh UD_PortType.ttcn UD_Types.ttcn" +gen_links $DIR $FILES + +DIR=$BASEDIR/titan.ProtocolModules.SDP/src +FILES="SDP_EncDec.cc SDP_Types.ttcn SDP_parse_.tab.c SDP_parse_.tab.h SDP_parse_parser.h SDP_parser.l +SDP_parser.y lex.SDP_parse_.c" +gen_links $DIR $FILES + +DIR=$BASEDIR/titan.ProtocolModules.RTP/src +FILES="RTP_EncDec.cc RTP_Types.ttcn" +gen_links $DIR $FILES + +DIR=$BASEDIR/titan.TestPorts.TELNETasp/src +FILES="TELNETasp_PT.cc TELNETasp_PT.hh TELNETasp_PortType.ttcn" +gen_links $DIR $FILES + +DIR=../library/hnbap +FILES="HNBAP_CommonDataTypes.asn HNBAP_Constants.asn HNBAP_Containers.asn HNBAP_IEs.asn HNBAP_PDU_Contents.asn HNBAP_PDU_Descriptions.asn " +FILES+="HNBAP_EncDec.cc HNBAP_Types.ttcn HNBAP_Templates.ttcn " +gen_links $DIR $FILES + +DIR=../library/rua +FILES="RUA_CommonDataTypes.asn RUA_Constants.asn RUA_Containers.asn RUA_IEs.asn RUA_PDU_Contents.asn RUA_PDU_Descriptions.asn " +FILES+="RUA_EncDec.cc RUA_Types.ttcn RUA_Templates.ttcn " +gen_links $DIR $FILES + +DIR=../library/ranap +FILES="RANAP_CommonDataTypes.asn RANAP_Constants.asn RANAP_Containers.asn RANAP_IEs.asn RANAP_PDU_Contents.asn RANAP_PDU_Descriptions.asn " +FILES+="RANAP_Types.ttcn RANAP_Templates.ttcn RANAP_CodecPort.ttcn RANAP_EncDec.cc " +gen_links $DIR $FILES + +DIR=../library +FILES="Iuh_Types.ttcn Iuh_CodecPort.ttcn Iuh_CodecPort_CtrlFunctDef.cc Iuh_CodecPort_CtrlFunct.ttcn Iuh_Emulation.ttcn DNS_Helpers.ttcn " +FILES+="MGCP_Emulation.ttcn MGCP_Types.ttcn MGCP_Templates.ttcn MGCP_CodecPort.ttcn MGCP_CodecPort_CtrlFunct.ttcn MGCP_CodecPort_CtrlFunctDef.cc " +FILES+="RAN_Adapter.ttcnpp RAN_Emulation.ttcnpp BSSAP_CodecPort.ttcn SCCP_Templates.ttcn " +FILES+="Misc_Helpers.ttcn General_Types.ttcn Osmocom_Types.ttcn GSM_Types.ttcn Osmocom_VTY_Functions.ttcn Native_Functions.ttcn Native_FunctionDefs.cc IPA_Types.ttcn IPA_CodecPort.ttcn IPA_CodecPort_CtrlFunct.ttcn IPA_CodecPort_CtrlFunctDef.cc IPA_Emulation.ttcnpp Osmocom_CTRL_Types.ttcn Osmocom_CTRL_Functions.ttcn Osmocom_CTRL_Adapter.ttcn RTP_CodecPort.ttcn RTP_CodecPort_CtrlFunct.ttcn RTP_CodecPort_CtrlFunctDef.cc RTP_Emulation.ttcn IuUP_Types.ttcn IuUP_EncDec.cc IuUP_Emulation.ttcn " +FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcn " +FILES+="RUA_Emulation.ttcn " + +gen_links $DIR $FILES + +ignore_pp_results diff --git a/hnbgw/osmo-hnbgw.cfg b/hnbgw/osmo-hnbgw.cfg new file mode 100644 index 000000000..f11f61363 --- /dev/null +++ b/hnbgw/osmo-hnbgw.cfg @@ -0,0 +1,35 @@ +! +! OsmoHNBGW (1.1.0) configuration saved from vty +!! +! +log stderr + logging filter all 1 + logging color 1 + logging print category-hex 1 + logging print category 1 + logging print thread-id 0 + logging print extended-timestamp 1 + logging print file 1 + logging level set-all debug +! +line vty + no login +! +cs7 instance 0 + point-code 0.23.5 + sccp-address msc + routing-indicator PC + point-code 0.23.4 + sccp-address sgsn + routing-indicator PC + point-code 0.23.1 +hnbgw + log-prefix hnb-id + iuh + local-ip 127.0.0.1 + local-port 29169 + hnbap-allow-tmsi 1 + iucs + remote-addr msc + iups + remote-addr sgsn diff --git a/hnbgw/osmo-stp.cfg b/hnbgw/osmo-stp.cfg new file mode 100644 index 000000000..85d60e948 --- /dev/null +++ b/hnbgw/osmo-stp.cfg @@ -0,0 +1,52 @@ +! +! OsmoSTP (0.8.1) configuration saved from vty +!! +! +log stderr + logging filter all 1 + logging color 1 + logging print category 1 + logging timestamp 1 + logging print extended-timestamp 1 + logging level lglobal notice + logging level llapd notice + logging level linp notice + logging level lmux notice + logging level lmi notice + logging level lmib notice + logging level lsms notice + logging level lctrl notice + logging level lgtp notice + logging level lstats notice + logging level lgsup notice + logging level loap notice + logging level lss7 debug + logging level lsccp debug + logging level lsua debug + logging level lm3ua debug + logging level lmgcp notice +! +line vty + no login +! +cs7 instance 0 + xua rkm routing-key-allocation dynamic-permitted + asp virt-msc0-0 23905 2905 m3ua + local-ip 127.0.0.1 + remote-ip 127.0.0.1 + as virt-msc0 m3ua + asp virt-msc0-0 + routing-key 1 0.23.4 + asp virt-sgsn0-0 23906 2905 m3ua + local-ip 127.0.0.1 + remote-ip 127.0.0.1 + as virt-sgsn0 m3ua + asp virt-sgsn0-0 + routing-key 2 0.23.1 + + + route-table system + update route 0.23.4 7.255.7 linkset virt-msc0 + update route 0.23.1 7.255.7 linkset virt-sgsn0 + listen m3ua 2905 + accept-asp-connections dynamic-permitted diff --git a/hnbgw/regen_makefile.sh b/hnbgw/regen_makefile.sh new file mode 100755 index 000000000..47f7ee838 --- /dev/null +++ b/hnbgw/regen_makefile.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +NAME=HNBGW_Tests + +FILES=" + *.asn + *.c + *.ttcn + *.ttcnpp + IPA_CodecPort_CtrlFunctDef.cc + IPL4asp_PT.cc + IPL4asp_discovery.cc + IuUP_EncDec.cc + Iuh_CodecPort_CtrlFunctDef.cc + Native_FunctionDefs.cc + RTP_CodecPort_CtrlFunctDef.cc + RTP_EncDec.cc + SCTPasp_PT.cc + SDP_EncDec.cc + StatsD_CodecPort_CtrlFunctdef.cc + TCCConversion.cc + TCCEncoding.cc + TCCInterface.cc + TELNETasp_PT.cc + HNBAP_EncDec.cc + RUA_EncDec.cc + RANAP_EncDec.cc + MGCP_CodecPort_CtrlFunctDef.cc + UD_PT.cc +" + +export CPPFLAGS_TTCN3=" + -DIPA_EMULATION_CTRL + -DRAN_EMULATION_RANAP + -DUSE_MTP3_DISTRIBUTOR +" + +../regen-makefile.sh -e $NAME $FILES + +sed -i -e 's/^LINUX_LIBS = -lxml2 -lsctp/LINUX_LIBS = -lxml2 -lsctp -lfftranscode/' Makefile diff --git a/library/RUA_Emulation.ttcn b/library/RUA_Emulation.ttcn new file mode 100644 index 000000000..2d6291da7 --- /dev/null +++ b/library/RUA_Emulation.ttcn @@ -0,0 +1,400 @@ +module RUA_Emulation { + +/* RUA_Emulation runs on top of Iuh_Emulation. It multiplexes/demultiplexes + * the individuao connections, so there can be separate TTCN-3 components + * handling each of the connections (one connection per UE). + * + * The RUA_Emulation.main() function processes RUA messages from the Iuh stack + * via the RUA_PT, and dispatches them to the per-connection components. + * + * Outbound RUA connections are initiated by sending a FIXME primitive to the + * RUA_Emulation component. + * + * For each new inbound connection, the RuaOps.create_cb() is called. It can create + * or resolve a TTCN-3 component, and returns a component reference to which that inbound + * connection is routed/dispatched. + * + * If a pre-existing component wants to register to handle future inbound connection, + * it can do so by registering an "expect" with the expected RANAP payload. + + * (C) 2022 by Harald Welte + * All rights reserved. + * + * Released under the terms of GNU General Public License, Version 2 or + * (at your option) any later version. + */ + +import from General_Types all; +import from Osmocom_Types all; + +import from Iuh_Emulation all; + +import from RUA_Templates all; +//import from RUA_Constants all; +import from RUA_PDU_Descriptions all; +import from RUA_IEs all; + +import from RANAP_PDU_Descriptions all; +//import from RANAP_Constants all; +import from RANAP_IEs all; +import from RANAP_Types all; +import from RANAP_Templates all; + +modulepar { + integer mp_max_context_id := hex2int('FFFFFF'H); +} + + +/* General "base class" component definition, of which specific implementations + * derive themselves by means of the "extends" feature */ +type component RUA_ConnHdlr { + port RUA_Conn_PT RUA; +} + +/* port between individual per-connection components and this dispatcher */ +type port RUA_Conn_PT message { + inout RANAP_PDU, + RUA_Conn_Req, + RUA_Disc_Req, + RUA_Disc_Ind; +} with { extension "internal" }; + +type record RUA_Conn_Req { + boolean ps_domain, + RANAP_PDU ranap +}; + +type record RUA_Disc_Req { + RANAP_PDU ranap, + RUA_IEs.Cause cause +}; + +type record RUA_Disc_Ind { + RUA_IEs.Cause cause +}; + +type bitstring ContextId length(24); // with { variant "FIELDLENGTH(24)" }; + +/* represents a single RANAP connection over RUA */ +type record ConnectionData { + RUA_ConnHdlr comp_ref, + RUA_IEs.CN_DomainIndicator domain, + integer context_id +} + +type component RUA_Emulation_CT { + /* port to the bottom side (Iuh) */ + port RUA_PT RUA; + + /* ports to the upper side (per-connection components) */ + port RUA_Conn_PT CLIENT; + + /* use 16 as this is also the number of SCCP connections that SCCP_Emulation can handle */ + var ConnectionData ConnectionTable[16]; + + /* pending expected incoming connections */ + //var ExpectData ExpectTable[8]; + + /* tables for mapping inbound unitdata (like paging) */ + //var ImsiMapping ImsiTable[16]; + + /* procedure based port to register for incoming connections */ + //port RUA_PROC_PT PROC; + + var charstring g_rua_id; + var RuaOps g_rua_ops; +} + +type function RanapCreateCallback(ContextId context_id, RUA_IEs.CN_DomainIndicator domain, charstring id) +runs on RUA_Emulation_CT return RUA_ConnHdlr; + +type function RanapUnitdataCallback(RANAP_PDU ranap) +runs on RUA_Emulation_CT return template RANAP_PDU; + +type record RuaOps { + RanapCreateCallback create_cb optional, + RanapUnitdataCallback unitdata_cb optional + //boolean deode_dtap + //boolean role_ms +}; + +private function f_context_id_known(ContextId context_id) +runs on RUA_Emulation_CT return boolean { + var integer i; + for (i := 0; i < sizeof(ConnectionTable); i := i+1) { + if (ConnectionTable[i].context_id == bit2int(context_id)){ + return true; + } + } + return false; +} + +private function f_comp_known(RUA_ConnHdlr client) +runs on RUA_Emulation_CT return boolean { + var integer i; + for (i := 0; i < sizeof(ConnectionTable); i := i+1) { + if (ConnectionTable[i].comp_ref == client) { + return true; + } + } + return false; +} + +/* resolve connection ID by component reference */ +private function f_context_id_by_comp(RUA_ConnHdlr client) +runs on RUA_Emulation_CT return ContextId { + for (var integer i := 0; i < sizeof(ConnectionTable); i := i+1) { + if (ConnectionTable[i].comp_ref == client) { + return int2bit(ConnectionTable[i].context_id, 24); + } + } + setverdict(fail, "RAN Connection table not found by component ", client); + mtc.stop; +} + +/* resolve ConnectionTable index component reference */ +private function f_idx_by_comp(RUA_ConnHdlr client) +runs on RUA_Emulation_CT return integer { + for (var integer i := 0; i < sizeof(ConnectionTable); i := i+1) { + if (ConnectionTable[i].comp_ref == client) { + return i; + } + } + setverdict(fail, "RAN Connection table not found by component ", client); + mtc.stop; +} + +private function f_gen_context_id() +runs on RUA_Emulation_CT return ContextId { + var ContextId context_id; + + do { + context_id := int2bit(float2int(rnd()*int2float(mp_max_context_id)), 24); + } while (f_context_id_known(context_id) == true); + + return context_id; +} + +private function f_conn_table_init() +runs on RUA_Emulation_CT { + for (var integer i := 0; i < sizeof(ConnectionTable); i := i+1) { + ConnectionTable[i].comp_ref := null; + ConnectionTable[i].context_id := -1; + } +/* + for (var integer i := 0; i < sizeof(ImsiTable); i := i+1) { + ImsiTable[i].comp_ref := null; + ImsiTable[i].imsi := omit; + ImsiTable[i].tmsi := 'FFFFFFFF'O; + } +*/ +} + +private function f_conn_table_add(RUA_ConnHdlr comp_ref, RUA_IEs.CN_DomainIndicator domain, ContextId context_id) +runs on RUA_Emulation_CT { + var integer int_context_id := bit2int(context_id); + for (var integer i := 0; i < sizeof(ConnectionTable); i := i+1) { + if (ConnectionTable[i].context_id == -1) { + ConnectionTable[i].comp_ref := comp_ref; + ConnectionTable[i].domain := domain; + ConnectionTable[i].context_id := int_context_id; + log("Added conn table entry ", i, comp_ref, int_context_id); + return; + } + } + testcase.stop("RUA Connection table full!"); +} + +private function f_conn_table_del(ContextId context_id) +runs on RUA_Emulation_CT { + var integer int_context_id := bit2int(context_id); + for (var integer i := 0; i < sizeof(ConnectionTable); i := i+1) { + if (ConnectionTable[i].context_id == int_context_id) { + log("Deleted conn table entry ", i, + ConnectionTable[i].comp_ref, int_context_id); + ConnectionTable[i].context_id := -1; + return + } + } + setverdict(fail, "RUA Connection table attempt to delete non-existant ", int_context_id); + mtc.stop; +} + + +/* resolve component reference by connection ID */ +private function f_comp_by_context_id(ContextId context_id) +runs on RUA_Emulation_CT return RUA_ConnHdlr { + var integer int_context_id := bit2int(context_id); + for (var integer i := 0; i < sizeof(ConnectionTable); i := i+1) { + if (ConnectionTable[i].context_id == int_context_id) { + return ConnectionTable[i].comp_ref; + } + } + setverdict(fail, "RUA Connection table not found by RUA Context ID ", int_context_id); + mtc.stop; +} + +private function CommonRanapUnitdataCallback(RANAP_PDU ranap) +runs on RUA_Emulation_CT return template RANAP_PDU { + /* TODO: paging */ + return g_rua_ops.unitdata_cb.apply(ranap); +} + +private function f_handle_userData_RANAP(RUA_ConnHdlr client, RANAP_PDU ranap) +runs on RUA_Emulation_CT { + /* TODO: L3 decoding, if requested */ + CLIENT.send(ranap) to client; +} + + +private altstep as_reset_ack() runs on RUA_Emulation_CT { + var RUA_PDU rua_clt; + [] RUA.receive(tr_RUA_ConnectionlessTransfer(decmatch tr_RANAP_Reset)) -> value rua_clt { + var RANAP_PDU rx := dec_RANAP_PDU(rua_clt.initiatingMessage.value_.connectionlessTransfer.protocolIEs[0].value_.rANAP_Message); + var RANAP_IEs.CN_DomainIndicator dom; + dom := rx.initiatingMessage.value_.Reset.protocolIEs[1].value_.cN_DomainIndicator; + RUA.send(ts_RUA_ConnectionlessTransfer(enc_RANAP_PDU(valueof(ts_RANAP_ResetAck(dom))))); + } +} + +private altstep as_main_rua() runs on RUA_Emulation_CT { + var RANAP_PDU ranap; + var RUA_PDU rua; + var octetstring ranap_enc; + var ContextId context_id; + var RUA_IEs.CN_DomainIndicator domain_ind; + var RUA_ConnHdlr vc_conn; + var RUA_Conn_Req creq; + var RUA_Disc_Req dreq; + var RUA_IEs.Cause cause; + + /* RUA -> Client: UNIT-DATA (connectionless RUA) from CN */ + [] RUA.receive(tr_RUA_ConnectionlessTransfer) -> value rua { + ranap := dec_RANAP_PDU(rua.initiatingMessage.value_.connectionlessTransfer.protocolIEs[0].value_.rANAP_Message); + var template RANAP_PDU resp; + resp := CommonRanapUnitdataCallback(ranap); + if (isvalue(resp)) { + RUA.send(ts_RUA_ConnectionlessTransfer(enc_RANAP_PDU(valueof(resp)))); + } + } + + /* RUA -> Client: new connection from CN */ + [] RUA.receive(tr_RUA_Connect) -> value rua { + domain_ind := rua.initiatingMessage.value_.connect_.protocolIEs[0].value_.cN_DomainIndicator; + context_id := rua.initiatingMessage.value_.connect_.protocolIEs[1].value_.context_ID; + ranap_enc := rua.initiatingMessage.value_.connect_.protocolIEs[3].value_.rANAP_Message; + ranap := dec_RANAP_PDU(ranap_enc); + vc_conn := g_rua_ops.create_cb.apply(context_id, domain_ind, g_rua_id); + /* store mapping between client components and RUA contextId */ + f_conn_table_add(vc_conn, domain_ind, context_id); + /* TODO: notify user about incoming connection? */ + /* handle user payload */ + f_handle_userData_RANAP(vc_conn, ranap); + } + + /* RUA -> Client: connection-oriented data in existing connection */ + [] RUA.receive(tr_RUA_DirectTransfer) -> value rua { + context_id := rua.initiatingMessage.value_.directTransfer.protocolIEs[1].value_.context_ID; + vc_conn := f_comp_by_context_id(context_id); + ranap_enc := rua.initiatingMessage.value_.directTransfer.protocolIEs[2].value_.rANAP_Message; + f_handle_userData_RANAP(vc_conn, dec_RANAP_PDU(ranap_enc)); + } + + /* RUA -> Client: disconnect of an existing connection */ + [] RUA.receive(tr_RUA_Disconnect) -> value rua { + cause := rua.initiatingMessage.value_.disconnect_.protocolIEs[2].value_.cause; + context_id := rua.initiatingMessage.value_.disconnect_.protocolIEs[1].value_.context_ID; + vc_conn := f_comp_by_context_id(context_id); + /* send contained RANAP message to user */ + ranap_enc := rua.initiatingMessage.value_.disconnect_.protocolIEs[3].value_.rANAP_Message; + f_handle_userData_RANAP(vc_conn, dec_RANAP_PDU(ranap_enc)); + /* notify user of disconnect */ + CLIENT.send(RUA_Disc_Ind:{cause}); + f_conn_table_del(context_id); + } + + /* RANAP from client through an existing RANAP connection */ + [] CLIENT.receive(RANAP_PDU:?) -> value ranap sender vc_conn { + var integer idx := f_idx_by_comp(vc_conn); + context_id := int2bit(ConnectionTable[idx].context_id, 24); + domain_ind := ConnectionTable[idx].domain; + RUA.send(ts_RUA_DirectTransfer(domain_ind, context_id, enc_RANAP_PDU(ranap))); + } + + /* Disconnect request from client */ + [] CLIENT.receive(RUA_Disc_Req:?) -> value dreq sender vc_conn { + var octetstring enc_ranap := enc_RANAP_PDU(dreq.ranap); + var integer idx := f_idx_by_comp(vc_conn); + context_id := int2bit(ConnectionTable[idx].context_id, 24); + domain_ind := ConnectionTable[idx].domain; + RUA.send(ts_RUA_Disconnect(domain_ind, context_id, dreq.cause, enc_ranap)); + f_conn_table_del(context_id); + } + + /* RANAP from client, for a new RANAP connection */ + [] CLIENT.receive(RUA_Conn_Req:?) -> value creq sender vc_conn { + var octetstring enc_ranap := enc_RANAP_PDU(creq.ranap); + + if (f_comp_known(vc_conn) == false) { + /* unknown client, create new connection */ + context_id := f_gen_context_id(); + if (creq.ps_domain) { + domain_ind := ps_domain; + } else { + domain_ind := cs_domain; + } + + f_conn_table_add(vc_conn, domain_ind, context_id); + RUA.send(ts_RUA_Connect(domain_ind, context_id, normal_call, enc_ranap)); + } else { + /* known client, send via existing component */ + context_id := f_context_id_by_comp(vc_conn); + RUA.send(ts_RUA_DirectTransfer(domain_ind, context_id, enc_ranap)); + } + } + +} + + + +function f_ranap_reset(RANAP_IEs.CN_DomainIndicator dom) runs on RUA_Emulation_CT { + timer T := 5.0; + + var RANAP_PDU tx := valueof(ts_RANAP_Reset(ts_RanapCause_om_intervention,dom)); + RUA.send(ts_RUA_ConnectionlessTransfer(enc_RANAP_PDU(tx))); + T.start; + alt { + [] RUA.receive(tr_RUA_ConnectionlessTransfer(decmatch tr_RANAP_ResetAck)) { + log("RUA-RANAP: Received RESET-ACK in resposne to RESET, we're reay to go!"); + } + [] as_reset_ack(); + [] RUA.receive { repeat; } + [] T.timeout { + setverdict(fail, "RUA-RANAP: Timeout waiting for RESET-ACK after sending RESET"); + mtc.stop; + } + } +} + +function main(RuaOps ops, charstring id) runs on RUA_Emulation_CT { + g_rua_id := id; + g_rua_ops := ops; + f_conn_table_init(); + //f_expect_table_init(); + + while (true) { + alt { + [] as_main_rua(); + + /* + [] PROC.getcall(RUA_Register:{?,?}) -> param(l3_info, vc_hdlr) { + f_create_expect(l3_info, vc_hdlr); + PROC.reply(RUA_register:{l3_info, vc_hdlr}) to vc_hdlr; + } + */ + } + } +} + + +}