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