blob: d9d9f7a9d99598bf25bb2a0d31bc2a0d82c9932b [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 *
Vadim Yanitskiyb46f01e2021-12-06 03:23:13 +030021 * Alternatively, all inbound DIAMETER PDUs can be routed to a single component
22 * regardless of the IMSI. This is called 'raw' mode and can be achieved by
23 * setting the 'raw' field in DIAMETEROps to true.
24 *
Harald Welted27ab242019-07-26 13:45:18 +020025 * (C) 2019 by Harald Welte <laforge@gnumonks.org>
26 * All rights reserved.
27 *
28 * Released under the terms of GNU General Public License, Version 2 or
29 * (at your option) any later version.
30 *
31 * SPDX-License-Identifier: GPL-2.0-or-later
32 */
33
34import from DIAMETER_CodecPort all;
35import from DIAMETER_CodecPort_CtrlFunct all;
36import from DIAMETER_Types all;
37import from DIAMETER_Templates all;
38import from Osmocom_Types all;
39import from IPL4asp_Types all;
Harald Welte61f73d52020-04-26 21:41:12 +020040import from Native_Functions all;
Harald Welted27ab242019-07-26 13:45:18 +020041
42type hexstring IMSI;
43
Harald Welted01b5d02020-04-26 22:05:53 +020044/* notify the recipient that a Capability Exchange happened */
45type record DiameterCapabilityExchgInd {
46 PDU_DIAMETER rx,
47 PDU_DIAMETER tx
48};
49
Harald Welted27ab242019-07-26 13:45:18 +020050type component DIAMETER_ConnHdlr {
51 port DIAMETER_Conn_PT DIAMETER;
52 /* procedure based port to register for incoming connections */
53 port DIAMETEREM_PROC_PT DIAMETER_PROC;
54}
55
56/* port between individual per-connection components and this dispatcher */
57type port DIAMETER_Conn_PT message {
Harald Welte0e038082019-08-18 19:38:54 +020058 inout PDU_DIAMETER;
Harald Welted27ab242019-07-26 13:45:18 +020059} with { extension "internal" };
60
61/* global test port e.g. for non-imsi/conn specific messages */
62type port DIAMETER_PT message {
Harald Welted01b5d02020-04-26 22:05:53 +020063 inout PDU_DIAMETER, DiameterCapabilityExchgInd;
Harald Welted27ab242019-07-26 13:45:18 +020064} with { extension "internal" };
65
66
67/* represents a single DIAMETER Association */
68type record AssociationData {
69 DIAMETER_ConnHdlr comp_ref,
70 hexstring imsi optional
71};
72
73type component DIAMETER_Emulation_CT {
74 /* Port facing to the UDP SUT */
75 port DIAMETER_CODEC_PT DIAMETER;
76 /* All DIAMETER_ConnHdlr DIAMETER ports connect here
77 * DIAMETER_Emulation_CT.main needs to figure out what messages
78 * to send where with CLIENT.send() to vc_conn */
79 port DIAMETER_Conn_PT DIAMETER_CLIENT;
80 /* currently tracked connections */
Pau Espin Pedrolb335f012022-04-11 12:07:41 +020081 var AssociationData DiameterAssocTable[256];
Harald Welted27ab242019-07-26 13:45:18 +020082 /* pending expected CRCX */
Pau Espin Pedrolb335f012022-04-11 12:07:41 +020083 var ExpectData DiameterExpectTable[256];
Harald Welted27ab242019-07-26 13:45:18 +020084 /* procedure based port to register for incoming connections */
85 port DIAMETEREM_PROC_PT DIAMETER_PROC;
86 /* test port for unit data messages */
87 port DIAMETER_PT DIAMETER_UNIT;
88
89 var charstring g_diameter_id;
90 var integer g_diameter_conn_id := -1;
91}
92
93type function DIAMETERCreateCallback(PDU_DIAMETER msg, hexstring imsi, charstring id)
94runs on DIAMETER_Emulation_CT return DIAMETER_ConnHdlr;
95
96type function DIAMETERUnitdataCallback(PDU_DIAMETER msg)
97runs on DIAMETER_Emulation_CT return template PDU_DIAMETER;
98
99type record DIAMETEROps {
100 DIAMETERCreateCallback create_cb,
Vadim Yanitskiyb46f01e2021-12-06 03:23:13 +0300101 DIAMETERUnitdataCallback unitdata_cb,
102 /* If true, this parameter disables IMSI based routing, so that all incoming
103 * PDUs get routed to a single component connected via the DIAMETER_UNIT port. */
104 boolean raw
Harald Welted27ab242019-07-26 13:45:18 +0200105}
106
107type record DIAMETER_conn_parameters {
108 HostName remote_ip,
109 PortNumber remote_sctp_port,
110 HostName local_ip,
Harald Welte61f73d52020-04-26 21:41:12 +0200111 PortNumber local_sctp_port,
112 charstring origin_host,
113 charstring origin_realm,
Pau Espin Pedrol33b47492022-03-08 17:43:01 +0100114 uint32_t auth_app_id optional,
115 uint32_t vendor_app_id optional
Harald Welted27ab242019-07-26 13:45:18 +0200116}
117
118function tr_DIAMETER_RecvFrom_R(template PDU_DIAMETER msg)
119runs on DIAMETER_Emulation_CT return template DIAMETER_RecvFrom {
120 var template DIAMETER_RecvFrom mrf := {
121 connId := g_diameter_conn_id,
122 remName := ?,
123 remPort := ?,
124 locName := ?,
125 locPort := ?,
126 msg := msg
127 }
128 return mrf;
129}
130
131private function f_imsi_known(hexstring imsi)
132runs on DIAMETER_Emulation_CT return boolean {
133 var integer i;
Pau Espin Pedrole4361702022-04-11 11:59:40 +0200134 for (i := 0; i < sizeof(DiameterAssocTable); i := i+1) {
135 if (DiameterAssocTable[i].imsi == imsi) {
Harald Welted27ab242019-07-26 13:45:18 +0200136 return true;
137 }
138 }
139 return false;
140}
141
142private function f_comp_known(DIAMETER_ConnHdlr client)
143runs on DIAMETER_Emulation_CT return boolean {
144 var integer i;
Pau Espin Pedrole4361702022-04-11 11:59:40 +0200145 for (i := 0; i < sizeof(DiameterAssocTable); i := i+1) {
146 if (DiameterAssocTable[i].comp_ref == client) {
Harald Welted27ab242019-07-26 13:45:18 +0200147 return true;
148 }
149 }
150 return false;
151}
152
153private function f_comp_by_imsi(hexstring imsi)
154runs on DIAMETER_Emulation_CT return DIAMETER_ConnHdlr {
155 var integer i;
Pau Espin Pedrole4361702022-04-11 11:59:40 +0200156 for (i := 0; i < sizeof(DiameterAssocTable); i := i+1) {
157 if (DiameterAssocTable[i].imsi == imsi) {
158 return DiameterAssocTable[i].comp_ref;
Harald Welted27ab242019-07-26 13:45:18 +0200159 }
160 }
161 setverdict(fail, "DIAMETER Association Table not found by IMSI", imsi);
162 mtc.stop;
163}
164
165private function f_imsi_by_comp(DIAMETER_ConnHdlr client)
166runs on DIAMETER_Emulation_CT return hexstring {
167 var integer i;
Pau Espin Pedrole4361702022-04-11 11:59:40 +0200168 for (i := 0; i < sizeof(DiameterAssocTable); i := i+1) {
169 if (DiameterAssocTable[i].comp_ref == client) {
170 return DiameterAssocTable[i].imsi;
Harald Welted27ab242019-07-26 13:45:18 +0200171 }
172 }
173 setverdict(fail, "DIAMETER Association Table not found by component ", client);
174 mtc.stop;
175}
176
177private function f_imsi_table_add(DIAMETER_ConnHdlr comp_ref, hexstring imsi)
178runs on DIAMETER_Emulation_CT {
179 var integer i;
Pau Espin Pedrole4361702022-04-11 11:59:40 +0200180 for (i := 0; i < sizeof(DiameterAssocTable); i := i+1) {
181 if (not isvalue(DiameterAssocTable[i].imsi)) {
182 DiameterAssocTable[i].imsi := imsi;
183 DiameterAssocTable[i].comp_ref := comp_ref;
Harald Welted27ab242019-07-26 13:45:18 +0200184 return;
185 }
186 }
187 testcase.stop("DIAMETER Association Table full!");
188}
189
190private function f_imsi_table_del(DIAMETER_ConnHdlr comp_ref, hexstring imsi)
191runs on DIAMETER_Emulation_CT {
192 var integer i;
Pau Espin Pedrole4361702022-04-11 11:59:40 +0200193 for (i := 0; i < sizeof(DiameterAssocTable); i := i+1) {
194 if (DiameterAssocTable[i].comp_ref == comp_ref and
195 DiameterAssocTable[i].imsi == imsi) {
196 DiameterAssocTable[i].imsi := omit;
197 DiameterAssocTable[i].comp_ref := null;
Harald Welted27ab242019-07-26 13:45:18 +0200198 return;
199 }
200 }
201 setverdict(fail, "DIAMETER Association Table: Couldn't find to-be-deleted entry!");
202 mtc.stop;
203}
204
205
206private function f_imsi_table_init()
207runs on DIAMETER_Emulation_CT {
Pau Espin Pedrole4361702022-04-11 11:59:40 +0200208 for (var integer i := 0; i < sizeof(DiameterAssocTable); i := i+1) {
209 DiameterAssocTable[i].comp_ref := null;
210 DiameterAssocTable[i].imsi := omit;
Harald Welted27ab242019-07-26 13:45:18 +0200211 }
212}
213
Harald Welted27ab242019-07-26 13:45:18 +0200214function f_DIAMETER_get_imsi(PDU_DIAMETER pdu) return template (omit) IMSI
215{
216 var template (omit) AVP imsi_avp;
217
218 imsi_avp := f_DIAMETER_get_avp(pdu, c_AVP_Code_BASE_NONE_User_Name);
219 if (istemplatekind(imsi_avp, "omit")) {
Harald Weltef9fb63e2020-04-26 18:07:19 +0200220 var template (omit) AVP sid_avp;
221 sid_avp := f_DIAMETER_get_avp(pdu, c_AVP_Code_DCC_NONE_Subscription_Id);
222 if (istemplatekind(sid_avp, "omit")) {
223 return omit;
224 }
225 var AVP_Grouped grp := valueof(sid_avp.avp_data.avp_DCC_NONE_Subscription_Id);
Pau Espin Pedrolb8cd34a2022-05-18 16:34:12 +0200226 if (not match(grp[0], tr_AVP_SubcrIdType(END_USER_IMSI))) {
Harald Weltef9fb63e2020-04-26 18:07:19 +0200227 return omit;
228 }
229 return str2hex(oct2char(grp[1].avp.avp_data.avp_DCC_NONE_Subscription_Id_Data));
Harald Welted27ab242019-07-26 13:45:18 +0200230 } else {
231 var octetstring imsi_oct := valueof(imsi_avp.avp_data.avp_BASE_NONE_User_Name);
232 return str2hex(oct2char(imsi_oct));
233 }
234}
235
236private template (value) SctpTuple ts_SCTP(template (omit) integer ppid := omit) := {
237 sinfo_stream := omit,
238 sinfo_ppid := ppid,
239 remSocks := omit,
240 assocId := omit
241};
242
243private template PortEvent tr_SctpAssocChange := {
244 sctpEvent := {
245 sctpAssocChange := ?
246 }
247}
248private template PortEvent tr_SctpPeerAddrChange := {
249 sctpEvent := {
250 sctpPeerAddrChange := ?
251 }
252}
253
254private function f_diameter_xceive(template (value) PDU_DIAMETER tx,
255 template PDU_DIAMETER rx_t := ?)
256runs on DIAMETER_Emulation_CT return PDU_DIAMETER {
257 timer T := 10.0;
258 var DIAMETER_RecvFrom mrf;
259
260 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, tx));
261 alt {
262 [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(rx_t)) -> value mrf { }
Vadim Yanitskiy3148a0e2023-02-11 09:27:42 +0700263 [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(?)) -> value mrf {
264 setverdict(fail, "Rx unexpected DIAMETER PDU: ", mrf);
265 mtc.stop;
266 }
Harald Welted27ab242019-07-26 13:45:18 +0200267 [] DIAMETER.receive(tr_SctpAssocChange) { repeat; }
268 [] DIAMETER.receive(tr_SctpPeerAddrChange) { repeat; }
269 [] T.timeout {
270 setverdict(fail, "Timeout waiting for ", rx_t);
271 mtc.stop;
272 }
273 }
274 return mrf.msg;
275}
276
277function main(DIAMETEROps ops, DIAMETER_conn_parameters p, charstring id) runs on DIAMETER_Emulation_CT {
278 var Result res;
279 g_diameter_id := id;
280 f_imsi_table_init();
281 f_expect_table_init();
282
283 map(self:DIAMETER, system:DIAMETER_CODEC_PT);
284 if (p.remote_sctp_port == -1) {
285 res := DIAMETER_CodecPort_CtrlFunct.f_IPL4_listen(DIAMETER, p.local_ip, p.local_sctp_port, { sctp := valueof(ts_SCTP) });
286 } else {
287 res := DIAMETER_CodecPort_CtrlFunct.f_IPL4_connect(DIAMETER, p.remote_ip, p.remote_sctp_port,
288 p.local_ip, p.local_sctp_port, -1, { sctp := valueof(ts_SCTP) });
289 }
290 if (not ispresent(res.connId)) {
291 setverdict(fail, "Could not connect DIAMETER socket, check your configuration");
292 mtc.stop;
293 }
294 g_diameter_conn_id := res.connId;
295
296 while (true) {
297 var DIAMETER_ConnHdlr vc_conn;
Harald Welted27ab242019-07-26 13:45:18 +0200298 var template IMSI imsi_t;
299 var hexstring imsi;
300 var DIAMETER_RecvFrom mrf;
301 var PDU_DIAMETER msg;
302 var charstring vlr_name, mme_name;
303 var PortEvent port_evt;
304
305 alt {
306 [] DIAMETER.receive(PortEvent:{connOpened := ?}) -> value port_evt {
307 g_diameter_conn_id := port_evt.connOpened.connId;
308 }
309 [] DIAMETER.receive(PortEvent:?) { }
310 /* DIAMETER from client */
311 [] DIAMETER_CLIENT.receive(PDU_DIAMETER:?) -> value msg sender vc_conn {
312 /* Pass message through */
313 /* TODO: check which ConnectionID client has allocated + store in table? */
314 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, msg));
315 }
316
317 /* handle CER/CEA handshake */
318 [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(tr_DIAMETER_R(cmd_code := Capabilities_Exchange))) -> value mrf {
319 var template (value) PDU_DIAMETER resp;
Pau Espin Pedrol33b47492022-03-08 17:43:01 +0100320 resp := f_ts_DIA_CEA(mrf.msg.hop_by_hop_id, mrf.msg.end_to_end_id, p.origin_host,
321 p.origin_realm, f_inet_addr(p.local_ip), p.auth_app_id, p.vendor_app_id);
Harald Welted27ab242019-07-26 13:45:18 +0200322 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, resp));
Harald Welted01b5d02020-04-26 22:05:53 +0200323 /* notify our user that the CER->CEA exchange has happened */
324 DIAMETER_UNIT.send(DiameterCapabilityExchgInd:{rx:=mrf.msg, tx:=valueof(resp)});
Harald Welted27ab242019-07-26 13:45:18 +0200325 }
Vadim Yanitskiy3f7a6dc2021-12-11 04:13:59 +0300326 /* handle DWR/DWA ping-pong */
327 [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(tr_DIA_DWR)) -> value mrf {
328 var template (value) PDU_DIAMETER resp;
329 resp := ts_DIA_DWA('00000001'O, p.origin_host, p.origin_realm,
330 hbh_id := mrf.msg.hop_by_hop_id,
331 ete_id := mrf.msg.end_to_end_id);
332 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, valueof(resp)));
333 }
Harald Welted27ab242019-07-26 13:45:18 +0200334
Vadim Yanitskiyb46f01e2021-12-06 03:23:13 +0300335 /* DIAMETER from the test suite */
336 [ops.raw] DIAMETER_UNIT.receive(PDU_DIAMETER:?) -> value msg {
337 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, msg));
338 }
339 /* DIAMETER from remote peer (raw mode) */
340 [ops.raw] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(?)) -> value mrf {
341 DIAMETER_UNIT.send(mrf.msg);
342 }
343 /* DIAMETER from remote peer (IMSI based routing) */
344 [not ops.raw] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(?)) -> value mrf {
Harald Welted27ab242019-07-26 13:45:18 +0200345 imsi_t := f_DIAMETER_get_imsi(mrf.msg);
346 if (isvalue(imsi_t)) {
347 imsi := valueof(imsi_t);
348 if (f_imsi_known(imsi)) {
349 vc_conn := f_comp_by_imsi(imsi);
350 DIAMETER_CLIENT.send(mrf.msg) to vc_conn;
351 } else {
352 vc_conn := ops.create_cb.apply(mrf.msg, imsi, id);
353 f_imsi_table_add(vc_conn, imsi);
354 DIAMETER_CLIENT.send(mrf.msg) to vc_conn;
355 }
356 } else {
357 /* message contained no IMSI; is not IMSI-oriented */
358 var template PDU_DIAMETER resp := ops.unitdata_cb.apply(mrf.msg);
359 if (isvalue(resp)) {
360 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, valueof(resp)));
361 }
362 }
363 }
364 [] DIAMETER.receive(tr_SctpAssocChange) { }
365 [] DIAMETER.receive(tr_SctpPeerAddrChange) { }
366 [] DIAMETER_PROC.getcall(DIAMETEREM_register:{?,?}) -> param(imsi, vc_conn) {
367 f_create_expect(imsi, vc_conn);
368 DIAMETER_PROC.reply(DIAMETEREM_register:{imsi, vc_conn}) to vc_conn;
369 }
370
371 }
372
373 }
374}
375
376/* "Expect" Handling */
377
378type record ExpectData {
379 hexstring imsi optional,
380 DIAMETER_ConnHdlr vc_conn
381}
382
383signature DIAMETEREM_register(in hexstring imsi, in DIAMETER_ConnHdlr hdlr);
384
385type port DIAMETEREM_PROC_PT procedure {
386 inout DIAMETEREM_register;
387} with { extension "internal" };
388
Pau Espin Pedrol69730a22022-04-11 12:00:53 +0200389/* Function that can be used as create_cb and will use the expect table */
Harald Welted27ab242019-07-26 13:45:18 +0200390function ExpectedCreateCallback(PDU_DIAMETER msg, hexstring imsi, charstring id)
391runs on DIAMETER_Emulation_CT return DIAMETER_ConnHdlr {
392 var DIAMETER_ConnHdlr ret := null;
393 var integer i;
394
395 for (i := 0; i < sizeof(DiameterExpectTable); i := i+1) {
396 if (not ispresent(DiameterExpectTable[i].imsi)) {
397 continue;
398 }
399 if (imsi == DiameterExpectTable[i].imsi) {
400 ret := DiameterExpectTable[i].vc_conn;
401 /* Release this entry */
402 DiameterExpectTable[i].imsi := omit;
403 DiameterExpectTable[i].vc_conn := null;
404 log("Found Expect[", i, "] for ", msg, " handled at ", ret);
405 return ret;
406 }
407 }
408 setverdict(fail, "Couldn't find Expect for ", msg);
409 mtc.stop;
410}
411
412private function f_create_expect(hexstring imsi, DIAMETER_ConnHdlr hdlr)
413runs on DIAMETER_Emulation_CT {
414 var integer i;
415
416 /* Check an entry like this is not already presnt */
417 for (i := 0; i < sizeof(DiameterExpectTable); i := i+1) {
418 if (imsi == DiameterExpectTable[i].imsi) {
419 setverdict(fail, "IMSI already present", imsi);
420 mtc.stop;
421 }
422 }
423 for (i := 0; i < sizeof(DiameterExpectTable); i := i+1) {
424 if (not ispresent(DiameterExpectTable[i].imsi)) {
425 DiameterExpectTable[i].imsi := imsi;
426 DiameterExpectTable[i].vc_conn := hdlr;
427 log("Created Expect[", i, "] for ", imsi, " to be handled at ", hdlr);
428 return;
429 }
430 }
431 testcase.stop("No space left in DiameterExpectTable")
432}
433
434/* client/conn_hdlr side function to use procedure port to create expect in emulation */
435function f_diameter_expect(hexstring imsi) runs on DIAMETER_ConnHdlr {
436 DIAMETER_PROC.call(DIAMETEREM_register:{imsi, self}) {
437 [] DIAMETER_PROC.getreply(DIAMETEREM_register:{?,?}) {};
438 }
439}
440
441private function f_expect_table_init()
442runs on DIAMETER_Emulation_CT {
443 var integer i;
444 for (i := 0; i < sizeof(DiameterExpectTable); i := i + 1) {
445 DiameterExpectTable[i].imsi := omit;
446 }
447}
448
449function DummyUnitdataCallback(PDU_DIAMETER msg)
450runs on DIAMETER_Emulation_CT return template PDU_DIAMETER {
451 log("Ignoring DIAMETER ", msg);
452 return omit;
453}
454
455
Harald Welted01b5d02020-04-26 22:05:53 +0200456function f_diameter_wait_capability(DIAMETER_PT pt)
457{
458 /* Wait for the Capability Exchange with the DUT */
459 timer T := 10.0;
460 T.start;
461 alt {
462 [] pt.receive(DiameterCapabilityExchgInd:?) {}
463 [] pt.receive {
464 setverdict(fail, "Unexpected receive waiting for DiameterCapabilityExchgInd");
465 mtc.stop;
466 }
467 [] T.timeout {
468 setverdict(fail, "Timeout waiting for DiameterCapabilityExchgInd");
469 mtc.stop;
470 }
471 }
472}
473
474
Harald Welted27ab242019-07-26 13:45:18 +0200475}