blob: 18404f14be6f7636222d9695a6971debdf9e6a1b [file] [log] [blame]
Pau Espin Pedrolac8a0542024-04-19 17:30:57 +02001/* Component implementing a IMS server towards Asterisk's IMS UE
2 * (C) 2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
3 * Author: Pau Espin Pedrol <pespin@sysmocom.de>
4 * All rights reserved.
5 *
6 * Released under the terms of GNU General Public License, Version 2 or
7 * (at your option) any later version.
8 *
9 * SPDX-License-Identifier: GPL-2.0-or-later
10 */
11module IMS_ConnectionHandler {
12
13import from TCCOpenSecurity_Functions all;
14import from General_Types all;
15import from Osmocom_Types all;
16import from Native_Functions all;
17import from Misc_Helpers all;
18
19import from SDP_Types all;
20import from SDP_Templates all;
21
22import from SIP_Emulation all;
23import from SIPmsg_Types all;
24import from SIP_Templates all;
25
Pau Espin Pedrola2812ec2024-05-10 20:30:44 +020026const char c_sip_server_name := "osmo-ttcn3-hacks/0.23";
27
28
Pau Espin Pedrolac8a0542024-04-19 17:30:57 +020029type port IMSCoord_PT message
30{
31 inout charstring;
32} with { extension "internal" };
33
34const charstring IMS_COORD_CMD_REGISTERED := "COORD_CMD_REGISTERED";
35
36type component IMS_ConnHdlr extends SIP_ConnHdlr {
37 var charstring g_name;
38 var IMS_ConnHdlrPars g_pars;
39 timer g_Tguard;
40 var PDU_SIP_Request g_rx_sip_req;
41 var PDU_SIP_Response g_rx_sip_resp;
42
43 port IMSCoord_PT COORD;
44}
45type record of IMS_ConnHdlr IMS_ConnHdlrList;
46
47type record IMS_ConnHdlrPars {
48 float t_guard,
Pau Espin Pedrola2812ec2024-05-10 20:30:44 +020049 charstring realm,
50 charstring local_sip_host,
51 uint16_t local_sip_port,
52 charstring remote_sip_host optional,
53 uint16_t remote_sip_port optional,
Pau Espin Pedrolac8a0542024-04-19 17:30:57 +020054 charstring user,
55 charstring display_name,
56 charstring password,
Pau Espin Pedrola2812ec2024-05-10 20:30:44 +020057 integer ipsec_local_spi_c,
58 integer ipsec_local_spi_s,
59 integer ipsec_remote_spi_c optional,
60 integer ipsec_remote_spi_s optional,
Pau Espin Pedrolac8a0542024-04-19 17:30:57 +020061 SipUrl registrar_sip_req_uri,
62 SipAddr registrar_sip_record,
63 CallidString registrar_sip_call_id,
64 integer registrar_sip_seq_nr,
65 Via local_via,
66 SipUrl local_sip_url_ext,
67 SipAddr local_sip_record,
68 Contact local_contact,
69 IMS_CallPars cp optional
70}
71type record of IMS_ConnHdlrPars IMS_ConnHdlrParsList;
72
73type record IMS_CallParsMT {
74 /* Whether to wait for COORD.receive(COORD_CMD_PICKUP) before accepting the call. */
75 boolean wait_coord_cmd_pickup,
76 /* Whether to expect CANCEL instead of ACK as answer to our OK */
77 boolean exp_cancel
78}
Pau Espin Pedrola2812ec2024-05-10 20:30:44 +020079template (value) IMS_CallParsMT t_IMS_CallParsMT := {
80 wait_coord_cmd_pickup := false,
81 exp_cancel := false
82}
Pau Espin Pedrolac8a0542024-04-19 17:30:57 +020083
84type record IMS_CallPars {
85 SipAddr calling optional,
86 SipAddr called optional,
87
88 SipAddr from_addr optional,
89 SipAddr to_addr optional,
90
91 CallidString sip_call_id,
92 integer sip_seq_nr,
93 charstring sip_body optional,
94
95 charstring local_rtp_addr,
96 uint16_t local_rtp_port,
97
98 SDP_Message peer_sdp optional,
99 IMS_CallParsMT mt
100}
101
Pau Espin Pedrola2812ec2024-05-10 20:30:44 +0200102template (value) IMS_CallPars t_IMS_CallPars(charstring local_rtp_addr,
103 uint16_t local_rtp_port := 0,
104 template (omit) SipAddr calling := omit,
105 template (omit) SipAddr called := omit) := {
106 calling := calling,
107 called := called,
108 from_addr := omit,
109 to_addr := omit,
110 sip_call_id := hex2str(f_rnd_hexstring(15)),
111 sip_seq_nr := f_sip_rand_seq_nr(),
112 sip_body := omit,
113 local_rtp_addr := local_rtp_addr,
114 local_rtp_port := local_rtp_port,
115 peer_sdp := omit,
116 mt := t_IMS_CallParsMT
117}
118
119template (value) IMS_ConnHdlrPars t_IMS_Pars(charstring local_sip_host,
120 uint16_t local_sip_port,
121 charstring user,
122 charstring display_name := "Anonymous",
123 charstring password := "secret",
124 template (omit) IMS_CallPars cp := omit) := {
125 t_guard := 30.0,
126 realm := local_sip_host,
127 local_sip_host := local_sip_host,
128 local_sip_port := local_sip_port,
129 remote_sip_host := omit,
130 remote_sip_port := omit,
131 user := user,
132 display_name := f_sip_str_quote(display_name),
133 password := password,
134 ipsec_local_spi_c := 4142,
135 ipsec_local_spi_s := 4143,
136 ipsec_remote_spi_c := omit,
137 ipsec_remote_spi_s := omit,
138 registrar_sip_req_uri := valueof(ts_SipUrlHost(local_sip_host)),
139 registrar_sip_record := ts_SipAddr(ts_HostPort(local_sip_host),
140 ts_UserInfo(user),
141 f_sip_str_quote(display_name)),
142 registrar_sip_call_id := hex2str(f_rnd_hexstring(15)) & "@" & local_sip_host,
143 registrar_sip_seq_nr := f_sip_rand_seq_nr(),
144 local_via := ts_Via_from(ts_HostPort(local_sip_host, local_sip_port)),
145 local_sip_url_ext := ts_SipUrl(ts_HostPort(local_sip_host, local_sip_port),
146 ts_UserInfo(user)),
147 local_sip_record := ts_SipAddr(ts_HostPort(local_sip_host),
148 ts_UserInfo(user)),
149 local_contact := valueof(ts_Contact({
150 ts_ContactAddress(
151 ts_Addr_Union_SipUrl(ts_SipUrl(ts_HostPort(
152 local_sip_host,
153 local_sip_port),
154 ts_UserInfo(user))),
155 omit)
156 })),
157 cp := cp
158}
159
160private altstep as_Tguard() runs on IMS_ConnHdlr {
161 [] g_Tguard.timeout {
162 setverdict(fail, "Tguard timeout");
163 mtc.stop;
164 }
165}
166
167type function ims_void_fn(charstring id) runs on IMS_ConnHdlr;
168function f_ims_handler_init(ims_void_fn fn, charstring id, IMS_ConnHdlrPars pars)
169runs on IMS_ConnHdlr {
170 g_name := id;
171 g_pars := pars;
172 g_Tguard.start(pars.t_guard);
173 activate(as_Tguard());
174
175 /* call the user-supied test case function */
176 fn.apply(id);
177}
178
179private altstep as_SIP_fail_req(charstring exp_msg_str := "") runs on IMS_ConnHdlr
180{
181 var PDU_SIP_Request sip_req;
182 [] SIP.receive(PDU_SIP_Request:?) -> value sip_req {
183 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
184 log2str(g_name & ": Received unexpected SIP Req message := ", sip_req, "\nvs exp := ", exp_msg_str));
185 }
186}
187
188private altstep as_SIP_fail_resp(charstring exp_msg_str := "") runs on IMS_ConnHdlr
189{
190 var PDU_SIP_Response sip_resp;
191 [] SIP.receive(PDU_SIP_Response:?) -> value sip_resp {
192 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
193 log2str(g_name & ": Received unexpected SIP Resp message := ", sip_resp, "\nvs exp := ", exp_msg_str));
194 }
195}
196
197private function f_ims_validate_register_contact(Contact rx_contact)
198{
199/* IMS contact shows up like this:
200 * Contact: <sip:8adf9f3d-9342-4060-aa4f-a909f37fd6f6@192.168.101.2:5060>;+g.3gpp.accesstype="cellular2";video;audio;+g.3gpp.smsip;+g.3gpp.nw-init-ussi;+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel";+sip.instance="<urn:gsma:imei:35589811-338445-0>"
201 */
202 /* TODO: "that the UE must include the IMS Communication Service Identifier (ICSI)
203in the contact: header to indicate IMS Multimedia Telephony." */
204 /* TODO: "The UE must include an IMEI URN in the +sip.instance header field
205parameter of the contact: header." */
206 /* TODO: "If the UE supports SMS over IP, it must include the feature tag
207“+g.3gpp.smsip” in the contact: header." */
208 /* TODO: "If the UE supports conversational audio and video service, then this must
209be indicated by adding a “video” media feature tag to the contact: header." */
210}
211
212private function f_ims_parse_security_client(Security_client security_client) runs on IMS_ConnHdlr
213{
214 var boolean found := false;
215 for (var integer i := 0; i < lengthof(security_client.sec_mechanism_list); i := i + 1) {
216 var Security_mechanism sec_mec := security_client.sec_mechanism_list[i];
217 if (sec_mec.mechanism_name != "ipsec-3gpp") {
218 log("Skipping Security Mechansim: ", sec_mec.mechanism_name);
219 continue;
220 }
221 var SemicolonParam_List sec_pars := sec_mec.mechanism_params;
222 var charstring par_val;
223 par_val := f_sip_param_get_value_present_or_fail(sec_pars, "alg");
224 if (par_val != "hmac-sha-1-96") {
225 log("Skipping Security Mechansim Algo: ", par_val);
226 continue;
227 }
228 par_val := f_sip_param_get_value_present_or_fail(sec_pars, "spi-c");
229 g_pars.ipsec_remote_spi_c := str2int(par_val);
230 par_val := f_sip_param_get_value_present_or_fail(sec_pars, "spi-s");
231 g_pars.ipsec_remote_spi_s := str2int(par_val);
232 found := true;
233 break;
234 }
235
236 if (not found) {
237 Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
238 log2str(g_name & "alg=hmac-sha-1-96 not found: ", security_client));
239 }
240
241 log("ipsec: remote_spi_c=", g_pars.ipsec_remote_spi_c, " remote_spi_s=", g_pars.ipsec_remote_spi_s,
242 "local_spi_c=", g_pars.ipsec_local_spi_c, " local_spi_s=", g_pars.ipsec_local_spi_s);
243}
244
245private function f_ims_setup_ipsec(PDU_SIP_Request req_req) runs on IMS_ConnHdlr
246{
247 var Security_client security_client := req_req.msgHeader.security_client;
248 f_ims_parse_security_client(security_client);
249}
250
251/* Peer is calling us, accept it: */
252altstep as_IMS_register(boolean exp_update_to_direct_rtp := true,
253 boolean fail_others := true) runs on IMS_ConnHdlr
254{
255 var template (present) PDU_SIP_Request exp_req :=
256 tr_SIP_REGISTER(g_pars.registrar_sip_req_uri,
257 ?,
258 tr_SipAddr(),
259 tr_SipAddr(),
260 tr_Via_from(?),
261 require := tr_Require(superset("sec-agree")),
262 security_client := tr_Security_client(superset(tr_Security_mechanism("ipsec-3gpp",
263 superset(tr_Param("alg","hmac-sha-1-96"))))),
264 supported := tr_Supported(superset("path", "sec-agree")));
265 var charstring sip_expect_str := log2str(exp_req);
266
267 [] SIP.receive(exp_req) -> value g_rx_sip_req {
268 var template (value) PDU_SIP_Response tx_resp;
269 var Via via;
270 var CallidString sip_call_id;
271 var Contact contact;
272 var template (value) SipAddr from_addr;
273 var template (value) SipAddr to_addr;
274 var template (value) CommaParam_List digestCln ;
275 var template (value) WwwAuthenticate wwwAuthenticate;
276 var template (value) Security_server security_server;
277 var template (value) Server server_name := ts_Server({c_sip_server_name});
278 var template (value) Supported supported := ts_Supported({"sec-agree"});
279 var Authorization authorization;
280 var integer sip_seq_nr;
281 var charstring tx_sdp;
282
283 sip_call_id := g_rx_sip_req.msgHeader.callId.callid;
284 via := g_rx_sip_req.msgHeader.via;
285 via.viaBody[0].viaParams := f_sip_param_set(via.viaBody[0].viaParams, "rport", "1234"); /* TODO: set remote src port of the REGISTER */
286 from_addr := ts_SipAddr_from_Addr_Union(g_rx_sip_req.msgHeader.fromField.addressField,
287 g_rx_sip_req.msgHeader.fromField.fromParams);
288 to_addr := ts_SipAddr_from_Addr_Union(g_rx_sip_req.msgHeader.toField.addressField,
289 g_rx_sip_req.msgHeader.toField.toParams);
290 sip_seq_nr := g_rx_sip_req.msgHeader.cSeq.seqNumber;
291
292 contact := g_rx_sip_req.msgHeader.contact;
293 f_ims_validate_register_contact(contact);
294
295 /* TODO: Validate "Expires" is 600000 */
296 /* TODO: validate presence of:
297 * Security-Client: ipsec-3gpp; alg=hmac-md5-96; ealg=des-ede3-cbc; spi-c=431842084; spi-s=650017092; port-c=41271; port-s=41718,ipsec-3gpp; alg=hmac-md5-96; ealg=aes-cbc; spi-c=431842084; spi-s=650017092; port-c=41271; port-s=41718,ipsec-3gpp; alg=hmac-md5-96; ealg=null; spi-c=431842084; spi-s=650017092; port-c=41271; port-s=41718,ipsec-3gpp; alg=hmac-sha-1-96; ealg=des-ede3-cbc; spi-c=431842084; spi-s=650017092; port-c=41271; port-s=41718,ipsec-3gpp; alg=hmac-sha-1-96; ealg=aes-cbc; spi-c=431842084; spi-s=650017092; port-c=41271; port-s=41718,ipsec-3gpp; alg=hmac-sha-1-96; ealg=null; spi-c=431842084; spi-s=650017092; port-c=41271; port-s=41718
298 */
299
300 /* Tx 100 Tyring */
301 tx_resp := ts_SIP_Response_Trying(sip_call_id,
302 from_addr,
303 to_addr,
304 via,
305 sip_seq_nr,
306 "REGISTER",
307 allow := omit,
308 server := server_name,
309 userAgent := omit);
310 SIP.send(tx_resp);
311
312 f_ims_setup_ipsec(g_rx_sip_req);
313
314 to_addr.params := f_sip_param_set(to_addr.params, "tag", f_sip_rand_tag());
315
316 digestCln := {
317 ts_Param("realm", f_sip_str_quote(g_pars.realm)),
318 ts_Param("qop", f_sip_str_quote("auth")),
319 ts_Param("algorithm", "AKAv1-MD5"),
320 ts_Param("nonce", f_sip_str_quote("FJh2MfZfjjeIoHmLbrzQjvbhmnzLAoAAoGsZyVRFFuU="))
321 /* "opaque not needed in IMS "*/
322 };
323 wwwAuthenticate := ts_WwwAuthenticate( { ts_Challenge_digestCln(digestCln) } )
324
325 /* Security-Server: ipsec-3gpp;q=0.1;prot=esp;mod=trans;spi-c=4096;spi-s=4097;port-c=5104;port-s=6104;alg=hmac-sha-1-96;ealg=null */
326 var template (value) SemicolonParam_List sec_params := {
327 ts_Param("q", "0.1"),
328 ts_Param("prot", "esp"),
329 ts_Param("mod", "trans"),
330 ts_Param("spi-c", int2str(g_pars.ipsec_local_spi_c)),
331 ts_Param("spi-s", int2str(g_pars.ipsec_local_spi_s)),
332 ts_Param("port-c", int2str(g_pars.local_sip_port)),
333 ts_Param("port-s", int2str(g_pars.local_sip_port)),
334 ts_Param("alg", "hmac-sha-1-96"),
335 ts_Param("ealg", "null")
336 };
337 security_server := ts_Security_server({
338 ts_Security_mechanism("ipsec-3gpp", sec_params)
339 });
340 /* Tx 401 Unauthorized
341 * TODO: with IMS params */
342 tx_resp := ts_SIP_Response_Unauthorized(sip_call_id,
343 from_addr,
344 to_addr,
345 via,
346 wwwAuthenticate,
347 sip_seq_nr,
348 "REGISTER",
349 security_server := security_server,
350 server := server_name,
351 supported := supported,
352 userAgent := omit);
353 SIP.send(tx_resp);
354
355 /* TODO: Generate expected Authoritzation based on AKAv1-MD5: */
356 /*authorization := f_sip_digest_gen_Authorization(valueof(wwwAuthenticate),
357 g_pars.user, g_pars.password,
358 "REGISTER",
359 f_sip_SipUrl_to_str(g_pars.registrar_sip_record.addr.nameAddr.addrSpec))
360 */
361 /* TODO: match Authorization from above: */
362 exp_req :=
363 tr_SIP_REGISTER(g_pars.registrar_sip_req_uri,
364 ?,
365 tr_SipAddr(),
366 tr_SipAddr(),
367 tr_Via_from(?));
368 SIP.receive(exp_req) -> value g_rx_sip_req;
369
370 sip_call_id := g_rx_sip_req.msgHeader.callId.callid;
371 via := g_rx_sip_req.msgHeader.via;
372 from_addr := ts_SipAddr_from_Addr_Union(g_rx_sip_req.msgHeader.fromField.addressField,
373 g_rx_sip_req.msgHeader.fromField.fromParams);
374 to_addr := ts_SipAddr_from_Addr_Union(g_rx_sip_req.msgHeader.toField.addressField,
375 g_rx_sip_req.msgHeader.toField.toParams);
376 to_addr.params := f_sip_param_set(to_addr.params, "tag", f_sip_rand_tag());
377 sip_seq_nr := g_rx_sip_req.msgHeader.cSeq.seqNumber;
378
379 /* TODO: Add following fields:
380 * Supported: sec-agree
381 * Security-Server: ipsec-3gpp;q=0.1;prot=esp;mod=trans;spi-c=4096;spi-s=4097;port-c=5104;port-s=6104;alg=hmac-sha-1-96;ealg=null
382 * */
383
384 tx_resp := ts_SIP_Response(sip_call_id,
385 from_addr,
386 to_addr,
387 "REGISTER", 200,
388 sip_seq_nr,
389 "OK",
390 via);
391 SIP.send(tx_resp);
392 }
393 [fail_others] as_SIP_fail_resp(sip_expect_str);
394 [fail_others] as_SIP_fail_req(sip_expect_str);
395
396}
397
Pau Espin Pedrolac8a0542024-04-19 17:30:57 +0200398}