diff options
Diffstat (limited to 'library/DIAMETER_Emulation.ttcn')
-rw-r--r-- | library/DIAMETER_Emulation.ttcn | 270 |
1 files changed, 191 insertions, 79 deletions
diff --git a/library/DIAMETER_Emulation.ttcn b/library/DIAMETER_Emulation.ttcn index f7a0f887..e5698fd5 100644 --- a/library/DIAMETER_Emulation.ttcn +++ b/library/DIAMETER_Emulation.ttcn @@ -18,6 +18,10 @@ module DIAMETER_Emulation { * the DiameterOps.unitdata_cb() callback, which is registered with an argument to the * main() function below. * + * Alternatively, all inbound DIAMETER PDUs can be routed to a single component + * regardless of the IMSI. This is called 'raw' mode and can be achieved by + * setting the 'raw' field in DIAMETEROps to true. + * * (C) 2019 by Harald Welte <laforge@gnumonks.org> * All rights reserved. * @@ -33,7 +37,9 @@ import from DIAMETER_Types all; import from DIAMETER_Templates all; import from Osmocom_Types all; import from IPL4asp_Types all; +import from TCCConversion_Functions all; import from Native_Functions all; +import from SCTP_Templates all; type hexstring IMSI; @@ -66,6 +72,12 @@ type record AssociationData { hexstring imsi optional }; +/* represents a single DIAMETER message identified by ete_id field */ +type record ETEIDData { + DIAMETER_ConnHdlr comp_ref, + UINT32 ete_id optional +}; + type component DIAMETER_Emulation_CT { /* Port facing to the UDP SUT */ port DIAMETER_CODEC_PT DIAMETER; @@ -74,9 +86,11 @@ type component DIAMETER_Emulation_CT { * to send where with CLIENT.send() to vc_conn */ port DIAMETER_Conn_PT DIAMETER_CLIENT; /* currently tracked connections */ - var AssociationData SgsapAssociationTable[16]; + var AssociationData DiameterAssocTable[256]; + /* Forward reply messages not containing IMSI to correct client port */ + var ETEIDData DiameterETEIDTable[256]; /* pending expected CRCX */ - var ExpectData DiameterExpectTable[8]; + var ExpectData DiameterExpectTable[256]; /* procedure based port to register for incoming connections */ port DIAMETEREM_PROC_PT DIAMETER_PROC; /* test port for unit data messages */ @@ -94,7 +108,10 @@ runs on DIAMETER_Emulation_CT return template PDU_DIAMETER; type record DIAMETEROps { DIAMETERCreateCallback create_cb, - DIAMETERUnitdataCallback unitdata_cb + DIAMETERUnitdataCallback unitdata_cb, + /* If true, this parameter disables IMSI based routing, so that all incoming + * PDUs get routed to a single component connected via the DIAMETER_UNIT port. */ + boolean raw } type record DIAMETER_conn_parameters { @@ -104,7 +121,8 @@ type record DIAMETER_conn_parameters { PortNumber local_sctp_port, charstring origin_host, charstring origin_realm, - uint32_t vendor_app_id + uint32_t auth_app_id optional, + uint32_t vendor_app_id optional } function tr_DIAMETER_RecvFrom_R(template PDU_DIAMETER msg) @@ -123,8 +141,8 @@ runs on DIAMETER_Emulation_CT return template DIAMETER_RecvFrom { private function f_imsi_known(hexstring imsi) runs on DIAMETER_Emulation_CT return boolean { var integer i; - for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) { - if (SgsapAssociationTable[i].imsi == imsi) { + for (i := 0; i < sizeof(DiameterAssocTable); i := i+1) { + if (DiameterAssocTable[i].imsi == imsi) { return true; } } @@ -134,8 +152,8 @@ runs on DIAMETER_Emulation_CT return boolean { private function f_comp_known(DIAMETER_ConnHdlr client) runs on DIAMETER_Emulation_CT return boolean { var integer i; - for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) { - if (SgsapAssociationTable[i].comp_ref == client) { + for (i := 0; i < sizeof(DiameterAssocTable); i := i+1) { + if (DiameterAssocTable[i].comp_ref == client) { return true; } } @@ -145,9 +163,9 @@ runs on DIAMETER_Emulation_CT return boolean { private function f_comp_by_imsi(hexstring imsi) runs on DIAMETER_Emulation_CT return DIAMETER_ConnHdlr { var integer i; - for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) { - if (SgsapAssociationTable[i].imsi == imsi) { - return SgsapAssociationTable[i].comp_ref; + for (i := 0; i < sizeof(DiameterAssocTable); i := i+1) { + if (DiameterAssocTable[i].imsi == imsi) { + return DiameterAssocTable[i].comp_ref; } } setverdict(fail, "DIAMETER Association Table not found by IMSI", imsi); @@ -157,9 +175,9 @@ runs on DIAMETER_Emulation_CT return DIAMETER_ConnHdlr { private function f_imsi_by_comp(DIAMETER_ConnHdlr client) runs on DIAMETER_Emulation_CT return hexstring { var integer i; - for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) { - if (SgsapAssociationTable[i].comp_ref == client) { - return SgsapAssociationTable[i].imsi; + for (i := 0; i < sizeof(DiameterAssocTable); i := i+1) { + if (DiameterAssocTable[i].comp_ref == client) { + return DiameterAssocTable[i].imsi; } } setverdict(fail, "DIAMETER Association Table not found by component ", client); @@ -169,10 +187,10 @@ runs on DIAMETER_Emulation_CT return hexstring { private function f_imsi_table_add(DIAMETER_ConnHdlr comp_ref, hexstring imsi) runs on DIAMETER_Emulation_CT { var integer i; - for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) { - if (not isvalue(SgsapAssociationTable[i].imsi)) { - SgsapAssociationTable[i].imsi := imsi; - SgsapAssociationTable[i].comp_ref := comp_ref; + for (i := 0; i < sizeof(DiameterAssocTable); i := i+1) { + if (not isvalue(DiameterAssocTable[i].imsi)) { + DiameterAssocTable[i].imsi := imsi; + DiameterAssocTable[i].comp_ref := comp_ref; return; } } @@ -182,11 +200,11 @@ runs on DIAMETER_Emulation_CT { private function f_imsi_table_del(DIAMETER_ConnHdlr comp_ref, hexstring imsi) runs on DIAMETER_Emulation_CT { var integer i; - for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) { - if (SgsapAssociationTable[i].comp_ref == comp_ref and - SgsapAssociationTable[i].imsi == imsi) { - SgsapAssociationTable[i].imsi := omit; - SgsapAssociationTable[i].comp_ref := null; + for (i := 0; i < sizeof(DiameterAssocTable); i := i+1) { + if (DiameterAssocTable[i].comp_ref == comp_ref and + DiameterAssocTable[i].imsi == imsi) { + DiameterAssocTable[i].imsi := omit; + DiameterAssocTable[i].comp_ref := null; return; } } @@ -194,30 +212,73 @@ runs on DIAMETER_Emulation_CT { mtc.stop; } - private function f_imsi_table_init() runs on DIAMETER_Emulation_CT { - for (var integer i := 0; i < sizeof(SgsapAssociationTable); i := i+1) { - SgsapAssociationTable[i].comp_ref := null; - SgsapAssociationTable[i].imsi := omit; + for (var integer i := 0; i < sizeof(DiameterAssocTable); i := i+1) { + DiameterAssocTable[i].comp_ref := null; + DiameterAssocTable[i].imsi := omit; } } -function f_DIAMETER_get_avp(PDU_DIAMETER pdu, template (present) AVP_Code avp_code) -return template (omit) AVP -{ +/* End-to-End ID table matching. */ +private function f_ete_id_known(UINT32 ete_id) +runs on DIAMETER_Emulation_CT return boolean { var integer i; + for (i := 0; i < sizeof(DiameterETEIDTable); i := i+1) { + if (DiameterETEIDTable[i].ete_id == ete_id) { + return true; + } + } + return false; +} - for (i := 0; i < lengthof(pdu.avps); i := i+1) { - if (not ispresent(pdu.avps[i].avp)) { - continue; +private function f_comp_by_ete_id(UINT32 ete_id) +runs on DIAMETER_Emulation_CT return DIAMETER_ConnHdlr { + var integer i; + for (i := 0; i < sizeof(DiameterETEIDTable); i := i+1) { + if (DiameterETEIDTable[i].ete_id == ete_id) { + return DiameterETEIDTable[i].comp_ref; } - var AVP_Header hdr := pdu.avps[i].avp.avp_header; - if (match(hdr.avp_code, avp_code)) { - return pdu.avps[i].avp; + } + setverdict(fail, "DIAMETER ETEID Table not found by ete_id", ete_id); + mtc.stop; +} + +private function f_eteid_table_add(DIAMETER_ConnHdlr comp_ref, UINT32 ete_id) +runs on DIAMETER_Emulation_CT { + var integer i; + for (i := 0; i < sizeof(DiameterETEIDTable); i := i+1) { + if (not isvalue(DiameterETEIDTable[i].ete_id)) { + DiameterETEIDTable[i].ete_id := ete_id; + DiameterETEIDTable[i].comp_ref := comp_ref; + return; } } - return omit; + testcase.stop("DIAMETER ETEID Table full!"); +} + +private function f_eteid_table_del(DIAMETER_ConnHdlr comp_ref, UINT32 ete_id) +runs on DIAMETER_Emulation_CT { + var integer i; + for (i := 0; i < sizeof(DiameterETEIDTable); i := i+1) { + if (DiameterETEIDTable[i].comp_ref == comp_ref and + DiameterETEIDTable[i].ete_id == ete_id) { + DiameterETEIDTable[i].ete_id := omit; + DiameterETEIDTable[i].comp_ref := null; + return; + } + } + setverdict(fail, "DIAMETER ETEID Table: Couldn't find to-be-deleted entry!"); + mtc.stop; +} + + +private function f_eteid_table_init() +runs on DIAMETER_Emulation_CT { + for (var integer i := 0; i < sizeof(DiameterETEIDTable); i := i+1) { + DiameterETEIDTable[i].comp_ref := null; + DiameterETEIDTable[i].ete_id := omit; + } } function f_DIAMETER_get_imsi(PDU_DIAMETER pdu) return template (omit) IMSI @@ -232,31 +293,19 @@ function f_DIAMETER_get_imsi(PDU_DIAMETER pdu) return template (omit) IMSI return omit; } var AVP_Grouped grp := valueof(sid_avp.avp_data.avp_DCC_NONE_Subscription_Id); - if (not match(grp[0], tr_SubcrIdType(END_USER_IMSI))) { + if (not match(grp[0], tr_AVP_SubcrIdType(END_USER_IMSI))) { return omit; } return str2hex(oct2char(grp[1].avp.avp_data.avp_DCC_NONE_Subscription_Id_Data)); } else { var octetstring imsi_oct := valueof(imsi_avp.avp_data.avp_BASE_NONE_User_Name); - return str2hex(oct2char(imsi_oct)); - } -} - -private template (value) SctpTuple ts_SCTP(template (omit) integer ppid := omit) := { - sinfo_stream := omit, - sinfo_ppid := ppid, - remSocks := omit, - assocId := omit -}; - -private template PortEvent tr_SctpAssocChange := { - sctpEvent := { - sctpAssocChange := ? - } -} -private template PortEvent tr_SctpPeerAddrChange := { - sctpEvent := { - sctpPeerAddrChange := ? + var charstring imsi_str := oct2char(imsi_oct); + /* Username may be a NAI instead of IMSI: "<IMSI>@nai.epc.mnc<MNC>.mcc<MCC>.3gppnetwork.org" */ + var integer pos := f_strstr(imsi_str, "@"); + if (pos != -1) { + imsi_str := substr(imsi_str, 0, pos); + } + return str2hex(imsi_str); } } @@ -267,10 +316,15 @@ runs on DIAMETER_Emulation_CT return PDU_DIAMETER { var DIAMETER_RecvFrom mrf; DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, tx)); + T.start; alt { [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(rx_t)) -> value mrf { } + [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(?)) -> value mrf { + setverdict(fail, "Rx unexpected DIAMETER PDU: ", mrf); + mtc.stop; + } [] DIAMETER.receive(tr_SctpAssocChange) { repeat; } - [] DIAMETER.receive(tr_SctpPeerAddrChange) { repeat; } + [] DIAMETER.receive(tr_SctpPeerAddrChange) { repeat; } [] T.timeout { setverdict(fail, "Timeout waiting for ", rx_t); mtc.stop; @@ -280,17 +334,21 @@ runs on DIAMETER_Emulation_CT return PDU_DIAMETER { } function main(DIAMETEROps ops, DIAMETER_conn_parameters p, charstring id) runs on DIAMETER_Emulation_CT { + var boolean server_mode := p.remote_sctp_port == -1; var Result res; g_diameter_id := id; f_imsi_table_init(); + f_eteid_table_init(); f_expect_table_init(); map(self:DIAMETER, system:DIAMETER_CODEC_PT); - if (p.remote_sctp_port == -1) { - res := DIAMETER_CodecPort_CtrlFunct.f_IPL4_listen(DIAMETER, p.local_ip, p.local_sctp_port, { sctp := valueof(ts_SCTP) }); + if (server_mode) { + res := DIAMETER_CodecPort_CtrlFunct.f_IPL4_listen(DIAMETER, p.local_ip, p.local_sctp_port, + { sctp := valueof(ts_SctpTuple) }); } else { res := DIAMETER_CodecPort_CtrlFunct.f_IPL4_connect(DIAMETER, p.remote_ip, p.remote_sctp_port, - p.local_ip, p.local_sctp_port, -1, { sctp := valueof(ts_SCTP) }); + p.local_ip, p.local_sctp_port, -1, + { sctp := valueof(ts_SctpTuple) }); } if (not ispresent(res.connId)) { setverdict(fail, "Could not connect DIAMETER socket, check your configuration"); @@ -298,10 +356,23 @@ function main(DIAMETEROps ops, DIAMETER_conn_parameters p, charstring id) runs o } g_diameter_conn_id := res.connId; + /* If in client mode, send CER immediately */ + if (not server_mode) { + var template (value) PDU_DIAMETER req; + var PDU_DIAMETER rsp; + + req := ts_DIA_CER(f_inet_addr(p.local_ip), p.vendor_app_id, + orig_host := p.origin_host, orig_realm := p.origin_realm); + rsp := f_diameter_xceive(req, tr_DIAMETER_A(Capabilities_Exchange, req.application_id)); + /* notify our user that the CER->CEA exchange has happened */ + DIAMETER_UNIT.send(DiameterCapabilityExchgInd:{rx := rsp, tx := valueof(req)}); + } + while (true) { var DIAMETER_ConnHdlr vc_conn; var template IMSI imsi_t; var hexstring imsi; + var UINT32 ete_id; var DIAMETER_RecvFrom mrf; var PDU_DIAMETER msg; var charstring vlr_name, mme_name; @@ -322,17 +393,39 @@ function main(DIAMETEROps ops, DIAMETER_conn_parameters p, charstring id) runs o /* handle CER/CEA handshake */ [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(tr_DIAMETER_R(cmd_code := Capabilities_Exchange))) -> value mrf { var template (value) PDU_DIAMETER resp; - resp := ts_DIA_CEA(mrf.msg.hop_by_hop_id, mrf.msg.end_to_end_id, p.origin_host, - p.origin_realm, f_inet_addr(p.local_ip), p.vendor_app_id); + resp := f_ts_DIA_CEA(mrf.msg.hop_by_hop_id, mrf.msg.end_to_end_id, p.origin_host, + p.origin_realm, f_inet_addr(p.local_ip), p.auth_app_id, p.vendor_app_id); DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, resp)); /* notify our user that the CER->CEA exchange has happened */ DIAMETER_UNIT.send(DiameterCapabilityExchgInd:{rx:=mrf.msg, tx:=valueof(resp)}); } + /* handle DWR/DWA ping-pong */ + [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(tr_DIA_DWR)) -> value mrf { + var template (value) PDU_DIAMETER resp; + resp := ts_DIA_DWA('00000001'O, p.origin_host, p.origin_realm, + hbh_id := mrf.msg.hop_by_hop_id, + ete_id := mrf.msg.end_to_end_id); + DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, valueof(resp))); + } - /* DIAMETER from remote peer */ - [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(?)) -> value mrf { + /* DIAMETER from the test suite */ + [ops.raw] DIAMETER_UNIT.receive(PDU_DIAMETER:?) -> value msg { + DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, msg)); + } + /* DIAMETER from remote peer (raw mode) */ + [ops.raw] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(?)) -> value mrf { + DIAMETER_UNIT.send(mrf.msg); + } + /* DIAMETER from remote peer (IMSI based routing) */ + [not ops.raw] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(?)) -> value mrf { imsi_t := f_DIAMETER_get_imsi(mrf.msg); - if (isvalue(imsi_t)) { + ete_id := mrf.msg.end_to_end_id; + if (f_ete_id_known(ete_id)) { + vc_conn := f_comp_by_ete_id(ete_id); + /* The ete_id is a single-time expect: */ + f_eteid_table_del(vc_conn, ete_id); + DIAMETER_CLIENT.send(mrf.msg) to vc_conn; + } else if (isvalue(imsi_t)) { imsi := valueof(imsi_t); if (f_imsi_known(imsi)) { vc_conn := f_comp_by_imsi(imsi); @@ -351,10 +444,14 @@ function main(DIAMETEROps ops, DIAMETER_conn_parameters p, charstring id) runs o } } [] DIAMETER.receive(tr_SctpAssocChange) { } - [] DIAMETER.receive(tr_SctpPeerAddrChange) { } - [] DIAMETER_PROC.getcall(DIAMETEREM_register:{?,?}) -> param(imsi, vc_conn) { + [] DIAMETER.receive(tr_SctpPeerAddrChange) { } + [] DIAMETER_PROC.getcall(DIAMETEREM_register_imsi:{?,?}) -> param(imsi, vc_conn) { f_create_expect(imsi, vc_conn); - DIAMETER_PROC.reply(DIAMETEREM_register:{imsi, vc_conn}) to vc_conn; + DIAMETER_PROC.reply(DIAMETEREM_register_imsi:{imsi, vc_conn}) to vc_conn; + } + [] DIAMETER_PROC.getcall(DIAMETEREM_register_eteid:{?,?}) -> param(ete_id, vc_conn) { + f_eteid_table_add(vc_conn, ete_id); + DIAMETER_PROC.reply(DIAMETEREM_register_eteid:{ete_id, vc_conn}) to vc_conn; } } @@ -362,20 +459,31 @@ function main(DIAMETEROps ops, DIAMETER_conn_parameters p, charstring id) runs o } } -/* "Expect" Handling */ +/* "E2E ID Expect" Handling */ +type record ExpectDataE2EID { + UINT32 ete_id optional, + DIAMETER_ConnHdlr vc_conn +} + +signature DIAMETEREM_register_eteid(in UINT32 ete_id, in DIAMETER_ConnHdlr hdlr); + +/* client/conn_hdlr side function to use procedure port to create expect in emulation */ +function f_diameter_expect_eteid(UINT32 ete_id) runs on DIAMETER_ConnHdlr { + DIAMETER_PROC.call(DIAMETEREM_register_eteid:{ete_id, self}) { + [] DIAMETER_PROC.getreply(DIAMETEREM_register_eteid:{?,?}) {}; + } +} + +/* "IMSI Expect" Handling */ type record ExpectData { hexstring imsi optional, DIAMETER_ConnHdlr vc_conn } -signature DIAMETEREM_register(in hexstring imsi, in DIAMETER_ConnHdlr hdlr); - -type port DIAMETEREM_PROC_PT procedure { - inout DIAMETEREM_register; -} with { extension "internal" }; +signature DIAMETEREM_register_imsi(in hexstring imsi, in DIAMETER_ConnHdlr hdlr); -/* Function that can be used as create_cb and will usse the expect table */ +/* Function that can be used as create_cb and will use the expect table */ function ExpectedCreateCallback(PDU_DIAMETER msg, hexstring imsi, charstring id) runs on DIAMETER_Emulation_CT return DIAMETER_ConnHdlr { var DIAMETER_ConnHdlr ret := null; @@ -421,9 +529,9 @@ runs on DIAMETER_Emulation_CT { } /* client/conn_hdlr side function to use procedure port to create expect in emulation */ -function f_diameter_expect(hexstring imsi) runs on DIAMETER_ConnHdlr { - DIAMETER_PROC.call(DIAMETEREM_register:{imsi, self}) { - [] DIAMETER_PROC.getreply(DIAMETEREM_register:{?,?}) {}; +function f_diameter_expect_imsi(hexstring imsi) runs on DIAMETER_ConnHdlr { + DIAMETER_PROC.call(DIAMETEREM_register_imsi:{imsi, self}) { + [] DIAMETER_PROC.getreply(DIAMETEREM_register_imsi:{?,?}) {}; } } @@ -441,6 +549,10 @@ runs on DIAMETER_Emulation_CT return template PDU_DIAMETER { return omit; } +type port DIAMETEREM_PROC_PT procedure { + inout DIAMETEREM_register_imsi; + inout DIAMETEREM_register_eteid; +} with { extension "internal" }; function f_diameter_wait_capability(DIAMETER_PT pt) { |