aboutsummaryrefslogtreecommitdiffstats
path: root/library/DIAMETER_Emulation.ttcn
diff options
context:
space:
mode:
Diffstat (limited to 'library/DIAMETER_Emulation.ttcn')
-rw-r--r--library/DIAMETER_Emulation.ttcn270
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)
{