/* GTP Emulation in TTCN-3 * * (C) 2018 Harald Welte * All rights reserved. * * Released under the terms of GNU General Public License, Version 2 or * (at your option) any later version. * * SPDX-License-Identifier: GPL-2.0-or-later */ module GTP_Emulation { import from IPL4asp_Types all; import from General_Types all; import from Osmocom_Types all; import from GTPC_Types all; import from GTPU_Types all; import from GTPv1C_CodecPort all; import from GTPv1U_CodecPort all; import from GTPv1C_CodecPort_CtrlFunct all; import from GTPv1U_CodecPort_CtrlFunct all; /*********************************************************************** * Main Emulation Component ***********************************************************************/ const integer GTP0_PORT := 3386; const integer GTP1C_PORT := 2123; const integer GTP1U_PORT := 2152; type record GtpEmulationCfg { HostName gtpc_bind_ip optional, PortNumber gtpc_bind_port optional, HostName gtpu_bind_ip optional, PortNumber gtpu_bind_port optional, boolean sgsn_role }; type component GTP_Emulation_CT { /* Communication with underlying GTP CodecPort */ port GTPC_PT GTPC; port GTPU_PT GTPU; /* Communication with Clients */ port GTPEM_PT CLIENT; port GTPEM_PROC_PT CLIENT_PROC; port GTPEM_PT CLIENT_DEFAULT; /* Configuration by the user */ var GtpEmulationCfg g_gtp_cfg; /* State */ var integer g_gtpc_id, g_gtpu_id; var OCT1 g_restart_ctr; var uint16_t g_c_seq_nr, g_u_seq_nr; var TidTableRec TidTable[16]; var ImsiTableRec ImsiTable[16]; }; type record TidTableRec { OCT4 teid, GTP_ConnHdlr vc_conn }; type record ImsiTableRec { hexstring imsi, GTP_ConnHdlr vc_conn }; private function f_comp_by_teid(OCT4 teid) runs on GTP_Emulation_CT return GTP_ConnHdlr { var integer i; for (i := 0; i < sizeof(TidTable); i := i+1) { if (isbound(TidTable[i].teid) and TidTable[i].teid == teid) { return TidTable[i].vc_conn; } } setverdict(fail, "No Component for TEID ", teid); mtc.stop; } private function f_comp_by_imsi(hexstring imsi) runs on GTP_Emulation_CT return GTP_ConnHdlr { var integer i; for (i := 0; i < sizeof(ImsiTable); i := i+1) { if (isbound(ImsiTable[i].imsi) and ImsiTable[i].imsi == imsi) { return ImsiTable[i].vc_conn; } } setverdict(fail, "No Component for IMSI ", imsi); mtc.stop; } private function f_tid_tbl_add(OCT4 teid, GTP_ConnHdlr vc_conn) runs on GTP_Emulation_CT { var integer i; for (i := 0; i < sizeof(TidTable); i := i+1) { if (not isbound(TidTable[i].teid)) { TidTable[i].teid := teid; TidTable[i].vc_conn := vc_conn; return; } } testcase.stop("No Space in TidTable for ", teid); } private function f_imsi_tbl_add(hexstring imsi, GTP_ConnHdlr vc_conn) runs on GTP_Emulation_CT { var integer i; for (i := 0; i < sizeof(ImsiTable); i := i+1) { if (not isbound(ImsiTable[i].imsi)) { ImsiTable[i].imsi := imsi; ImsiTable[i].vc_conn := vc_conn; return; } } testcase.stop("No Space in IMSI Table for ", imsi); } function f_gtpc_extract_imsi(PDU_GTPC gtp) return template (omit) hexstring { if (ischosen(gtp.gtpc_pdu.createPDPContextRequest)) { return gtp.gtpc_pdu.createPDPContextRequest.imsi.digits; } else if (ischosen(gtp.gtpc_pdu.updatePDPContextRequest.updatePDPContextRequestSGSN)) { if (ispresent(gtp.gtpc_pdu.updatePDPContextRequest.updatePDPContextRequestSGSN.imsi.digits)) { return gtp.gtpc_pdu.updatePDPContextRequest.updatePDPContextRequestSGSN.imsi.digits; } else { return omit; } } else if (ischosen(gtp.gtpc_pdu.updatePDPContextRequest.updatePDPContextRequestGGSN)) { if (ispresent(gtp.gtpc_pdu.updatePDPContextRequest.updatePDPContextRequestGGSN.imsi.digits)) { return gtp.gtpc_pdu.updatePDPContextRequest.updatePDPContextRequestGGSN.imsi.digits; } else { return omit; } } else if (ischosen(gtp.gtpc_pdu.updatePDPContextRequest.updatePDPContextRequestCGW)) { if (ispresent(gtp.gtpc_pdu.updatePDPContextRequest.updatePDPContextRequestCGW.imsi.digits)) { return gtp.gtpc_pdu.updatePDPContextRequest.updatePDPContextRequestCGW.imsi.digits; } else { return omit; } } else if (ischosen(gtp.gtpc_pdu.pdu_NotificationRequest)) { return gtp.gtpc_pdu.pdu_NotificationRequest.imsi.digits; } else if (ischosen(gtp.gtpc_pdu.sendRouteingInformationForGPRSRequest)) { return gtp.gtpc_pdu.sendRouteingInformationForGPRSRequest.imsi.digits; } else if (ischosen(gtp.gtpc_pdu.sendRouteingInformationForGPRSResponse)) { return gtp.gtpc_pdu.sendRouteingInformationForGPRSResponse.imsi.digits; } else if (ischosen(gtp.gtpc_pdu.failureReportRequest)) { return gtp.gtpc_pdu.failureReportRequest.imsi.digits; } else if (ischosen(gtp.gtpc_pdu.noteMS_GPRSPresentRequest)) { return gtp.gtpc_pdu.noteMS_GPRSPresentRequest.imsi.digits; } else if (ischosen(gtp.gtpc_pdu.identificationResponse) ) { if (ispresent(gtp.gtpc_pdu.identificationResponse.imsi.digits)) { return gtp.gtpc_pdu.identificationResponse.imsi.digits; } else { return omit; } } else if (ischosen(gtp.gtpc_pdu.sgsn_ContextRequest)) { if (ispresent(gtp.gtpc_pdu.sgsn_ContextRequest.imsi.digits)) { return gtp.gtpc_pdu.sgsn_ContextRequest.imsi.digits; } else { return omit; } } else if (ischosen(gtp.gtpc_pdu.sgsn_ContextResponse)) { if (ispresent(gtp.gtpc_pdu.sgsn_ContextResponse.imsi.digits)) { return gtp.gtpc_pdu.sgsn_ContextResponse.imsi.digits; } else { return omit; } } else if (ischosen(gtp.gtpc_pdu.forwardRelocationRequest)) { if (ispresent(gtp.gtpc_pdu.forwardRelocationRequest.imsi.digits)) { return gtp.gtpc_pdu.forwardRelocationRequest.imsi.digits; } else { return omit; } } else if (ischosen(gtp.gtpc_pdu.relocationCancelRequest)) { if (ispresent(gtp.gtpc_pdu.relocationCancelRequest.imsi.digits)) { return gtp.gtpc_pdu.relocationCancelRequest.imsi.digits; } else { return omit; } } else if (ischosen(gtp.gtpc_pdu.uERegistrationQueryRequest)) { return gtp.gtpc_pdu.uERegistrationQueryRequest.imsi.digits; } else if (ischosen(gtp.gtpc_pdu.uERegistrationQueryResponse)) { return gtp.gtpc_pdu.uERegistrationQueryResponse.imsi.digits; } else if (ischosen(gtp.gtpc_pdu.mBMSNotificationRequest)) { return gtp.gtpc_pdu.mBMSNotificationRequest.imsi.digits; } else if (ischosen(gtp.gtpc_pdu.createMBMSContextRequest)) { if (ispresent(gtp.gtpc_pdu.createMBMSContextRequest.imsi.digits)) { return gtp.gtpc_pdu.createMBMSContextRequest.imsi.digits; } else { return omit; } } else if (ischosen(gtp.gtpc_pdu.deleteMBMSContextRequest)) { if (ispresent(gtp.gtpc_pdu.deleteMBMSContextRequest.imsi.digits)) { return gtp.gtpc_pdu.deleteMBMSContextRequest.imsi.digits; } else { return omit; } } else if (ischosen(gtp.gtpc_pdu.mS_InfoChangeNotificationRequest)) { if (ispresent(gtp.gtpc_pdu.mS_InfoChangeNotificationRequest.imsi.digits)) { return gtp.gtpc_pdu.mS_InfoChangeNotificationRequest.imsi.digits; } else { return omit; } } else if (ischosen(gtp.gtpc_pdu.mS_InfoChangeNotificationResponse)) { if (ispresent(gtp.gtpc_pdu.mS_InfoChangeNotificationResponse.imsi.digits)) { return gtp.gtpc_pdu.mS_InfoChangeNotificationResponse.imsi.digits; } else { return omit; } } else { return omit; } } private function f_init(GtpEmulationCfg cfg) runs on GTP_Emulation_CT { var Result res; if (isvalue(cfg.gtpc_bind_ip) and isvalue(cfg.gtpc_bind_port)) { map(self:GTPC, system:GTPC); res := GTPv1C_CodecPort_CtrlFunct.f_IPL4_listen(GTPC, cfg.gtpc_bind_ip, cfg.gtpc_bind_port, {udp:={}}); g_gtpc_id := res.connId; } if (isvalue(cfg.gtpu_bind_ip) and isvalue(cfg.gtpu_bind_port)) { map(self:GTPU, system:GTPU); res := GTPv1U_CodecPort_CtrlFunct.f_GTPU_listen(GTPU, cfg.gtpu_bind_ip, cfg.gtpu_bind_port, {udp:={}}); g_gtpu_id := res.connId; } g_restart_ctr := f_rnd_octstring(1); g_c_seq_nr := f_rnd_int(65535); g_u_seq_nr := f_rnd_int(65535); g_gtp_cfg := cfg; } function main(GtpEmulationCfg cfg) runs on GTP_Emulation_CT { var Gtp1cUnitdata g1c_ud; var Gtp1uUnitdata g1u_ud; var GTP_ConnHdlr vc_conn; var hexstring imsi; var OCT4 teid; f_init(cfg); while (true) { alt { /* route inbound GTP-C based on IMSI or TEID */ [] GTPC.receive(Gtp1cUnitdata:?) -> value g1c_ud { var template hexstring imsi_t := f_gtpc_extract_imsi(g1c_ud.gtpc); if (isvalue(imsi_t)) { vc_conn := f_comp_by_imsi(valueof(imsi_t)); CLIENT.send(g1c_ud) to vc_conn; } else if(g1c_ud.gtpc.teid != int2oct(0, 4)) { vc_conn := f_comp_by_teid(g1c_ud.gtpc.teid); CLIENT.send(g1c_ud) to vc_conn; } else { /* Check if a default port is set: */ if (CLIENT_DEFAULT.checkstate("Connected")) { CLIENT_DEFAULT.send(g1c_ud); } else { /* Send to all clients */ var integer i; for (i := 0; i < sizeof(TidTable); i := i+1) { if (isbound(TidTable[i].teid) and TidTable[i].teid == teid) { CLIENT.send(g1c_ud) to TidTable[i].vc_conn; } } } } } [] GTPU.receive(Gtp1uUnitdata:?) -> value g1u_ud { vc_conn := f_comp_by_teid(g1u_ud.gtpu.teid); CLIENT.send(g1u_ud) to vc_conn; } /* transparently forward any GTP-C / GTP-U from clients to peer[s] */ [] CLIENT.receive(Gtp1cUnitdata:?) -> value g1c_ud sender vc_conn { GTPC.send(g1c_ud); } [] CLIENT.receive(Gtp1uUnitdata:?) -> value g1u_ud sender vc_conn { GTPU.send(g1u_ud); } [] CLIENT_DEFAULT.receive(Gtp1cUnitdata:?) -> value g1c_ud sender vc_conn { GTPC.send(g1c_ud); } [] CLIENT_PROC.getcall(GTPEM_register_imsi:{?}) -> param(imsi) sender vc_conn { f_imsi_tbl_add(imsi, vc_conn); CLIENT_PROC.reply(GTPEM_register_imsi:{imsi}) to vc_conn; } [] CLIENT_PROC.getcall(GTPEM_register_teid:{?}) -> param(teid) sender vc_conn { f_tid_tbl_add(teid, vc_conn); CLIENT_PROC.reply(GTPEM_register_teid:{teid}) to vc_conn; } } } } /*********************************************************************** * Interaction between Main and Client Components ***********************************************************************/ type port GTPEM_PT message { inout Gtp1cUnitdata, Gtp1uUnitdata; } with { extension "internal" }; signature GTPEM_register_imsi(hexstring imsi); signature GTPEM_register_teid(OCT4 teid); type port GTPEM_PROC_PT procedure { inout GTPEM_register_imsi, GTPEM_register_teid; } with { extension "internal" }; /*********************************************************************** * Client Component ***********************************************************************/ /* maximum number of GTP ports a GTP_ConnHdlr component can manage. This allows * connection one GTP_ConnHdlr to several GTP_Emulation(s). */ const integer NUM_MAX_GTP := 4; type component GTP_ConnHdlr { port GTPEM_PT GTP[NUM_MAX_GTP]; port GTPEM_PROC_PT GTP_PROC[NUM_MAX_GTP]; }; function f_gtp_register_imsi(hexstring imsi, integer gtp_idx := 0) runs on GTP_ConnHdlr { GTP_PROC[gtp_idx].call(GTPEM_register_imsi:{imsi}) { [] GTP_PROC[gtp_idx].getreply(GTPEM_register_imsi:{imsi}); } } function f_gtp_register_teid(OCT4 teid, integer gtp_idx := 0) runs on GTP_ConnHdlr { GTP_PROC[gtp_idx].call(GTPEM_register_teid:{teid}) { [] GTP_PROC[gtp_idx].getreply(GTPEM_register_teid:{teid}); } } function f_gtp_teid_random() return OCT4 { return f_rnd_octstring(4); } }