blob: cff4d638b8d9fa6c9282f4b476849e88f8df2432 [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
40type component DIAMETER_ConnHdlr {
41 port DIAMETER_Conn_PT DIAMETER;
42 /* procedure based port to register for incoming connections */
43 port DIAMETEREM_PROC_PT DIAMETER_PROC;
44}
45
46/* port between individual per-connection components and this dispatcher */
47type port DIAMETER_Conn_PT message {
Harald Welte0e038082019-08-18 19:38:54 +020048 inout PDU_DIAMETER;
Harald Welted27ab242019-07-26 13:45:18 +020049} with { extension "internal" };
50
51/* global test port e.g. for non-imsi/conn specific messages */
52type port DIAMETER_PT message {
53 inout PDU_DIAMETER;
54} with { extension "internal" };
55
56
57/* represents a single DIAMETER Association */
58type record AssociationData {
59 DIAMETER_ConnHdlr comp_ref,
60 hexstring imsi optional
61};
62
63type component DIAMETER_Emulation_CT {
64 /* Port facing to the UDP SUT */
65 port DIAMETER_CODEC_PT DIAMETER;
66 /* All DIAMETER_ConnHdlr DIAMETER ports connect here
67 * DIAMETER_Emulation_CT.main needs to figure out what messages
68 * to send where with CLIENT.send() to vc_conn */
69 port DIAMETER_Conn_PT DIAMETER_CLIENT;
70 /* currently tracked connections */
71 var AssociationData SgsapAssociationTable[16];
72 /* pending expected CRCX */
73 var ExpectData DiameterExpectTable[8];
74 /* procedure based port to register for incoming connections */
75 port DIAMETEREM_PROC_PT DIAMETER_PROC;
76 /* test port for unit data messages */
77 port DIAMETER_PT DIAMETER_UNIT;
78
79 var charstring g_diameter_id;
80 var integer g_diameter_conn_id := -1;
81}
82
83type function DIAMETERCreateCallback(PDU_DIAMETER msg, hexstring imsi, charstring id)
84runs on DIAMETER_Emulation_CT return DIAMETER_ConnHdlr;
85
86type function DIAMETERUnitdataCallback(PDU_DIAMETER msg)
87runs on DIAMETER_Emulation_CT return template PDU_DIAMETER;
88
89type record DIAMETEROps {
90 DIAMETERCreateCallback create_cb,
91 DIAMETERUnitdataCallback unitdata_cb
92}
93
94type record DIAMETER_conn_parameters {
95 HostName remote_ip,
96 PortNumber remote_sctp_port,
97 HostName local_ip,
Harald Welte61f73d52020-04-26 21:41:12 +020098 PortNumber local_sctp_port,
99 charstring origin_host,
100 charstring origin_realm,
101 uint32_t vendor_app_id
Harald Welted27ab242019-07-26 13:45:18 +0200102}
103
104function tr_DIAMETER_RecvFrom_R(template PDU_DIAMETER msg)
105runs on DIAMETER_Emulation_CT return template DIAMETER_RecvFrom {
106 var template DIAMETER_RecvFrom mrf := {
107 connId := g_diameter_conn_id,
108 remName := ?,
109 remPort := ?,
110 locName := ?,
111 locPort := ?,
112 msg := msg
113 }
114 return mrf;
115}
116
117private function f_imsi_known(hexstring imsi)
118runs on DIAMETER_Emulation_CT return boolean {
119 var integer i;
120 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
121 if (SgsapAssociationTable[i].imsi == imsi) {
122 return true;
123 }
124 }
125 return false;
126}
127
128private function f_comp_known(DIAMETER_ConnHdlr client)
129runs on DIAMETER_Emulation_CT return boolean {
130 var integer i;
131 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
132 if (SgsapAssociationTable[i].comp_ref == client) {
133 return true;
134 }
135 }
136 return false;
137}
138
139private function f_comp_by_imsi(hexstring imsi)
140runs on DIAMETER_Emulation_CT return DIAMETER_ConnHdlr {
141 var integer i;
142 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
143 if (SgsapAssociationTable[i].imsi == imsi) {
144 return SgsapAssociationTable[i].comp_ref;
145 }
146 }
147 setverdict(fail, "DIAMETER Association Table not found by IMSI", imsi);
148 mtc.stop;
149}
150
151private function f_imsi_by_comp(DIAMETER_ConnHdlr client)
152runs on DIAMETER_Emulation_CT return hexstring {
153 var integer i;
154 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
155 if (SgsapAssociationTable[i].comp_ref == client) {
156 return SgsapAssociationTable[i].imsi;
157 }
158 }
159 setverdict(fail, "DIAMETER Association Table not found by component ", client);
160 mtc.stop;
161}
162
163private function f_imsi_table_add(DIAMETER_ConnHdlr comp_ref, hexstring imsi)
164runs on DIAMETER_Emulation_CT {
165 var integer i;
166 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
167 if (not isvalue(SgsapAssociationTable[i].imsi)) {
168 SgsapAssociationTable[i].imsi := imsi;
169 SgsapAssociationTable[i].comp_ref := comp_ref;
170 return;
171 }
172 }
173 testcase.stop("DIAMETER Association Table full!");
174}
175
176private function f_imsi_table_del(DIAMETER_ConnHdlr comp_ref, hexstring imsi)
177runs on DIAMETER_Emulation_CT {
178 var integer i;
179 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
180 if (SgsapAssociationTable[i].comp_ref == comp_ref and
181 SgsapAssociationTable[i].imsi == imsi) {
182 SgsapAssociationTable[i].imsi := omit;
183 SgsapAssociationTable[i].comp_ref := null;
184 return;
185 }
186 }
187 setverdict(fail, "DIAMETER Association Table: Couldn't find to-be-deleted entry!");
188 mtc.stop;
189}
190
191
192private function f_imsi_table_init()
193runs on DIAMETER_Emulation_CT {
194 for (var integer i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
195 SgsapAssociationTable[i].comp_ref := null;
196 SgsapAssociationTable[i].imsi := omit;
197 }
198}
199
200function f_DIAMETER_get_avp(PDU_DIAMETER pdu, template (present) AVP_Code avp_code)
201return template (omit) AVP
202{
203 var integer i;
204
205 for (i := 0; i < lengthof(pdu.avps); i := i+1) {
206 if (not ispresent(pdu.avps[i].avp)) {
207 continue;
208 }
209 var AVP_Header hdr := pdu.avps[i].avp.avp_header;
210 if (match(hdr.avp_code, avp_code)) {
211 return pdu.avps[i].avp;
212 }
213 }
214 return omit;
215}
216
217function f_DIAMETER_get_imsi(PDU_DIAMETER pdu) return template (omit) IMSI
218{
219 var template (omit) AVP imsi_avp;
220
221 imsi_avp := f_DIAMETER_get_avp(pdu, c_AVP_Code_BASE_NONE_User_Name);
222 if (istemplatekind(imsi_avp, "omit")) {
Harald Weltef9fb63e2020-04-26 18:07:19 +0200223 var template (omit) AVP sid_avp;
224 sid_avp := f_DIAMETER_get_avp(pdu, c_AVP_Code_DCC_NONE_Subscription_Id);
225 if (istemplatekind(sid_avp, "omit")) {
226 return omit;
227 }
228 var AVP_Grouped grp := valueof(sid_avp.avp_data.avp_DCC_NONE_Subscription_Id);
229 if (not match(grp[0], tr_SubcrIdType(END_USER_IMSI))) {
230 return omit;
231 }
232 return str2hex(oct2char(grp[1].avp.avp_data.avp_DCC_NONE_Subscription_Id_Data));
Harald Welted27ab242019-07-26 13:45:18 +0200233 } else {
234 var octetstring imsi_oct := valueof(imsi_avp.avp_data.avp_BASE_NONE_User_Name);
235 return str2hex(oct2char(imsi_oct));
236 }
237}
238
239private template (value) SctpTuple ts_SCTP(template (omit) integer ppid := omit) := {
240 sinfo_stream := omit,
241 sinfo_ppid := ppid,
242 remSocks := omit,
243 assocId := omit
244};
245
246private template PortEvent tr_SctpAssocChange := {
247 sctpEvent := {
248 sctpAssocChange := ?
249 }
250}
251private template PortEvent tr_SctpPeerAddrChange := {
252 sctpEvent := {
253 sctpPeerAddrChange := ?
254 }
255}
256
257private function f_diameter_xceive(template (value) PDU_DIAMETER tx,
258 template PDU_DIAMETER rx_t := ?)
259runs on DIAMETER_Emulation_CT return PDU_DIAMETER {
260 timer T := 10.0;
261 var DIAMETER_RecvFrom mrf;
262
263 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, tx));
264 alt {
265 [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(rx_t)) -> value mrf { }
266 [] DIAMETER.receive(tr_SctpAssocChange) { repeat; }
267 [] DIAMETER.receive(tr_SctpPeerAddrChange) { repeat; }
268 [] T.timeout {
269 setverdict(fail, "Timeout waiting for ", rx_t);
270 mtc.stop;
271 }
272 }
273 return mrf.msg;
274}
275
276function main(DIAMETEROps ops, DIAMETER_conn_parameters p, charstring id) runs on DIAMETER_Emulation_CT {
277 var Result res;
278 g_diameter_id := id;
279 f_imsi_table_init();
280 f_expect_table_init();
281
282 map(self:DIAMETER, system:DIAMETER_CODEC_PT);
283 if (p.remote_sctp_port == -1) {
284 res := DIAMETER_CodecPort_CtrlFunct.f_IPL4_listen(DIAMETER, p.local_ip, p.local_sctp_port, { sctp := valueof(ts_SCTP) });
285 } else {
286 res := DIAMETER_CodecPort_CtrlFunct.f_IPL4_connect(DIAMETER, p.remote_ip, p.remote_sctp_port,
287 p.local_ip, p.local_sctp_port, -1, { sctp := valueof(ts_SCTP) });
288 }
289 if (not ispresent(res.connId)) {
290 setverdict(fail, "Could not connect DIAMETER socket, check your configuration");
291 mtc.stop;
292 }
293 g_diameter_conn_id := res.connId;
294
295 while (true) {
296 var DIAMETER_ConnHdlr vc_conn;
Harald Welted27ab242019-07-26 13:45:18 +0200297 var template IMSI imsi_t;
298 var hexstring imsi;
299 var DIAMETER_RecvFrom mrf;
300 var PDU_DIAMETER msg;
301 var charstring vlr_name, mme_name;
302 var PortEvent port_evt;
303
304 alt {
305 [] DIAMETER.receive(PortEvent:{connOpened := ?}) -> value port_evt {
306 g_diameter_conn_id := port_evt.connOpened.connId;
307 }
308 [] DIAMETER.receive(PortEvent:?) { }
309 /* DIAMETER from client */
310 [] DIAMETER_CLIENT.receive(PDU_DIAMETER:?) -> value msg sender vc_conn {
311 /* Pass message through */
312 /* TODO: check which ConnectionID client has allocated + store in table? */
313 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, msg));
314 }
315
316 /* handle CER/CEA handshake */
317 [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(tr_DIAMETER_R(cmd_code := Capabilities_Exchange))) -> value mrf {
318 var template (value) PDU_DIAMETER resp;
Harald Welte61f73d52020-04-26 21:41:12 +0200319 resp := ts_DIA_CEA(mrf.msg.hop_by_hop_id, mrf.msg.end_to_end_id, p.origin_host,
320 p.origin_realm, f_inet_addr(p.local_ip), p.vendor_app_id);
Harald Welted27ab242019-07-26 13:45:18 +0200321 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, resp));
322 }
323
324 /* DIAMETER from remote peer */
325 [] DIAMETER.receive(tr_DIAMETER_RecvFrom_R(?)) -> value mrf {
326 imsi_t := f_DIAMETER_get_imsi(mrf.msg);
327 if (isvalue(imsi_t)) {
328 imsi := valueof(imsi_t);
329 if (f_imsi_known(imsi)) {
330 vc_conn := f_comp_by_imsi(imsi);
331 DIAMETER_CLIENT.send(mrf.msg) to vc_conn;
332 } else {
333 vc_conn := ops.create_cb.apply(mrf.msg, imsi, id);
334 f_imsi_table_add(vc_conn, imsi);
335 DIAMETER_CLIENT.send(mrf.msg) to vc_conn;
336 }
337 } else {
338 /* message contained no IMSI; is not IMSI-oriented */
339 var template PDU_DIAMETER resp := ops.unitdata_cb.apply(mrf.msg);
340 if (isvalue(resp)) {
341 DIAMETER.send(t_DIAMETER_Send(g_diameter_conn_id, valueof(resp)));
342 }
343 }
344 }
345 [] DIAMETER.receive(tr_SctpAssocChange) { }
346 [] DIAMETER.receive(tr_SctpPeerAddrChange) { }
347 [] DIAMETER_PROC.getcall(DIAMETEREM_register:{?,?}) -> param(imsi, vc_conn) {
348 f_create_expect(imsi, vc_conn);
349 DIAMETER_PROC.reply(DIAMETEREM_register:{imsi, vc_conn}) to vc_conn;
350 }
351
352 }
353
354 }
355}
356
357/* "Expect" Handling */
358
359type record ExpectData {
360 hexstring imsi optional,
361 DIAMETER_ConnHdlr vc_conn
362}
363
364signature DIAMETEREM_register(in hexstring imsi, in DIAMETER_ConnHdlr hdlr);
365
366type port DIAMETEREM_PROC_PT procedure {
367 inout DIAMETEREM_register;
368} with { extension "internal" };
369
370/* Function that can be used as create_cb and will usse the expect table */
371function ExpectedCreateCallback(PDU_DIAMETER msg, hexstring imsi, charstring id)
372runs on DIAMETER_Emulation_CT return DIAMETER_ConnHdlr {
373 var DIAMETER_ConnHdlr ret := null;
374 var integer i;
375
376 for (i := 0; i < sizeof(DiameterExpectTable); i := i+1) {
377 if (not ispresent(DiameterExpectTable[i].imsi)) {
378 continue;
379 }
380 if (imsi == DiameterExpectTable[i].imsi) {
381 ret := DiameterExpectTable[i].vc_conn;
382 /* Release this entry */
383 DiameterExpectTable[i].imsi := omit;
384 DiameterExpectTable[i].vc_conn := null;
385 log("Found Expect[", i, "] for ", msg, " handled at ", ret);
386 return ret;
387 }
388 }
389 setverdict(fail, "Couldn't find Expect for ", msg);
390 mtc.stop;
391}
392
393private function f_create_expect(hexstring imsi, DIAMETER_ConnHdlr hdlr)
394runs on DIAMETER_Emulation_CT {
395 var integer i;
396
397 /* Check an entry like this is not already presnt */
398 for (i := 0; i < sizeof(DiameterExpectTable); i := i+1) {
399 if (imsi == DiameterExpectTable[i].imsi) {
400 setverdict(fail, "IMSI already present", imsi);
401 mtc.stop;
402 }
403 }
404 for (i := 0; i < sizeof(DiameterExpectTable); i := i+1) {
405 if (not ispresent(DiameterExpectTable[i].imsi)) {
406 DiameterExpectTable[i].imsi := imsi;
407 DiameterExpectTable[i].vc_conn := hdlr;
408 log("Created Expect[", i, "] for ", imsi, " to be handled at ", hdlr);
409 return;
410 }
411 }
412 testcase.stop("No space left in DiameterExpectTable")
413}
414
415/* client/conn_hdlr side function to use procedure port to create expect in emulation */
416function f_diameter_expect(hexstring imsi) runs on DIAMETER_ConnHdlr {
417 DIAMETER_PROC.call(DIAMETEREM_register:{imsi, self}) {
418 [] DIAMETER_PROC.getreply(DIAMETEREM_register:{?,?}) {};
419 }
420}
421
422private function f_expect_table_init()
423runs on DIAMETER_Emulation_CT {
424 var integer i;
425 for (i := 0; i < sizeof(DiameterExpectTable); i := i + 1) {
426 DiameterExpectTable[i].imsi := omit;
427 }
428}
429
430function DummyUnitdataCallback(PDU_DIAMETER msg)
431runs on DIAMETER_Emulation_CT return template PDU_DIAMETER {
432 log("Ignoring DIAMETER ", msg);
433 return omit;
434}
435
436
437}