module GBProxy_Tests { /* Osmocom GBProxy test suite in TTCN-3 * (C) 2020 sysmocom - s.f.m.c. GmbH * All rights reserved. * * Author: Daniel Willmann * Released under the terms of GNU General Public License, Version 2 or * (at your option) any later version. * * SPDX-License-Identifier: GPL-2.0-or-later */ import from General_Types all; import from Osmocom_Types all; import from GSM_Types all; import from Native_Functions all; import from NS_Types all; import from NS_Emulation all; import from BSSGP_Types all; import from BSSGP_Emulation all; import from SCCPasp_Types all; import from Osmocom_Gb_Types all; import from MobileL3_CommonIE_Types all; import from MobileL3_GMM_SM_Types all; import from MobileL3_Types all; import from L3_Templates all; import from L3_Common all; import from TELNETasp_PortType all; import from Osmocom_VTY_Functions all; import from LLC_Types all; import from LLC_Templates all; import from GSM_RR_Types all; modulepar { /* IP/port on which we run our internal GSUP/HLR emulation */ NSConfigurations_SGSN mp_nsconfig_sgsn := { { nsei := 101, role_sgsn := true, handle_sns := false, nsvc := { { provider := { ip := { address_family := AF_INET, local_udp_port := 7777, local_ip := "127.0.0.1", remote_udp_port := 23000, remote_ip := "127.0.0.1" } }, nsvci := 101 } } } }; NSConfigurations_PCU mp_nsconfig_pcu := { { nsei := 96, role_sgsn := false, handle_sns := false, nsvc := { { provider := { ip := { address_family := AF_INET, local_udp_port := 21010, local_ip := "127.0.0.1", remote_udp_port := 23000, remote_ip := "127.0.0.1" } }, nsvci := 97 } } }, { nsei := 97, role_sgsn := false, handle_sns := false, nsvc := { { provider := { ip := { address_family := AF_INET, local_udp_port := 21011, local_ip := "127.0.0.1", remote_udp_port := 23000, remote_ip := "127.0.0.1" } }, nsvci := 98 } } }, { nsei := 98, role_sgsn := false, handle_sns := false, nsvc := { { provider := { ip := { address_family := AF_INET, local_udp_port := 21012, local_ip := "127.0.0.1", remote_udp_port := 23000, remote_ip := "127.0.0.1" } }, nsvci := 99 } } } }; }; const integer NUM_BVC_PER_NSE := 3; type record GbInstance { NS_CT vc_NS, BSSGP_CT vc_BSSGP, BSSGP_BVC_CT vc_BSSGP_BVC[NUM_BVC_PER_NSE], BssgpConfig cfg }; const integer NUM_PCU := 3; type record length(NUM_PCU) of GbInstance GbInstances_PCU; type record length(NUM_PCU) of NSConfiguration NSConfigurations_PCU; type record length(NUM_PCU) of BssgpCellId BssgpCellIds; const integer NUM_SGSN := 1; type record length(NUM_SGSN) of GbInstance GbInstances_SGSN; type record length(NUM_SGSN) of NSConfiguration NSConfigurations_SGSN; type component test_CT { var GbInstances_PCU g_pcu; var GbInstances_SGSN g_sgsn; port BSSGP_CT_PROC_PT PROC; port TELNETasp_PT GBPVTY; var boolean g_initialized := false; var boolean g_use_echo := false; }; type component BSSGP_ConnHdlr { port BSSGP_PT PCU[NUM_PCU]; port BSSGP_PT PCU_SIG[NUM_PCU]; port BSSGP_PROC_PT PCU_PROC[NUM_PCU]; port BSSGP_PT SGSN[NUM_SGSN]; port BSSGP_PT SGSN_SIG[NUM_SGSN]; port BSSGP_PROC_PT SGSN_PROC[NUM_SGSN]; var BSSGP_ConnHdlrPars g_pars; timer g_Tguard; var LLC_Entities llc; } type record SGSN_ConnHdlrNetworkPars { boolean expect_ptmsi, boolean expect_auth, boolean expect_ciph }; type record BSSGP_ConnHdlrPars { /* IMEI of the simulated ME */ hexstring imei, /* IMSI of the simulated MS */ hexstring imsi, /* MSISDN of the simulated MS (probably unused) */ hexstring msisdn, /* P-TMSI allocated to the simulated MS */ OCT4 p_tmsi optional, OCT3 p_tmsi_sig optional, /* TLLI of the simulated MS */ OCT4 tlli, OCT4 tlli_old optional, RoutingAreaIdentificationV ra optional, BssgpCellIds bssgp_cell_id, float t_guard }; private function f_cellid_to_RAI(in BssgpCellId cell_id) return RoutingAreaIdentificationV { /* mcc_mnc is encoded as of 24.008 10.5.5.15 */ var BcdMccMnc mcc_mnc := cell_id.ra_id.lai.mcc_mnc; var RoutingAreaIdentificationV ret := { mccDigit1 := mcc_mnc[0], mccDigit2 := mcc_mnc[1], mccDigit3 := mcc_mnc[2], mncDigit3 := mcc_mnc[3], mncDigit1 := mcc_mnc[4], mncDigit2 := mcc_mnc[5], lac := int2oct(cell_id.ra_id.lai.lac, 16), rac := int2oct(cell_id.ra_id.rac, 8) } return ret; }; private function f_init_gb_pcu(inout GbInstance gb, charstring id, integer offset) runs on test_CT { gb.vc_NS := NS_CT.create(id & "-NS(PCU)" & int2str(offset)); gb.vc_BSSGP := BSSGP_CT.create(id & "-BSSGP(PCU)" & int2str(offset)); /* connect lower end of BSSGP emulation with NS upper port */ connect(gb.vc_BSSGP:BSCP, gb.vc_NS:NS_SP); gb.vc_NS.start(NSStart(mp_nsconfig_pcu[offset])); gb.vc_BSSGP.start(BssgpStart(gb.cfg, id)); for (var integer i := 0; i < lengthof(gb.cfg.bvc); i := i + 1) { connect(self:PROC, gb.vc_BSSGP:PROC); gb.vc_BSSGP_BVC[i] := f_bssgp_get_bvci_ct(gb.cfg.bvc[i].bvci, PROC); disconnect(self:PROC, gb.vc_BSSGP:PROC); } } private function f_init_gb_sgsn(inout GbInstance gb, charstring id, integer offset) runs on test_CT { gb.vc_NS := NS_CT.create(id & "-NS(SGSN)" & int2str(offset)); gb.vc_BSSGP := BSSGP_CT.create(id & "-BSSGP(SGSN)" & int2str(offset)); /* connect lower end of BSSGP emulation with NS upper port */ connect(gb.vc_BSSGP:BSCP, gb.vc_NS:NS_SP); gb.vc_NS.start(NSStart(mp_nsconfig_sgsn[offset])); gb.vc_BSSGP.start(BssgpStart(gb.cfg, id)); for (var integer i := 0; i < lengthof(gb.cfg.bvc); i := i + 1) { connect(self:PROC, gb.vc_BSSGP:PROC); gb.vc_BSSGP_BVC[i] := f_bssgp_get_bvci_ct(gb.cfg.bvc[i].bvci, PROC); disconnect(self:PROC, gb.vc_BSSGP:PROC); } } private function f_init_vty() runs on test_CT { map(self:GBPVTY, system:GBPVTY); f_vty_set_prompts(GBPVTY); f_vty_transceive(GBPVTY, "enable"); } /* mcc_mnc is 24.008 10.5.5.15 encoded. 262 42 */ function f_init(BcdMccMnc mcc_mnc := '262F42'H) runs on test_CT { var integer i; if (g_initialized == true) { return; } g_initialized := true; g_pcu[0].cfg := { nsei := 96, sgsn_role := false, bvc := { { bvci := 196, cell_id := { ra_id := { lai := { mcc_mnc := mcc_mnc, lac := 13135}, rac := 0 }, cell_id := 20960 }, depth := BSSGP_DECODE_DEPTH_BSSGP } } }; g_pcu[1].cfg := { nsei := 97, sgsn_role := false, bvc := { { bvci := 210, cell_id := { ra_id := { lai := { mcc_mnc := mcc_mnc, lac := 13200}, rac := 0 }, cell_id := 20961 }, depth := BSSGP_DECODE_DEPTH_BSSGP } } }; g_pcu[2].cfg := { nsei := 98, sgsn_role := false, bvc := { { bvci := 220, cell_id := { ra_id := { lai := { mcc_mnc := mcc_mnc, lac := 13300}, rac := 0 }, cell_id := 20962 }, depth := BSSGP_DECODE_DEPTH_BSSGP } } }; g_sgsn[0].cfg := { nsei := 101, sgsn_role := true, bvc := { { bvci := 196, cell_id := { ra_id := { lai := { mcc_mnc := mcc_mnc, lac := 13135}, rac := 0 }, cell_id := 20960 }, depth := BSSGP_DECODE_DEPTH_BSSGP }, { bvci := 210, cell_id := { ra_id := { lai := { mcc_mnc := mcc_mnc, lac := 13200}, rac := 0 }, cell_id := 20961 }, depth := BSSGP_DECODE_DEPTH_BSSGP }, { bvci := 220, cell_id := { ra_id := { lai := { mcc_mnc := mcc_mnc, lac := 13300}, rac := 0 }, cell_id := 20962 }, depth := BSSGP_DECODE_DEPTH_BSSGP } } }; f_init_vty(); f_init_gb_sgsn(g_sgsn[0], "GbProxy_Test-SGSN0", 0); f_sleep(4.0); f_init_gb_pcu(g_pcu[0], "GbProxy_Test-PCU0", 0); f_init_gb_pcu(g_pcu[1], "GbProxy_Test-PCU1", 1); f_init_gb_pcu(g_pcu[2], "GbProxy_Test-PCU2", 2); } function f_cleanup() runs on test_CT { self.stop; } type function void_fn(charstring id) runs on BSSGP_ConnHdlr; /* helper function to create, connect and start a BSSGP_ConnHdlr component */ function f_start_handler(void_fn fn, charstring id, GbInstances_PCU pcu, GbInstances_SGSN sgsn, integer imsi_suffix, float t_guard := 30.0) runs on test_CT return BSSGP_ConnHdlr { var BSSGP_ConnHdlr vc_conn; var BSSGP_ConnHdlrPars pars := { imei := f_gen_imei(imsi_suffix), imsi := f_gen_imsi(imsi_suffix), msisdn := f_gen_msisdn(imsi_suffix), p_tmsi := omit, p_tmsi_sig := omit, tlli := f_gprs_tlli_random(), tlli_old := omit, ra := omit, bssgp_cell_id := { pcu[0].cfg.bvc[0].cell_id, pcu[1].cfg.bvc[0].cell_id, pcu[2].cfg.bvc[0].cell_id }, t_guard := t_guard }; vc_conn := BSSGP_ConnHdlr.create(id); // PDU side connect(vc_conn:PCU[0], pcu[0].vc_BSSGP_BVC[0]:BSSGP_SP); connect(vc_conn:PCU_SIG[0], pcu[0].vc_BSSGP_BVC[0]:BSSGP_SP_SIG); connect(vc_conn:PCU_PROC[0], pcu[0].vc_BSSGP_BVC[0]:BSSGP_PROC); connect(vc_conn:PCU[1], pcu[1].vc_BSSGP_BVC[0]:BSSGP_SP); connect(vc_conn:PCU_SIG[1], pcu[1].vc_BSSGP_BVC[0]:BSSGP_SP_SIG); connect(vc_conn:PCU_PROC[1], pcu[1].vc_BSSGP_BVC[0]:BSSGP_PROC); connect(vc_conn:PCU[2], pcu[2].vc_BSSGP_BVC[0]:BSSGP_SP); connect(vc_conn:PCU_SIG[2], pcu[2].vc_BSSGP_BVC[0]:BSSGP_SP_SIG); connect(vc_conn:PCU_PROC[2], pcu[2].vc_BSSGP_BVC[0]:BSSGP_PROC); // SGSN side connect(vc_conn:SGSN[0], sgsn[0].vc_BSSGP_BVC[0]:BSSGP_SP); connect(vc_conn:SGSN_SIG[0], sgsn[0].vc_BSSGP_BVC[0]:BSSGP_SP_SIG); connect(vc_conn:SGSN_PROC[0], sgsn[0].vc_BSSGP_BVC[0]:BSSGP_PROC); vc_conn.start(f_handler_init(fn, id, pars)); return vc_conn; } private altstep as_Tguard() runs on BSSGP_ConnHdlr { [] g_Tguard.timeout { setverdict(fail, "Tguard timeout"); mtc.stop; } } /* first function called in every ConnHdlr */ private function f_handler_init(void_fn fn, charstring id, BSSGP_ConnHdlrPars pars) runs on BSSGP_ConnHdlr { /* do some common stuff like setting up g_pars */ g_pars := pars; llc := f_llc_create(false); g_Tguard.start(pars.t_guard); activate(as_Tguard()); /* call the user-supplied test case function */ fn.apply(id); } /* TODO: * Detach without Attach * SM procedures without attach / RAU * ATTACH / RAU ** with / without authentication ** with / without P-TMSI allocation * re-transmissions of LLC frames * PDP Context activation ** with different GGSN config in SGSN VTY ** with different PDP context type (v4/v6/v46) ** timeout from GGSN ** multiple / secondary PDP context */ private function f_TC_BVC_bringup(charstring id) runs on BSSGP_ConnHdlr { f_sleep(5.0); setverdict(pass); } testcase TC_BVC_bringup() runs on test_CT { var BSSGP_ConnHdlr vc_conn; f_init(); vc_conn := f_start_handler(refers(f_TC_BVC_bringup), testcasename(), g_pcu, g_sgsn, 51); vc_conn.done; f_cleanup(); } friend function f_bssgp_suspend(integer ran_idx := 0) runs on BSSGP_ConnHdlr return OCT1 { timer T := 5.0; var PDU_BSSGP rx_pdu; PCU_SIG[ran_idx].send(ts_BSSGP_SUSPEND(g_pars.tlli, g_pars.bssgp_cell_id[ran_idx].ra_id)); T.start; alt { [] PCU_SIG[ran_idx].receive(tr_BSSGP_SUSPEND_ACK(g_pars.tlli, g_pars.bssgp_cell_id[ran_idx].ra_id, ?)) -> value rx_pdu { return rx_pdu.pDU_BSSGP_SUSPEND_ACK.suspend_Reference_Number.suspend_Reference_Number_value; } [] PCU_SIG[ran_idx].receive(tr_BSSGP_SUSPEND_NACK(g_pars.tlli, g_pars.bssgp_cell_id[ran_idx].ra_id, ?)) -> value rx_pdu { setverdict(fail, "SUSPEND-NACK in response to SUSPEND for TLLI ", g_pars.tlli); mtc.stop; } [] T.timeout { setverdict(fail, "No SUSPEND-ACK in response to SUSPEND for TLLI ", g_pars.tlli); mtc.stop; } } return '00'O; } friend function f_bssgp_resume(OCT1 susp_ref, integer ran_idx := 0) runs on BSSGP_ConnHdlr { timer T := 5.0; PCU_SIG[ran_idx].send(ts_BSSGP_RESUME(g_pars.tlli, g_pars.bssgp_cell_id[ran_idx].ra_id, susp_ref)); T.start; alt { [] PCU_SIG[ran_idx].receive(tr_BSSGP_RESUME_ACK(g_pars.tlli, g_pars.bssgp_cell_id[ran_idx].ra_id)); [] PCU_SIG[ran_idx].receive(tr_BSSGP_RESUME_NACK(g_pars.tlli, g_pars.bssgp_cell_id[ran_idx].ra_id, ?)) { setverdict(fail, "RESUME-NACK in response to RESUME for TLLI ", g_pars.tlli); mtc.stop; } [] T.timeout { setverdict(fail, "No RESUME-ACK in response to SUSPEND for TLLI ", g_pars.tlli); mtc.stop; } } } control { execute( TC_BVC_bringup() ); } }