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