diff --git a/mme/MME_Tests.cfg b/mme/MME_Tests.cfg new file mode 100644 index 000000000..3e478c326 --- /dev/null +++ b/mme/MME_Tests.cfg @@ -0,0 +1,18 @@ +[ORDERED_INCLUDE] +# Common configuration, shared between test suites +"../Common.cfg" +# testsuite specific configuration, not expected to change +"./MME_Tests.default" + +# Local configuration below + +[LOGGING] + +[TESTPORT_PARAMETERS] + +[MODULE_PARAMETERS] + +[MAIN_CONTROLLER] + +[EXECUTE] +MME_Tests.control diff --git a/mme/MME_Tests.default b/mme/MME_Tests.default new file mode 100644 index 000000000..1c220d8d9 --- /dev/null +++ b/mme/MME_Tests.default @@ -0,0 +1,12 @@ +[LOGGING] +FileMask := LOG_ALL | TTCN_MATCHING; +mtc.FileMask := ERROR | WARNING | PARALLEL | VERDICTOP; + +[TESTPORT_PARAMETERS] + + +[MODULE_PARAMETERS] + +[MAIN_CONTROLLER] + +[EXECUTE] diff --git a/mme/MME_Tests.ttcn b/mme/MME_Tests.ttcn index 3ba0677b5..ae71173b9 100644 --- a/mme/MME_Tests.ttcn +++ b/mme/MME_Tests.ttcn @@ -10,31 +10,84 @@ module MME_Tests { +import from General_Types all; + +import from S1AP_Types all; +import from S1AP_Templates all; +import from S1AP_Emulation all; +import from S1AP_PDU_Descriptions all; +import from S1AP_IEs all; + +import from NAS_EPS_Types all; +import from NAS_Templates all; + import from SGsAP_Types all; import from SGsAP_Templates all; import from SGsAP_Emulation all; +import from LTE_CryptoFunctions all; + import from L3_Templates all; import from DNS_Helpers all; +import from Osmocom_Types all; friend module MME_Tests_SGsAP; +/* (maximum) number of emulated eNBs */ +const integer NUM_ENB := 3; + +/* (maximum) number of emulated UEs */ +const integer NUM_UE := 3; + +/* parameters of emulated ENB */ +type record EnbParams { + Global_ENB_ID global_enb_id, + integer cell_identity, + SupportedTAs supported_tas +} + +/* parameters of emulated UE */ +type record UeParams { + hexstring imsi +} + type component MTC_CT { + /* S1 intreface of emulated ENBs */ + var EnbParams g_enb_pars[NUM_ENB]; + var S1AP_Emulation_CT vc_S1AP[NUM_ENB]; + port S1AP_PT S1AP_UNIT[NUM_ENB]; + port S1APEM_PROC_PT S1AP_PROC[NUM_ENB]; + + /* SGs interface of emulated MSC/VLR */ var SGsAP_Emulation_CT vc_SGsAP; port SGsAP_PT SGsAP_UNIT; port SGsAPEM_PROC_PT SGsAP_PROC; + + var UeParams g_ue_pars[NUM_UE]; } -type component ConnHdlr extends SGsAP_ConnHdlr { +type component ConnHdlr extends S1AP_ConnHdlr, SGsAP_ConnHdlr { var ConnHdlrPars g_pars; timer g_Tguard := 30.0; } type record ConnHdlrPars { - hexstring imsi + /* copied over from MTC_CT on start of component */ + EnbParams enb_pars[NUM_ENB], + /* copied over from MTC_CT on start of component */ + UeParams ue_pars, + /* currently used MME (index into enb_pars, S1AP, ...) */ + integer mme_idx } modulepar { + /* S1 interface */ + charstring mp_mme_ip := "127.0.0.1"; + integer mp_mme_s1ap_port := 36412; + charstring mp_s1_local_ip := "127.0.0.1"; + integer mp_s1_local_port := 50000; + + /* SGs interface */ charstring mp_sgs_local_ip := "127.0.0.1"; integer mp_sgs_local_port := 29118; charstring mp_vlr_name := "vlr.example.net"; @@ -68,11 +121,96 @@ friend function f_init_sgsap(charstring id) runs on MTC_CT { vc_SGsAP.start(SGsAP_Emulation.main(ops, pars, id)); } +/* send incoming unit data messages (like reset) to global S1AP_UNIT port */ +friend function S1apForwardUnitdataCallback(S1AP_PDU msg) +runs on S1AP_Emulation_CT return template S1AP_PDU { + S1AP_UNIT.send(msg); + return omit; +} + +friend function S1apCreateCallback(S1AP_PDU msg, template (omit) MME_UE_S1AP_ID mme_id, + template (omit) ENB_UE_S1AP_ID enb_id, charstring id) +runs on S1AP_Emulation_CT return S1AP_ConnHdlr +{ + setverdict(fail, "implement this"); + mtc.stop; +} + +friend function f_init_one_enb(charstring id, integer num := 0) runs on MTC_CT { + id := id & "-S1AP" & int2str(num); + var S1APOps ops := { + create_cb := refers(S1apCreateCallback), + unitdata_cb := refers(S1apForwardUnitdataCallback) + } + var S1AP_conn_parameters pars := { + remote_ip := mp_mme_ip, + remote_sctp_port := mp_mme_s1ap_port, + local_ip := mp_s1_local_ip, + local_sctp_port := mp_s1_local_port + num, + role := NAS_ROLE_UE + } + var PLMNidentity plmn_id := '00f110'O; + var EnbParams enb_pars := { + global_enb_id := { + pLMNidentity := plmn_id, + eNB_ID := { + macroENB_ID := int2bit(num, 20) + }, + iE_Extensions := omit + }, + cell_identity := num, + supported_tas := { + { + tAC := int2oct(12345, 2), + broadcastPLMNs := { plmn_id }, + iE_Extensions := omit + } + } + }; + + g_enb_pars[num] := enb_pars; + vc_S1AP[num] := S1AP_Emulation_CT.create(id); + map(vc_S1AP[num]:S1AP, system:S1AP_CODEC_PT); + connect(vc_S1AP[num]:S1AP_PROC, self:S1AP_PROC[num]); + connect(vc_S1AP[num]:S1AP_UNIT, self:S1AP_UNIT[num]); + vc_S1AP[num].start(S1AP_Emulation.main(ops, pars, id)); + S1AP_UNIT[num].receive(S1APEM_Event:{up_down:=S1APEM_EVENT_UP}); +} +friend function f_init_one_ue(inout UeParams uep, integer imsi_suffix) { + uep := { + imsi := f_gen_imsi(imsi_suffix) + } +} +friend function f_init_s1ap(charstring id, integer imsi_suffix) runs on MTC_CT { + var integer i; + for (i := 0; i < NUM_ENB; i := i+1) { + f_init_one_enb(id, i); + } + for (i := 0; i < NUM_UE; i := i+1) { + f_init_one_ue(g_ue_pars[i], i*1000 + imsi_suffix); + } +} + +friend template (value) TAI ts_enb_S1AP_TAI(EnbParams enb) := { + pLMNidentity := enb.global_enb_id.pLMNidentity, + tAC := enb.supported_tas[0].tAC, + iE_Extensions := omit +} + +friend template (value) EUTRAN_CGI ts_enb_S1AP_CGI(EnbParams enb) := { + pLMNidentity := enb.global_enb_id.pLMNidentity, + cell_ID := int2bit(enb.cell_identity, 28), + iE_Extensions := omit +} + + /* generate parameters for a connection handler */ -friend function f_init_pars(integer imsi_suffix) +friend function f_init_pars(integer ue_idx := 0) runs on MTC_CT return ConnHdlrPars { var ConnHdlrPars pars := { - imsi := f_gen_imsi(imsi_suffix) + enb_pars := g_enb_pars, + ue_pars := g_ue_pars[ue_idx], + mme_idx := 0 }; return pars; } @@ -86,9 +224,14 @@ runs on MTC_CT return ConnHdlr { var charstring id := testcasename() & int2str(s1ap_idx); vc_conn := ConnHdlr.create(id); - /* SGsAP part */ - connect(vc_conn:SGsAP, vc_SGsAP:SGsAP_CLIENT); - connect(vc_conn:SGsAP_PROC, vc_SGsAP:SGsAP_PROC); + /* S1AP part */ + connect(vc_conn:S1AP, vc_S1AP[s1ap_idx]:S1AP_CLIENT); + connect(vc_conn:S1AP_PROC, vc_S1AP[s1ap_idx]:S1AP_PROC); + if (isbound(vc_SGsAP)) { + /* SGsAP part */ + connect(vc_conn:SGsAP, vc_SGsAP:SGsAP_CLIENT); + connect(vc_conn:SGsAP_PROC, vc_SGsAP:SGsAP_PROC); + } /* 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/ */ @@ -110,8 +253,209 @@ friend function f_init_handler(ConnHdlrPars pars, float t_guard := 30.0) runs on /* start guard timre and activate it as default */ g_Tguard.start(t_guard); activate(as_Tguard()); - /* Route all SGsAP mesages for our IMSIto us */ - f_create_sgsap_expect(pars.imsi); + if (SGsAP_PROC.checkstate("Connected")) { + /* Route all SGsAP mesages for our IMSIto us */ + f_create_sgsap_expect(pars.ue_pars.imsi); + } +} + + + +friend function f_s1ap_setup(integer idx := 0, template Cause cause := omit) runs on MTC_CT { + var template (present) Cause exp_cause; + var boolean exp_fail := false; + timer T := 5.0; + if (not istemplatekind(cause, "omit")) { + exp_fail := true; + exp_cause := cause; + } + + S1AP_UNIT[idx].send(ts_S1AP_SetupReq(g_enb_pars[idx].global_enb_id, + g_enb_pars[idx].supported_tas, v32)); + T.start; + alt { + [exp_fail] S1AP_UNIT[idx].receive(tr_S1AP_SetupFail(exp_cause)) { + setverdict(pass); + } + [not exp_fail] S1AP_UNIT[idx].receive(tr_S1AP_SetupResp) { + setverdict(pass); + } + [] S1AP_UNIT[idx].receive { + setverdict(fail, "Received unexpected S1AP"); + } + [] T.timeout { + setverdict(fail, "Timeout waiting for S1AP Setup result"); + } + } +} + +/* Unsuccessful S1 Setup procedure to MME (wrong PLMN) */ +testcase TC_s1ap_setup_wrong_plmn() runs on MTC_CT { + var charstring id := testcasename(); + f_init_s1ap(id, 1); + g_enb_pars[0].global_enb_id.pLMNidentity := '62F224'O; + f_s1ap_setup(0, {misc:=unknown_PLMN}); +} + +/* Unsuccessful S1 Setup procedure to MME (wrong PLMN) */ +testcase TC_s1ap_setup_wrong_tac() runs on MTC_CT { + var charstring id := testcasename(); + f_init_s1ap(id, 2); + g_enb_pars[0].supported_tas[0].broadcastPLMNs[0] := '62F224'O; + f_s1ap_setup(0, {misc:=unknown_PLMN}); +} + +/* Successful S1 Setup procedure to MME */ +testcase TC_s1ap_setup() runs on MTC_CT { + var charstring id := testcasename(); + f_init_s1ap(id, 3); + f_s1ap_setup(0); +} + +private const EPS_QualityOfServiceV c_NAS_defaultQoS := { + qCI := '00'O, + maxBitRateUplink := omit, + maxBitRateDownlink := omit, + guaranteedBitRateUplink := omit, + guaranteedBitRateDownlink := omit, + maxBitRateUplinkExt := omit, + maxBitRateDownlinkExt := omit, + guaranteedBitRateUplinkExt := omit, + guaranteedBitRateDownlinkExt := omit, + maxBitRateUplinkExt2 := omit, + maxBitRateDownlinkExt2 := omit, + guaranteedBitRateUplinkExt2 := omit, + guaranteedBitRateDownlinkExt2 := omit +}; + +private const UENetworkCapabilityV c_NAS_defaultUeNetCap := { + eEA := '10000000'B, + eIA := '11000000'B, + uEA := omit, + uIA := omit, + uCS2 := omit, + nF := omit, + vCC := omit, + lCS := omit, + lPP := omit, + aCC_CSFB := omit, + h245_ASH := omit, + proSe := omit, + proSe_dd := omit, + proSe_dc := omit, + proSe_relay := omit, + cP_CIoT := omit, + uP_CIoT := omit, + s1_Udata := omit, + eRwoPDN := omit, + hC_CP_CIoT := omit, + ePCO := omit, + multipleDRB := omit, + v2XPC5 := omit, + restrictEC := omit, + cPbackoff := omit, + dCNR := omit, + n1Mode := omit, + sGC := omit, + spare1 := omit, + spare := omit +}; + +private const octetstring c_NAS_defaultAPN := '00'O; + +private altstep as_s1ap_handle_auth() runs on ConnHdlr { + var PDU_NAS_EPS rx_nas; + [] S1AP.receive(tr_NAS_AuthReq) -> value rx_nas { + /* static XRES result as we fixed the HSS RAND value and always have the following + RAND: 20080c3818183b522614162c07601d0d + AUTN: f11b89a2a8be00001f9c526f3d75d44c + IK: 11329aae8e8d2941bb226b2061137c58 + CK: 740d62df9803eebde5120acf358433d0 + RES: 6a91970e838fd079 + SRES: e91e4777 + Kc: 3b0f999e42198874 + SQN: 32 + IND: 0 + */ + /* KASME: 95AFAD9A0D29AFAA079A9451DF7161D7EE4CBF2AF9387F766D058BB6B44B905D */ + const OCT16 ck := '740d62df9803eebde5120acf358433d0'O; + const OCT16 ik := '11329aae8e8d2941bb226b2061137c58'O; + const OCT16 autn := 'f11b89a2a8be00001f9c526f3d75d44c'O; + const OCT8 res := '6a91970e838fd079'O; + const OCT3 plmn_id := '00F110'O; + const OCT6 sqn := '000000000020'O; + const OCT6 ak := substr(autn, 0, 6) xor4b sqn; + var octetstring kasme := f_kdf_kasme(ck, ik, plmn_id, sqn, ak); + var S1APEM_Config cfg := { + set_nas_keys := { + k_nas_int := f_kdf_nas_int(1, kasme), + k_nas_enc := f_kdf_nas_enc(1, kasme) + } + }; + S1AP.send(cfg); + S1AP.send(ts_NAS_AuthResp(res)); + } +} + +private altstep as_s1ap_handle_sec_mode() runs on ConnHdlr { + var PDU_NAS_EPS rx_nas; + var NAS_SecurityAlgorithmsV alg := { + typeOfIntegrityProtection := '001'B, + spare1 := '0'B, + typeOfCiphering := '000'B, + spare2 := '0'B + }; + var NAS_KeySetIdentifierV kset_id := { + identifier := '000'B, + tSC := '0'B + }; + [] S1AP.receive(tr_NAS_SecModeCmd(alg, kset_id, ?)) { + S1AP.send(ts_NAS_SecModeCmpl); + } +} + +private function f_TC_attach(ConnHdlrPars pars) runs on ConnHdlr { + f_init_handler(pars); + var template (value) EPS_MobileIdentityV mi := ts_NAS_MobileId_IMSI('001010000000001'H); + var template (value) PDU_NAS_EPS nas_esm, nas_emm; +/* + nas_esm := ts_NAS_ActDefEpsBearCtxReq(bearer_id := '0000'B, proc_tid := int2bit(1,8), + qos := c_NAS_defaultQoS, apn := c_NAS_defaultAPN, + addr_type := '000'B, addr_info := ''O); +*/ + nas_esm := ts_NAS_PdnConnReq(bearer_id := '0000'B, proc_tid := int2bit(1,8), + pdn_type := NAS_PDN_T_IPv4, req_type := '001'B); + nas_emm := ts_NAS_AttachRequest(att_type := '000'B, kset_id := '000'B, mobile_id := mi, + ue_net_cap := c_NAS_defaultUeNetCap, + esm_enc := enc_PDU_NAS_EPS(valueof(nas_esm))); + var template (value) S1AP_PDU tx; + tx := ts_S1AP_InitialUE(p_eNB_value := 0, p_nasPdu := enc_PDU_NAS_EPS(valueof(nas_emm)), + p_tAI := ts_enb_S1AP_TAI(g_pars.enb_pars[g_pars.mme_idx]), + p_eUTRAN_CGI := ts_enb_S1AP_CGI(g_pars.enb_pars[g_pars.mme_idx]), + p_rrcCause := mo_Signalling); + S1AP.send(tx); + + as_s1ap_handle_auth(); + as_s1ap_handle_sec_mode(); + + f_sleep(10.0); +} +testcase TC_s1ap_attach() runs on MTC_CT { + var charstring id := testcasename(); + + f_init_s1ap(id, 4); + f_s1ap_setup(0); + + var ConnHdlrPars pars := f_init_pars(ue_idx := 0); + var ConnHdlr vc_conn; + vc_conn := f_start_handler_with_pars(refers(f_TC_attach), pars); + vc_conn.done; +} + +control { + execute( TC_s1ap_setup_wrong_plmn() ); + execute( TC_s1ap_setup_wrong_tac() ); + execute( TC_s1ap_setup() ); } diff --git a/mme/MME_Tests_SGsAP.ttcn b/mme/MME_Tests_SGsAP.ttcn index 935a59ee3..d20479c23 100644 --- a/mme/MME_Tests_SGsAP.ttcn +++ b/mme/MME_Tests_SGsAP.ttcn @@ -48,23 +48,23 @@ function f_sgsap_page(Service_Indicator serv_ind, template (omit) OCT4 tmsi, if (not istemplatekind(exp_cause, "omit")) { exp_success := false; } - SGsAP.send(ts_SGsAP_PAGING_REQ(g_pars.imsi, vlr_name, serv_ind, tmsi)); + SGsAP.send(ts_SGsAP_PAGING_REQ(g_pars.ue_pars.imsi, vlr_name, serv_ind, tmsi)); alt { /* we expect success */ - [exp_success] SGsAP.receive(tr_SGsAP_SERVICE_REQ(g_pars.imsi, serv_ind, ?)) { + [exp_success] SGsAP.receive(tr_SGsAP_SERVICE_REQ(g_pars.ue_pars.imsi, serv_ind, ?)) { setverdict(pass); } - [exp_success] SGsAP.receive(tr_SGsAP_PAGING_REJ(g_pars.imsi, ?)) { + [exp_success] SGsAP.receive(tr_SGsAP_PAGING_REJ(g_pars.ue_pars.imsi, ?)) { setverdict(fail, "Received unexpected PAGING REJECT"); } /* we expect failure */ - [not exp_success] SGsAP.receive(tr_SGsAP_SERVICE_REQ(g_pars.imsi, serv_ind, ?)) { + [not exp_success] SGsAP.receive(tr_SGsAP_SERVICE_REQ(g_pars.ue_pars.imsi, serv_ind, ?)) { setverdict(fail, "Received SERVICE REQ waiting for PAGING REJECT"); } - [not exp_success] SGsAP.receive(tr_SGsAP_PAGING_REJ(g_pars.imsi, exp_cause)) { + [not exp_success] SGsAP.receive(tr_SGsAP_PAGING_REJ(g_pars.ue_pars.imsi, exp_cause)) { setverdict(pass); } - [not exp_success] SGsAP.receive(tr_SGsAP_PAGING_REJ(g_pars.imsi, ?)) { + [not exp_success] SGsAP.receive(tr_SGsAP_PAGING_REJ(g_pars.ue_pars.imsi, ?)) { setverdict(fail, "Received unexpected PAGING REJECT cause"); } [] SGsAP.receive { @@ -80,21 +80,21 @@ function f_sgsap_alert(template (omit) SGs_Cause exp_cause) runs on ConnHdlr{ if (not istemplatekind(exp_cause, "omit")) { exp_success := false; } - SGsAP.send(ts_SGsAP_ALERT_REQ(g_pars.imsi)); + SGsAP.send(ts_SGsAP_ALERT_REQ(g_pars.ue_pars.imsi)); alt { - [exp_success] SGsAP.receive(tr_SGsAP_ALERT_ACK(g_pars.imsi)) { + [exp_success] SGsAP.receive(tr_SGsAP_ALERT_ACK(g_pars.ue_pars.imsi)) { setverdict(pass); } - [exp_success] SGsAP.receive(tr_SGsAP_ALERT_REJECT(g_pars.imsi, ?)) -> value rx { + [exp_success] SGsAP.receive(tr_SGsAP_ALERT_REJECT(g_pars.ue_pars.imsi, ?)) -> value rx { setverdict(fail, "Received unexpected ALERT REJECT ", rx); } - [not exp_success] SGsAP.receive(tr_SGsAP_ALERT_ACK(g_pars.imsi)) { + [not exp_success] SGsAP.receive(tr_SGsAP_ALERT_ACK(g_pars.ue_pars.imsi)) { setverdict(fail, "Received unexpected ALERT ACK"); } - [not exp_success] SGsAP.receive(tr_SGsAP_ALERT_REJECT(g_pars.imsi, exp_cause)) { + [not exp_success] SGsAP.receive(tr_SGsAP_ALERT_REJECT(g_pars.ue_pars.imsi, exp_cause)) { setverdict(pass) } - [not exp_success] SGsAP.receive(tr_SGsAP_ALERT_REJECT(g_pars.imsi, ?)) -> value rx { + [not exp_success] SGsAP.receive(tr_SGsAP_ALERT_REJECT(g_pars.ue_pars.imsi, ?)) -> value rx { setverdict(fail, "Received ALERT REJECT with unexpected cause ", rx); } [] SGsAP.receive { @@ -168,7 +168,7 @@ private function f_TC_sgsap_alert(ConnHdlrPars pars) runs on ConnHdlr { /* TODO: register subscriber on S1 */ f_sgsap_alert(omit); /* TOOD: do something on S1 triggering UE ACT IND */ - SGsAP.receive(tr_SGsAP_UE_ACT_IND(g_pars.imsi)); + SGsAP.receive(tr_SGsAP_UE_ACT_IND(g_pars.ue_pars.imsi)); } testcase TC_sgsap_alert() runs on MTC_CT { var ConnHdlrPars pars; diff --git a/mme/gen_links.sh b/mme/gen_links.sh index 8c1da59f3..66effb55f 100755 --- a/mme/gen_links.sh +++ b/mme/gen_links.sh @@ -38,14 +38,14 @@ gen_links $DIR $FILES DIR=../library/s1ap FILES="S1AP_CommonDataTypes.asn S1AP_Constants.asn S1AP_Containers.asn S1AP_IEs.asn S1AP_PDU_Contents.asn S1AP_PDU_Descriptions.asn " -FILES+="S1AP_EncDec.cc S1AP_Types.ttcn " +FILES+="S1AP_EncDec.cc S1AP_Types.ttcn S1AP_Templates.ttcn " gen_links $DIR $FILES DIR=../library FILES="Misc_Helpers.ttcn General_Types.ttcn GSM_Types.ttcn Osmocom_Types.ttcn Native_Functions.ttcn Native_FunctionDefs.cc " FILES+="SGsAP_Templates.ttcn SGsAP_CodecPort.ttcn SGsAP_CodecPort_CtrlFunct.ttcn SGsAP_CodecPort_CtrlFunctDef.cc SGsAP_Emulation.ttcn DNS_Helpers.ttcn " FILES+="L3_Templates.ttcn " -FILES+="S1AP_CodecPort.ttcn " +FILES+="S1AP_CodecPort.ttcn S1AP_CodecPort_CtrlFunctDef.cc S1AP_CodecPort_CtrlFunct.ttcn S1AP_Emulation.ttcn " FILES+="NAS_Templates.ttcn " gen_links $DIR $FILES diff --git a/mme/regen_makefile.sh b/mme/regen_makefile.sh index 475230210..75fc31d5c 100755 --- a/mme/regen_makefile.sh +++ b/mme/regen_makefile.sh @@ -1,6 +1,6 @@ #!/bin/sh -FILES="*.ttcn *.asn *.c IPL4asp_PT.cc IPL4asp_discovery.cc Native_FunctionDefs.cc SGsAP_CodecPort_CtrlFunctDef.cc TCCConversion.cc TCCEncoding.cc TCCInterface.cc TELNETasp_PT.cc S1AP_EncDec.cc LTE_CryptoFunctionDefs.cc " +FILES="*.ttcn *.asn *.c IPL4asp_PT.cc IPL4asp_discovery.cc Native_FunctionDefs.cc SGsAP_CodecPort_CtrlFunctDef.cc S1AP_CodecPort_CtrlFunctDef.cc TCCConversion.cc TCCEncoding.cc TCCInterface.cc TELNETasp_PT.cc S1AP_EncDec.cc LTE_CryptoFunctionDefs.cc " export CPPFLAGS_TTCN3=""