blob: 220ab111d97deea36e972fa5642ca94d924c9d0a [file] [log] [blame]
Harald Welted27ab242019-07-26 13:45:18 +02001module DIAMETER_Emulation {
2
3/* DIAMETER Emulation, runs on top of DIAMETER_CodecPort. It multiplexes/demultiplexes
4 * the individual IMSIs/subscribers, so there can be separate TTCN-3 components handling
5 * each of them.
6 *
7 * The DIAMETER_Emulation.main() function processes DIAMETER primitives from the DIAMETER
8 * socket via the DIAMETER_CodecPort, and dispatches them to the per-IMSI components.
9 *
10 * For each new IMSI, the DiameterOps.create_cb() is called. It can create
11 * or resolve a TTCN-3 component, and returns a component reference to which that IMSI
12 * is routed/dispatched.
13 *
14 * If a pre-existing component wants to register to handle a future inbound IMSI, it can
15 * do so by registering an "expect" with the expected IMSI.
16 *
17 * Inbound DIAMETER messages without IMSI (such as RESET-IND/ACK) are dispatched to
18 * the DiameterOps.unitdata_cb() callback, which is registered with an argument to the
19 * main() function below.
20 *
21 * (C) 2019 by Harald Welte <laforge@gnumonks.org>
22 * All rights reserved.
23 *
24 * Released under the terms of GNU General Public License, Version 2 or
25 * (at your option) any later version.
26 *
27 * SPDX-License-Identifier: GPL-2.0-or-later
28 */
29
30import from DIAMETER_CodecPort all;
31import from DIAMETER_CodecPort_CtrlFunct all;
32import from DIAMETER_Types all;
33import from DIAMETER_Templates all;
34import from Osmocom_Types all;
35import from IPL4asp_Types all;
Harald Welte61f73d52020-04-26 21:41:12 +020036import from Native_Functions all;
Harald Welted27ab242019-07-26 13:45:18 +020037
38type hexstring IMSI;
39
Harald Welted01b5d02020-04-26 22:05:53 +020040/* notify the recipient that a Capability Exchange happened */
41type record DiameterCapabilityExchgInd {
42 PDU_DIAMETER rx,
43 PDU_DIAMETER tx
44};
45
Harald Welted27ab242019-07-26 13:45:18 +020046type component DIAMETER_ConnHdlr {
47 port DIAMETER_Conn_PT DIAMETER;
48 /* procedure based port to register for incoming connections */
49 port DIAMETEREM_PROC_PT DIAMETER_PROC;
50}
51
52/* port between individual per-connection components and this dispatcher */
53type port DIAMETER_Conn_PT message {
Harald Welte0e038082019-08-18 19:38:54 +020054 inout PDU_DIAMETER;
Harald Welted27ab242019-07-26 13:45:18 +020055} with { extension "internal" };
56
57/* global test port e.g. for non-imsi/conn specific messages */
58type port DIAMETER_PT message {
Harald Welted01b5d02020-04-26 22:05:53 +020059 inout PDU_DIAMETER, DiameterCapabilityExchgInd;
Harald Welted27ab242019-07-26 13:45:18 +020060} with { extension "internal" };
61
62
63/* represents a single DIAMETER Association */
64type record AssociationData {
65 DIAMETER_ConnHdlr comp_ref,
66 hexstring imsi optional
67};
68
69type component DIAMETER_Emulation_CT {
70 /* Port facing to the UDP SUT */
71 port DIAMETER_CODEC_PT DIAMETER;
72 /* All DIAMETER_ConnHdlr DIAMETER ports connect here
73 * DIAMETER_Emulation_CT.main needs to figure out what messages
74 * to send where with CLIENT.send() to vc_conn */
75 port DIAMETER_Conn_PT DIAMETER_CLIENT;
76 /* currently tracked connections */
77 var AssociationData SgsapAssociationTable[16];
78 /* pending expected CRCX */
79 var ExpectData DiameterExpectTable[8];
80 /* procedure based port to register for incoming connections */
81 port DIAMETEREM_PROC_PT DIAMETER_PROC;
82 /* test port for unit data messages */
83 port DIAMETER_PT DIAMETER_UNIT;
84
85 var charstring g_diameter_id;
86 var integer g_diameter_conn_id := -1;
87}
88
89type function DIAMETERCreateCallback(PDU_DIAMETER msg, hexstring imsi, charstring id)
90runs on DIAMETER_Emulation_CT return DIAMETER_ConnHdlr;
91
92type function DIAMETERUnitdataCallback(PDU_DIAMETER msg)
93runs on DIAMETER_Emulation_CT return template PDU_DIAMETER;
94
95type record DIAMETEROps {
96 DIAMETERCreateCallback create_cb,
97 DIAMETERUnitdataCallback unitdata_cb
98}
99
100type record DIAMETER_conn_parameters {
101 HostName remote_ip,
102 PortNumber remote_sctp_port,
103 HostName local_ip,
Harald Welte61f73d52020-04-26 21:41:12 +0200104 PortNumber local_sctp_port,
105 charstring origin_host,
106 charstring origin_realm,
107 uint32_t vendor_app_id
Harald Welted27ab242019-07-26 13:45:18 +0200108}
109
110function tr_DIAMETER_RecvFrom_R(template PDU_DIAMETER msg)
111runs on DIAMETER_Emulation_CT return template DIAMETER_RecvFrom {
112 var template DIAMETER_RecvFrom mrf := {
113 connId := g_diameter_conn_id,
114 remName := ?,
115 remPort := ?,
116 locName := ?,
117 locPort := ?,
118 msg := msg
119 }
120 return mrf;
121}
122
123private function f_imsi_known(hexstring imsi)
124runs on DIAMETER_Emulation_CT return boolean {
125 var integer i;
126 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
127 if (SgsapAssociationTable[i].imsi == imsi) {
128 return true;
129 }
130 }
131 return false;
132}
133
134private function f_comp_known(DIAMETER_ConnHdlr client)
135runs on DIAMETER_Emulation_CT return boolean {
136 var integer i;
137 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
138 if (SgsapAssociationTable[i].comp_ref == client) {
139 return true;
140 }
141 }
142 return false;
143}
144
145private function f_comp_by_imsi(hexstring imsi)
146runs on DIAMETER_Emulation_CT return DIAMETER_ConnHdlr {
147 var integer i;
148 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
149 if (SgsapAssociationTable[i].imsi == imsi) {
150 return SgsapAssociationTable[i].comp_ref;
151 }
152 }
153 setverdict(fail, "DIAMETER Association Table not found by IMSI", imsi);
154 mtc.stop;
155}
156
157private function f_imsi_by_comp(DIAMETER_ConnHdlr client)
158runs on DIAMETER_Emulation_CT return hexstring {
159 var integer i;
160 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
161 if (SgsapAssociationTable[i].comp_ref == client) {
162 return SgsapAssociationTable[i].imsi;
163 }
164 }
165 setverdict(fail, "DIAMETER Association Table not found by component ", client);
166 mtc.stop;
167}
168
169private function f_imsi_table_add(DIAMETER_ConnHdlr comp_ref, hexstring imsi)
170runs on DIAMETER_Emulation_CT {
171 var integer i;
172 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
173 if (not isvalue(SgsapAssociationTable[i].imsi)) {
174 SgsapAssociationTable[i].imsi := imsi;
175 SgsapAssociationTable[i].comp_ref := comp_ref;
176 return;
177 }
178 }
179 testcase.stop("DIAMETER Association Table full!");
180}
181
182private function f_imsi_table_del(DIAMETER_ConnHdlr comp_ref, hexstring imsi)
183runs on DIAMETER_Emulation_CT {
184 var integer i;
185 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
186 if (SgsapAssociationTable[i].comp_ref == comp_ref and
187 SgsapAssociationTable[i].imsi == imsi) {
188 SgsapAssociationTable[i].imsi := omit;
189 SgsapAssociationTable[i].comp_ref := null;
190 return;
191 }
192 }
193 setverdict(fail, "DIAMETER Association Table: Couldn't find to-be-deleted entry!");
194 mtc.stop;
195}
196
197
198private function f_imsi_table_init()
199runs on DIAMETER_Emulation_CT {
200 for (var integer i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
201 SgsapAssociationTable[i].comp_ref := null;
202 SgsapAssociationTable[i].imsi := omit;
203 }
204}
205
206function f_DIAMETER_get_avp(PDU_DIAMETER pdu, template (present) AVP_Code avp_code)
207return template (omit) AVP
208{
209 var integer i;
210
211 for (i := 0; i < lengthof(pdu.avps); i := i+1) {
212 if (not ispresent(pdu.avps[i].avp)) {
213 continue;
214 }
215 var AVP_Header hdr := pdu.avps[i].avp.avp_header;
216 if (match(hdr.avp_code, avp_code)) {
217 return pdu.avps[i].avp;
218 }
219 }
220 return omit;
221}
222
223function f_DIAMETER_get_imsi(PDU_DIAMETER pdu) return template (omit) IMSI
224{
225 var template (omit) AVP imsi_avp;
226
227 imsi_avp := f_DIAMETER_get_avp(pdu, c_AVP_Code_BASE_NONE_User_Name);
228 if (istemplatekind(imsi_avp, "omit")) {
Harald Weltef9fb63e2020-04-26 18:07:19 +0200229 var template (omit) AVP sid_avp;
230 sid_avp := f_DIAMETER_get_avp(pdu, c_AVP_Code_DCC_NONE_Subscription_Id);
231 if (istemplatekind(sid_avp, "omit")) {
232 return omit;
233 }
234 var AVP_Grouped grp := valueof(sid_avp.avp_data.avp_DCC_NONE_Subscription_Id);
235 if (not match(grp[0], tr_SubcrIdType(END_USER_IMSI))) {
236 return omit;
237 }
238 return str2hex(oct2char(grp[1].avp.avp_data.avp_DCC_NONE_Subscription_Id_Data));
Harald Welted27ab242019-07-26 13:45:18 +0200239 } else {
240 var octetstring imsi_oct := valueof(imsi_avp.avp_data.avp_BASE_NONE_User_Name);
241 return str2hex(oct2char(imsi_oct));
242 }
243}
244
245private template (value) SctpTuple ts_SCTP(template (omit) integer ppid := omit) := {
246 sinfo_stream := omit,
247 sinfo_ppid := ppid,
248 remSocks := omit,
249 assocId := omit
250};
251
252private template PortEvent tr_SctpAssocChange := {
253 sctpEvent := {
254 sctpAssocChange := ?
255 }
256}
257private template PortEvent tr_SctpPeerAddrChange := {
258 sctpEvent := {
259 sctpPeerAddrChange := ?
260 }
261}
262
263private function f_diameter_xceive(template (value) PDU_DIAMETER tx,
264 template PDU_DIAMETER rx_t := ?)
265runs on DIAMETER_Emulation_CT return PDU_DIAMETER {
266 timer T := 10.0;
267 var DIAMETER_RecvFrom mrf;
268
269 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, tx));
270 alt {
271 [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(rx_t)) -> value mrf { }
272 [] DIAMETER.receive(tr_SctpAssocChange) { repeat; }
273 [] DIAMETER.receive(tr_SctpPeerAddrChange) { repeat; }
274 [] T.timeout {
275 setverdict(fail, "Timeout waiting for ", rx_t);
276 mtc.stop;
277 }
278 }
279 return mrf.msg;
280}
281
282function main(DIAMETEROps ops, DIAMETER_conn_parameters p, charstring id) runs on DIAMETER_Emulation_CT {
283 var Result res;
284 g_diameter_id := id;
285 f_imsi_table_init();
286 f_expect_table_init();
287
288 map(self:DIAMETER, system:DIAMETER_CODEC_PT);
289 if (p.remote_sctp_port == -1) {
290 res := DIAMETER_CodecPort_CtrlFunct.f_IPL4_listen(DIAMETER, p.local_ip, p.local_sctp_port, { sctp := valueof(ts_SCTP) });
291 } else {
292 res := DIAMETER_CodecPort_CtrlFunct.f_IPL4_connect(DIAMETER, p.remote_ip, p.remote_sctp_port,
293 p.local_ip, p.local_sctp_port, -1, { sctp := valueof(ts_SCTP) });
294 }
295 if (not ispresent(res.connId)) {
296 setverdict(fail, "Could not connect DIAMETER socket, check your configuration");
297 mtc.stop;
298 }
299 g_diameter_conn_id := res.connId;
300
301 while (true) {
302 var DIAMETER_ConnHdlr vc_conn;
Harald Welted27ab242019-07-26 13:45:18 +0200303 var template IMSI imsi_t;
304 var hexstring imsi;
305 var DIAMETER_RecvFrom mrf;
306 var PDU_DIAMETER msg;
307 var charstring vlr_name, mme_name;
308 var PortEvent port_evt;
309
310 alt {
311 [] DIAMETER.receive(PortEvent:{connOpened := ?}) -> value port_evt {
312 g_diameter_conn_id := port_evt.connOpened.connId;
313 }
314 [] DIAMETER.receive(PortEvent:?) { }
315 /* DIAMETER from client */
316 [] DIAMETER_CLIENT.receive(PDU_DIAMETER:?) -> value msg sender vc_conn {
317 /* Pass message through */
318 /* TODO: check which ConnectionID client has allocated + store in table? */
319 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, msg));
320 }
321
322 /* handle CER/CEA handshake */
323 [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(tr_DIAMETER_R(cmd_code := Capabilities_Exchange))) -> value mrf {
324 var template (value) PDU_DIAMETER resp;
Harald Welte61f73d52020-04-26 21:41:12 +0200325 resp := ts_DIA_CEA(mrf.msg.hop_by_hop_id, mrf.msg.end_to_end_id, p.origin_host,
326 p.origin_realm, f_inet_addr(p.local_ip), p.vendor_app_id);
Harald Welted27ab242019-07-26 13:45:18 +0200327 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, resp));
Harald Welted01b5d02020-04-26 22:05:53 +0200328 /* notify our user that the CER->CEA exchange has happened */
329 DIAMETER_UNIT.send(DiameterCapabilityExchgInd:{rx:=mrf.msg, tx:=valueof(resp)});
Harald Welted27ab242019-07-26 13:45:18 +0200330 }
Vadim Yanitskiy3f7a6dc2021-12-11 04:13:59 +0300331 /* handle DWR/DWA ping-pong */
332 [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(tr_DIA_DWR)) -> value mrf {
333 var template (value) PDU_DIAMETER resp;
334 resp := ts_DIA_DWA('00000001'O, p.origin_host, p.origin_realm,
335 hbh_id := mrf.msg.hop_by_hop_id,
336 ete_id := mrf.msg.end_to_end_id);
337 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, valueof(resp)));
338 }
Harald Welted27ab242019-07-26 13:45:18 +0200339
340 /* DIAMETER from remote peer */
341 [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(?)) -> value mrf {
342 imsi_t := f_DIAMETER_get_imsi(mrf.msg);
343 if (isvalue(imsi_t)) {
344 imsi := valueof(imsi_t);
345 if (f_imsi_known(imsi)) {
346 vc_conn := f_comp_by_imsi(imsi);
347 DIAMETER_CLIENT.send(mrf.msg) to vc_conn;
348 } else {
349 vc_conn := ops.create_cb.apply(mrf.msg, imsi, id);
350 f_imsi_table_add(vc_conn, imsi);
351 DIAMETER_CLIENT.send(mrf.msg) to vc_conn;
352 }
353 } else {
354 /* message contained no IMSI; is not IMSI-oriented */
355 var template PDU_DIAMETER resp := ops.unitdata_cb.apply(mrf.msg);
356 if (isvalue(resp)) {
357 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, valueof(resp)));
358 }
359 }
360 }
361 [] DIAMETER.receive(tr_SctpAssocChange) { }
362 [] DIAMETER.receive(tr_SctpPeerAddrChange) { }
363 [] DIAMETER_PROC.getcall(DIAMETEREM_register:{?,?}) -> param(imsi, vc_conn) {
364 f_create_expect(imsi, vc_conn);
365 DIAMETER_PROC.reply(DIAMETEREM_register:{imsi, vc_conn}) to vc_conn;
366 }
367
368 }
369
370 }
371}
372
373/* "Expect" Handling */
374
375type record ExpectData {
376 hexstring imsi optional,
377 DIAMETER_ConnHdlr vc_conn
378}
379
380signature DIAMETEREM_register(in hexstring imsi, in DIAMETER_ConnHdlr hdlr);
381
382type port DIAMETEREM_PROC_PT procedure {
383 inout DIAMETEREM_register;
384} with { extension "internal" };
385
386/* Function that can be used as create_cb and will usse the expect table */
387function ExpectedCreateCallback(PDU_DIAMETER msg, hexstring imsi, charstring id)
388runs on DIAMETER_Emulation_CT return DIAMETER_ConnHdlr {
389 var DIAMETER_ConnHdlr ret := null;
390 var integer i;
391
392 for (i := 0; i < sizeof(DiameterExpectTable); i := i+1) {
393 if (not ispresent(DiameterExpectTable[i].imsi)) {
394 continue;
395 }
396 if (imsi == DiameterExpectTable[i].imsi) {
397 ret := DiameterExpectTable[i].vc_conn;
398 /* Release this entry */
399 DiameterExpectTable[i].imsi := omit;
400 DiameterExpectTable[i].vc_conn := null;
401 log("Found Expect[", i, "] for ", msg, " handled at ", ret);
402 return ret;
403 }
404 }
405 setverdict(fail, "Couldn't find Expect for ", msg);
406 mtc.stop;
407}
408
409private function f_create_expect(hexstring imsi, DIAMETER_ConnHdlr hdlr)
410runs on DIAMETER_Emulation_CT {
411 var integer i;
412
413 /* Check an entry like this is not already presnt */
414 for (i := 0; i < sizeof(DiameterExpectTable); i := i+1) {
415 if (imsi == DiameterExpectTable[i].imsi) {
416 setverdict(fail, "IMSI already present", imsi);
417 mtc.stop;
418 }
419 }
420 for (i := 0; i < sizeof(DiameterExpectTable); i := i+1) {
421 if (not ispresent(DiameterExpectTable[i].imsi)) {
422 DiameterExpectTable[i].imsi := imsi;
423 DiameterExpectTable[i].vc_conn := hdlr;
424 log("Created Expect[", i, "] for ", imsi, " to be handled at ", hdlr);
425 return;
426 }
427 }
428 testcase.stop("No space left in DiameterExpectTable")
429}
430
431/* client/conn_hdlr side function to use procedure port to create expect in emulation */
432function f_diameter_expect(hexstring imsi) runs on DIAMETER_ConnHdlr {
433 DIAMETER_PROC.call(DIAMETEREM_register:{imsi, self}) {
434 [] DIAMETER_PROC.getreply(DIAMETEREM_register:{?,?}) {};
435 }
436}
437
438private function f_expect_table_init()
439runs on DIAMETER_Emulation_CT {
440 var integer i;
441 for (i := 0; i < sizeof(DiameterExpectTable); i := i + 1) {
442 DiameterExpectTable[i].imsi := omit;
443 }
444}
445
446function DummyUnitdataCallback(PDU_DIAMETER msg)
447runs on DIAMETER_Emulation_CT return template PDU_DIAMETER {
448 log("Ignoring DIAMETER ", msg);
449 return omit;
450}
451
452
Harald Welted01b5d02020-04-26 22:05:53 +0200453function f_diameter_wait_capability(DIAMETER_PT pt)
454{
455 /* Wait for the Capability Exchange with the DUT */
456 timer T := 10.0;
457 T.start;
458 alt {
459 [] pt.receive(DiameterCapabilityExchgInd:?) {}
460 [] pt.receive {
461 setverdict(fail, "Unexpected receive waiting for DiameterCapabilityExchgInd");
462 mtc.stop;
463 }
464 [] T.timeout {
465 setverdict(fail, "Timeout waiting for DiameterCapabilityExchgInd");
466 mtc.stop;
467 }
468 }
469}
470
471
Harald Welted27ab242019-07-26 13:45:18 +0200472}