/* Component implementing a IMS server towards Asterisk's IMS UE * (C) 2024 by sysmocom - s.f.m.c. GmbH * Author: Pau Espin Pedrol * All rights reserved. * * Released under the terms of GNU General Public License, Version 2 or * (at your option) any later version. * * SPDX-License-Identifier: GPL-2.0-or-later */ module IMS_ConnectionHandler { import from TCCEncoding_Functions all; import from TCCOpenSecurity_Functions all; import from General_Types all; import from Osmocom_Types all; import from Native_Functions all; import from Misc_Helpers all; /* the PIPE asp port allows us to interact with ip xfrm via stdin/stdout */ import from PIPEasp_PortType all; import from PIPEasp_Types all; import from PIPEasp_Templates all; import from SDP_Types all; import from SDP_Templates all; import from SIP_Emulation all; import from SIPmsg_Types all; import from SIP_Templates all; modulepar { charstring mp_ipsec_setup_script_path := "./IMS_ipsec_setup.sh"; } const integer c_def_expires := 600000; /* 3GPP TS 24.229 5.1.1.2.1 e) */ const charstring c_sip_server_name := "osmo-ttcn3-hacks/0.23"; type port IMSCoord_PT message { inout charstring; } with { extension "internal" }; const charstring IMS_COORD_CMD_REGISTERED := "IMS_COORD_CMD_REGISTERED"; const charstring IMS_COORD_CMD_START := "IMS_COORD_CMD_START"; const charstring IMS_COORD_CMD_CALL_ESTABLISHED := "IMS_COORD_CMD_CALL_ESTABLISHED"; const charstring IMS_COORD_CMD_HANGUP := "IMS_COORD_CMD_HANGUP"; const charstring IMS_COORD_CMD_CALL_TRYING := "IMS_COORD_CMD_CALL_TRYING"; const charstring IMS_COORD_CMD_CALL_SESSION_PROGRESS := "IMS_COORD_CMD_CALL_SESSION_PROGRESS"; type component IMS_ConnHdlr extends SIP_ConnHdlr { var charstring g_name; var IMS_ConnHdlrPars g_pars; timer g_Tguard; var PDU_SIP_Request g_rx_sip_req; var PDU_SIP_Response g_rx_sip_resp; port IMSCoord_PT COORD; port PIPEasp_PT PIPE; } type record of IMS_ConnHdlr IMS_ConnHdlrList; type record IMS_AuthVector { OCT16 rand, OCT16 autn, OCT8 res, OCT16 ck, OCT16 ik, OCT14 auts } type record IMS_ConnHdlrSubscrPars { charstring remote_sip_host optional, uint16_t remote_sip_port optional, charstring imsi, charstring imei, charstring display_name, charstring password, charstring msisdn, boolean support_video, boolean support_smsip, /* Expected User-Location-Info in P-Access-Network-Info */ charstring uli_str, IMS_AuthVector auth, charstring ipsec_auth_key, integer ipsec_local_spi_c, integer ipsec_local_spi_s, integer ipsec_remote_spi_c optional, integer ipsec_remote_spi_s optional, uint16_t ipsec_remote_port_c optional, uint16_t ipsec_remote_port_s optional, SipAddr registrar_sip_record, CallidString registrar_sip_call_id, integer registrar_sip_seq_nr, integer exp_uac_expires, integer registrar_expires, SipUrl local_sip_url_ext, SipAddr local_sip_record, Contact registered_contact optional, P_Associated_Uri p_associated_uri, IMS_CallPars cp optional } type record of IMS_ConnHdlrSubscrPars IMS_ConnHdlrSubscrParsList; type record IMS_ConnHdlrPars { float t_guard, charstring realm, charstring local_sip_host, uint16_t local_sip_port, SipUrl registrar_sip_req_uri, Via local_via, Server server_name, IMS_ConnHdlrSubscrPars subscr optional } type record of IMS_ConnHdlrPars IMS_ConnHdlrParsList; type record IMS_CallParsMO { /* Whether to COORD.send(IMS_COORD_CMD_CALL_TRYING) when receiving an INVITE from UE. */ boolean tx_coord_cmd_invite_trying } template (value) IMS_CallParsMO t_IMS_CallParsMO := { tx_coord_cmd_invite_trying := false } type record IMS_CallParsMT { /* Whether to COORD.send(IMS_COORD_CMD_CALL_SESSION_PROGRESS) when receiving an 183 Session Progress from UE. */ boolean tx_coord_cmd_session_progress } template (value) IMS_CallParsMT t_IMS_CallParsMT := { tx_coord_cmd_session_progress := false } type record IMS_CallPars { SipAddr calling optional, SipAddr called optional, From from_addr optional, To to_addr optional, CallidString sip_call_id, integer sip_seq_nr, charstring sip_body optional, charstring local_rtp_addr, uint16_t local_rtp_port, boolean support_precondition_ext, boolean require_precondition_ext, SDP_Message peer_sdp optional, IMS_CallParsMO mo, IMS_CallParsMT mt } template (value) IMS_CallPars t_IMS_CallPars(charstring local_rtp_addr, uint16_t local_rtp_port := 0, template (omit) SipAddr calling := omit, template (omit) SipAddr called := omit) := { calling := calling, called := called, from_addr := omit, to_addr := omit, sip_call_id := hex2str(f_rnd_hexstring(15)), sip_seq_nr := f_sip_rand_seq_nr(), sip_body := omit, local_rtp_addr := local_rtp_addr, local_rtp_port := local_rtp_port, support_precondition_ext := true, require_precondition_ext := false, peer_sdp := omit, mo := t_IMS_CallParsMO, mt := t_IMS_CallParsMT } template (value) IMS_ConnHdlrSubscrPars t_IMS_SubscrPars(charstring local_sip_host, uint16_t local_sip_port, charstring domain, charstring imsi, charstring imei, charstring msisdn := "90828", charstring display_name := "Anonymous", charstring password := "secret", template (omit) IMS_CallPars cp := omit) := { remote_sip_host := omit, remote_sip_port := omit, imsi := imsi, imei := imei, display_name := f_sip_str_quote(display_name), password := password, msisdn := msisdn, support_video := false, support_smsip := false, uli_str := "2380100010000101", auth := { /* The Nonce field is the Base64 encoded version of the RAND value and concatenated with the AUTN: */ rand := 'd5d5de2bce418d7865ed7fa6956618a2'O, autn := 'd42e61db5f15800067393a5b7691a227'O, res := '6f2556bbe4366ab1'O, ck := '0b389d08c833991734936bec55cac800'O, ik := '17141862125bd30c81c4224391a0909a'O, /* NOTE: AUTS value randomly crafted. It's fine since it's just forwarded * AMI -> asterisk -> IMS and we blindly match and accept it. */ auts := 'd42e61db5f15800067393a5b7691'O }, ipsec_auth_key := "0x17141862125bd30c81c4224391a0909a00000000", ipsec_local_spi_c := 4142, ipsec_local_spi_s := 4143, ipsec_remote_spi_c := omit, ipsec_remote_spi_s := omit, ipsec_remote_port_c := omit, ipsec_remote_port_s := omit, registrar_sip_record := ts_SipAddr(ts_HostPort(domain), ts_UserInfo(imsi), f_sip_str_quote(display_name)), registrar_sip_call_id := hex2str(f_rnd_hexstring(15)) & "@" & domain, registrar_sip_seq_nr := f_sip_rand_seq_nr(), exp_uac_expires := c_def_expires, registrar_expires := c_def_expires, local_sip_url_ext := ts_SipUrl(ts_HostPort(domain, local_sip_port), ts_UserInfo(imsi)), local_sip_record := ts_SipAddr(ts_HostPort(domain), ts_UserInfo(imsi)), registered_contact := omit, p_associated_uri := ts_P_Associated_Uri({}), cp := cp } template (value) IMS_ConnHdlrPars t_IMS_Pars(charstring local_sip_host, uint16_t local_sip_port, charstring domain, charstring imsi, charstring imei, template (omit) IMS_CallPars cp := omit) := { t_guard := 60.0, realm := domain, local_sip_host := local_sip_host, local_sip_port := local_sip_port, registrar_sip_req_uri := valueof(ts_SipUrlHost(domain)), local_via := ts_Via_from(ts_HostPort(local_sip_host, local_sip_port)), server_name := valueof(ts_Server({c_sip_server_name})), subscr := t_IMS_SubscrPars(local_sip_host, local_sip_port, domain := domain, imsi := imsi, imei := imei, cp := cp) } private altstep as_Tguard() runs on IMS_ConnHdlr { [] g_Tguard.timeout { setverdict(fail, "Tguard timeout"); mtc.stop; } } type function ims_void_fn(charstring id) runs on IMS_ConnHdlr; function f_ims_handler_init(ims_void_fn fn, charstring id, IMS_ConnHdlrPars pars) runs on IMS_ConnHdlr { g_name := id; g_pars := pars; g_Tguard.start(pars.t_guard); activate(as_Tguard()); /* call the user-supied test case function */ fn.apply(id); } altstep as_SIP_fail_req(charstring exp_msg_str := "") runs on IMS_ConnHdlr { var PDU_SIP_Request sip_req; [] SIP.receive(PDU_SIP_Request:?) -> value sip_req { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received unexpected SIP Req message := ", sip_req, "\nvs exp := ", exp_msg_str)); } } altstep as_SIP_fail_resp(charstring exp_msg_str := "") runs on IMS_ConnHdlr { var PDU_SIP_Response sip_resp; [] SIP.receive(PDU_SIP_Response:?) -> value sip_resp { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received unexpected SIP Resp message := ", sip_resp, "\nvs exp := ", exp_msg_str)); } } altstep as_SIP_expect_resp(template (present) PDU_SIP_Response sip_expect, boolean fail_others := true) runs on IMS_ConnHdlr { var charstring sip_expect_str := log2str(sip_expect); [] SIP.receive(sip_expect) -> value g_rx_sip_resp; [fail_others] as_SIP_fail_resp(sip_expect_str); [fail_others] as_SIP_fail_req(sip_expect_str); } altstep as_SIP_ignore_resp(template PDU_SIP_Response sip_expect := ?) runs on IMS_ConnHdlr { [] SIP.receive(sip_expect) -> value g_rx_sip_resp { log("Ignoring ", g_rx_sip_resp); repeat; } } private function f_nonce_from_rand_autn(octetstring rand, octetstring autn) return charstring { var octetstring concat := rand & autn; var charstring nonce := enc_MIME_Base64(concat); log("rand=", rand, " & autn=",autn, " => nonce=", nonce); return nonce; } /* HTTP Digest Authentication Using AKA (AKAv1-MD5): RFC 3310 */ function f_tr_Authorization_AKAv1MD5(WwwAuthenticate www_authenticate, charstring username, charstring uri) return template (present) Authorization { var CommaParam_List digestCln; var template (present) Authorization authorization; var template (present) Credentials cred; var template (omit) GenericParam rx_param; digestCln := www_authenticate.challenge[0].digestCln; var charstring algorithm := f_sip_param_get_value_present_or_fail(digestCln, "algorithm"); var charstring realm := f_sip_param_get_value_present_or_fail(digestCln, "realm"); var charstring nonce := f_sip_param_get_value_present_or_fail(digestCln, "nonce"); var template (present) CommaParam_List digestResponse := superset( tr_Param("username", f_sip_str_quote(username)), tr_Param("realm", f_sip_str_quote(realm)), tr_Param("nonce", f_sip_str_quote(nonce)), tr_Param("uri", f_sip_str_quote(uri)), tr_Param("response", ?), tr_Param("algorithm", algorithm), tr_Param("qop", "auth"), tr_Param("cnonce", ?), tr_Param("nc", ?) ); cred := tr_Credentials_DigestResponse(digestResponse); authorization := tr_Authorization(cred); return authorization; } private function f_ims_validate_Authorization_AKAv1MD5_Response(Authorization authorization, charstring method) runs on IMS_ConnHdlr { f_sip_digest_validate_Authorization_AKAv1MD5(authorization, method, g_pars.subscr.auth.res); } /* GSMA IR-92 2.2.1 SIP Registration Procedure */ private function f_ims_validate_register_contact(Contact rx_contact) runs on IMS_ConnHdlr { /* IMS contact shows up like this: * Contact: ;+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="" */ if (ischosen(rx_contact.contactBody.wildcard)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received unexpected Contact wildcard := ", rx_contact)); } var ContactAddress_List caddrs := rx_contact.contactBody.contactAddresses; if (lengthof(caddrs) == 0) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received unexpected empty Contact Address list")); } for (var integer i := 0; i < lengthof(caddrs); i := i + 1) { var ContactAddress caddr := caddrs[i]; var template (omit) GenericParam param_tpl; var boolean rx_supported; if (not ispresent(caddr.contactParams)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received unexpected empty Contact attributes (omit)")); } f_sip_param_match_value_or_fail(caddr.contactParams, "+g.3gpp.icsi-ref", "\"urn\\%3Aurn-7\\%3A3gpp-service.ims.icsi.mmtel\""); f_sip_param_match_value_or_fail(caddr.contactParams, "+sip.instance", "\"\""); f_sip_param_match_value_or_fail(caddr.contactParams, "audio", omit); /* Check video support */ param_tpl := f_sip_param_find(caddr.contactParams, "video"); rx_supported := not istemplatekind(param_tpl, "omit"); if (rx_supported) { /* This attribute never has a value: */ if (not istemplatekind(param_tpl.paramValue, "omit")) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received Contact with 'video' attribute containing a value := ", rx_contact)); } if (not g_pars.subscr.support_video) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received Contact with unexpected 'video' attribute := ", rx_contact)); } } else if (g_pars.subscr.support_video) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received Contact with missing 'video' attribute := ", rx_contact)); } /* Check smsip support */ param_tpl := f_sip_param_find(caddr.contactParams, "+g.3gpp.smsip"); rx_supported := not istemplatekind(param_tpl, "omit"); if (rx_supported) { /* This attribute never has a value: */ if (not istemplatekind(param_tpl.paramValue, "omit")) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received Contact with '+g.3gpp.smsip' attribute containing a value := ", rx_contact)); } if (not g_pars.subscr.support_smsip) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received Contact with unexpected '+g.3gpp.smsip' attribute := ", rx_contact)); } } else if (g_pars.subscr.support_smsip) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received Contact with missing '+g.3gpp.smsip' attribute := ", rx_contact)); } } } /* Validate P-Access-Network-Info: RFC7315 6.4 */ private function f_ims_validate_register_P_Access_Network_info(PDU_SIP_Request req, boolean exp_present := true) runs on IMS_ConnHdlr { if (not exp_present) { if (ispresent(g_rx_sip_req.msgHeader.p_access_network_info)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received unexpected [rfc7315 6.4] P-Access-Info := ", g_rx_sip_req.msgHeader.p_access_network_info)); } return; } /* exp_present: */ var template (present) P_Access_Network_Info expl_tmpl := tr_P_Access_Network_Info({ tr_Access_net_spec_EUTRAN(g_pars.subscr.uli_str) }); if (not ispresent(g_rx_sip_req.msgHeader.p_access_network_info)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received no P-Access-Info vs exp := ", expl_tmpl)); } if (not match(g_rx_sip_req.msgHeader.p_access_network_info, expl_tmpl)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received unexpected P-Access-Info := ", g_rx_sip_req.msgHeader.p_access_network_info, "\nvs exp := ", expl_tmpl)); } } /* GSMA IR.92 2.2.4 Call Establishment and Termination */ private function f_ims_validate_INVITE_Contact(Contact rx_contact) runs on IMS_ConnHdlr { /* INVITE Contact header shows up like this: * Contact: ;+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel";audio;+g.3gpp.mid-call;+g.3gpp.srvcc-alerting;+g.3gpp.ps2cs-srvcc-orig-pre-alerting */ if (ischosen(rx_contact.contactBody.wildcard)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received unexpected Contact wildcard := ", rx_contact)); } var ContactAddress_List caddrs := rx_contact.contactBody.contactAddresses; if (lengthof(caddrs) == 0) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received unexpected empty Contact Address list")); } for (var integer i := 0; i < lengthof(caddrs); i := i + 1) { var ContactAddress caddr := caddrs[i]; if (not ispresent(caddr.contactParams)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received unexpected empty Contact attributes (omit)")); } /* GSMA FCM.01 3.2.3.3: "The INVITE request contains Within the Contact header [...] the IMS * Communication Service Identifier's (ICSI) for IMS Multimedia Telephony" */ f_sip_param_match_value_or_fail(caddr.contactParams, "+g.3gpp.icsi-ref", "\"urn\\%3Aurn-7\\%3A3gpp-service.ims.icsi.mmtel\""); /* "The UE must include the audio media feature tag, as defined in IETF RFC 3840, * in the Contact header field of the SIP INVITE request, and in the Contact header * field of the SIP response to the SIP INVITE request" */ f_sip_param_match_value_or_fail(caddr.contactParams, "audio", omit); } } /* Validate P-Preferred-Service: GSMA FCM.01 3.2.3.3 */ private function f_ims_validate_invite_P_Preferred_Service(PDU_SIP_Request req, boolean exp_present := true) runs on IMS_ConnHdlr { if (not exp_present) { if (ispresent(g_rx_sip_req.msgHeader.p_preferred_service)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received unexpected [rfc6050] P-Preferred-Service := ", g_rx_sip_req.msgHeader.p_preferred_service)); } return; } /* exp_present: */ var template (present) P_Preferred_Service expl_tmpl := tr_P_Preferred_Service({ "urn:urn-7:3gpp-service.ims.icsi.mmtel" }); if (not ispresent(g_rx_sip_req.msgHeader.p_preferred_service)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received no P-Access-Info vs exp := ", expl_tmpl)); } if (not match(g_rx_sip_req.msgHeader.p_preferred_service, expl_tmpl)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Received unexpected P-Preferred-Service := ", g_rx_sip_req.msgHeader.p_preferred_service, "\nvs exp := ", expl_tmpl)); } } /* GSMA IR.92 2.2.4 Call Establishment and Termination */ private function f_ims_validate_INVITE_SDP(SDP_Message sdp) runs on IMS_ConnHdlr { /* "The UE must indicate in the SDP of the initial INVITE that the audio media * is send-receive i.e. either by including the direction attribute "a=sendrecv" * or by omitting the direction attributes. */ if (ispresent(sdp.attributes)) { if (match (sdp.attributes, superset(tr_SDP_recvonly)) or match (sdp.attributes, superset(tr_SDP_sendonly))) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Unexpected SDP direction attribute != sendrecv: ", sdp.attributes)); } } } private function f_ims_parse_security_client(Security_client security_client) runs on IMS_ConnHdlr { var boolean found := false; for (var integer i := 0; i < lengthof(security_client.sec_mechanism_list); i := i + 1) { var Security_mechanism sec_mec := security_client.sec_mechanism_list[i]; if (sec_mec.mechanism_name != "ipsec-3gpp") { log("Skipping Security Mechansim: ", sec_mec.mechanism_name); continue; } var SemicolonParam_List sec_pars := sec_mec.mechanism_params; var charstring par_val; par_val := f_sip_param_get_value_present_or_fail(sec_pars, "alg"); if (par_val != "hmac-sha-1-96") { log("Skipping Security Mechansim Algo: ", par_val); continue; } par_val := f_sip_param_get_value_present_or_fail(sec_pars, "spi-c"); g_pars.subscr.ipsec_remote_spi_c := str2int(par_val); par_val := f_sip_param_get_value_present_or_fail(sec_pars, "spi-s"); g_pars.subscr.ipsec_remote_spi_s := str2int(par_val); par_val := f_sip_param_get_value_present_or_fail(sec_pars, "port-c"); g_pars.subscr.ipsec_remote_port_c := str2int(par_val); par_val := f_sip_param_get_value_present_or_fail(sec_pars, "port-s"); g_pars.subscr.ipsec_remote_port_s := str2int(par_val); found := true; break; } if (not found) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & "alg=hmac-sha-1-96 not found: ", security_client)); } log("ipsec: remote_spi_c=", g_pars.subscr.ipsec_remote_spi_c, " remote_spi_s=", g_pars.subscr.ipsec_remote_spi_s, "local_spi_c=", g_pars.subscr.ipsec_local_spi_c, " local_spi_s=", g_pars.subscr.ipsec_local_spi_s); } private function f_ims_parse_register_contact(Contact contact) runs on IMS_ConnHdlr { var HostPort hp := valueof(contact.contactBody.contactAddresses[0].addressField.nameAddr.addrSpec.hostPort); g_pars.subscr.remote_sip_host := hp.host; if (ispresent(hp.portField)) { g_pars.subscr.remote_sip_port := hp.portField; } else { g_pars.subscr.remote_sip_port := 5060; } } private function f_IMS_exec_sync(charstring cmdline, template (present) integer rc := 0) runs on IMS_ConnHdlr return ASP_PResult { var ASP_PResult res; map(self:PIPE, system:PIPE); res := f_PIPEasp_exec_sync_PResult(PIPE, cmdline, tr_PResult(?, ?, rc)); unmap(self:PIPE, system:PIPE); return res; } private function f_ims_setup_ipsec() runs on IMS_ConnHdlr { var ASP_PResult res; var charstring cmd := mp_ipsec_setup_script_path & " " & g_pars.local_sip_host & " " & int2str(g_pars.local_sip_port) & " " & int2str(g_pars.subscr.ipsec_local_spi_c) & " " & int2str(g_pars.local_sip_port) & " " & int2str(g_pars.subscr.ipsec_local_spi_s) & " " & g_pars.subscr.remote_sip_host & " " & int2str(g_pars.subscr.ipsec_remote_port_c) & " " & int2str(g_pars.subscr.ipsec_remote_spi_c) & " " & int2str(g_pars.subscr.ipsec_remote_port_s) & " " & int2str(g_pars.subscr.ipsec_remote_spi_s) & " " & g_pars.subscr.ipsec_auth_key; res := f_IMS_exec_sync(cmd); /* Debug applied rules: */ /* res := f_IMS_exec_sync("ip xfrm state"); log("ip-xfrm-state Result-Stdout: " & res.stdout); res := f_IMS_exec_sync("ip xfrm policy"); log("ip-xfrm-policy Result-Stdout: " & res.stdout); */ } private function f_tr_Via_response(Via via_req) return template (present) Via { template (present) SemicolonParam_List via_resp_params := ?; /*via_resp_params := { { id := "rport", paramValue := int2str(g_pars.subscr.remote_sip_port.subscr.remote_sip_port) }, { id := "received", paramValue := g_pars.subscr.remote_sip_host } }; */ return tr_Via_from(via_req.viaBody[0].sentBy, via_req.viaBody[0].sentProtocol.transport, via_resp_params); } private function f_tr_From(template (value) SipAddr from_req) return template (present) SipAddr { return tr_SipAddr_from_val(from_req); } private altstep as_SIP_expect_req(template (present) PDU_SIP_Request sip_expect, boolean fail_others := true) runs on IMS_ConnHdlr { var charstring sip_expect_str := log2str(sip_expect); [] SIP.receive(sip_expect) -> value g_rx_sip_req; [fail_others] as_SIP_fail_req(sip_expect_str); [fail_others] as_SIP_fail_resp(sip_expect_str); } type enumerated IMS_gen_sdp_state { IMS_GEN_SDP_None, IMS_GEN_SDP_MO_INVITE, IMS_GEN_SDP_MO_UPDATE, IMS_GEN_SDP_MT_Session_Progress, IMS_GEN_SDP_MT_UPDATE_200OK } private function f_gen_sdp(IMS_gen_sdp_state st := IMS_GEN_SDP_None) runs on IMS_ConnHdlr return charstring { var charstring sdp := "v=0\r\n" & "o=0502 2390 1824 IN IP4 " & g_pars.subscr.cp.local_rtp_addr & "\r\n" & "s=Talk\r\n" & "c=IN IP4 " & g_pars.subscr.cp.local_rtp_addr & "\r\n" & "t=0 0\r\n" & "a=rtcp-xr:rcvr-rtt=all:10000 stat-summary=loss,dup,jitt,TTL voip-metrics\r\n" & "a=record:off\r\n" & "m=audio " & int2str(g_pars.subscr.cp.local_rtp_port) & " RTP/AVP 8 96 97 98 0 18 99 100 101\r\n" & "a=rtpmap:8 PCMA/8000\r\n" & "a=rtpmap:96 opus/48000/2\r\n" & "a=fmtp:96 useinbandfec=1\r\n" & "a=rtpmap:97 speex/16000\r\n" & "a=fmtp:97 vbr=on\r\n" & "a=rtpmap:98 speex/8000\r\n" & "a=fmtp:98 vbr=on\r\n" & "a=fmtp:18 annexb=yes\r\n" & "a=rtpmap:99 telephone-event/48000\r\n" & "a=rtpmap:100 telephone-event/16000\r\n" & "a=rtpmap:101 telephone-event/8000\r\n" & "a=rtcp:" & int2str(g_pars.subscr.cp.local_rtp_port + 1) & "\r\n" & "a=rtcp-fb:* trr-int 1000\r\n" & "a=rtcp-fb:* ccm tmmbr\r\n"; select (st) { case (IMS_GEN_SDP_None) { } case (IMS_GEN_SDP_MO_INVITE) { sdp := sdp & "a=curr:qos local none\r\n" & "a=curr:qos remote none\r\n" & "a=des:qos mandatory local sendrecv\r\n" & "a=des:qos optional remote sendrecv\r\n"; } case (IMS_GEN_SDP_MO_UPDATE) { sdp := sdp & "a=curr:qos local sendrecv\r\n" & "a=curr:qos remote none\r\n" & "a=des:qos mandatory local sendrecv\r\n" & "a=des:qos mandatory remote sendrecv\r\n"; } case (IMS_GEN_SDP_MT_Session_Progress) { sdp := sdp & "a=curr:qos local none\r\n" & "a=curr:qos remote none\r\n" & "a=des:qos mandatory local sendrecv\r\n" & "a=des:qos mandatory remote sendrecv\r\n" & "a=conf:qos remote sendrecv\r\n"; } case (IMS_GEN_SDP_MT_UPDATE_200OK) { sdp := sdp & "a=curr:qos local sendrecv\r\n" & "a=curr:qos remote sendrecv\r\n" & "a=des:qos mandatory local sendrecv\r\n" & "a=des:qos mandatory remote sendrecv\r\n"; } } return sdp; } private function f_gen_Security_server() runs on IMS_ConnHdlr return Security_server { var template (value) Security_server security_server; /* 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 */ var template (value) SemicolonParam_List sec_params := { ts_Param("q", "0.1"), ts_Param("prot", "esp"), ts_Param("mod", "trans"), ts_Param("spi-c", int2str(g_pars.subscr.ipsec_local_spi_c)), ts_Param("spi-s", int2str(g_pars.subscr.ipsec_local_spi_s)), ts_Param("port-c", int2str(g_pars.local_sip_port)), ts_Param("port-s", int2str(g_pars.local_sip_port)), ts_Param("alg", "hmac-sha-1-96"), ts_Param("ealg", "null") }; security_server := ts_Security_server({ ts_Security_mechanism("ipsec-3gpp", sec_params) }); return valueof(security_server); } private function f_gen_WwwAuthenticate() runs on IMS_ConnHdlr return WwwAuthenticate { var template (value) WwwAuthenticate wwwAuthenticate; var template (value) CommaParam_List digestCln; digestCln := { ts_Param("realm", f_sip_str_quote(g_pars.realm)), ts_Param("qop", f_sip_str_quote("auth")), ts_Param("algorithm", "AKAv1-MD5"), ts_Param("nonce", f_sip_str_quote(f_nonce_from_rand_autn(g_pars.subscr.auth.rand, g_pars.subscr.auth.autn))) /* "opaque not needed in IMS "*/ }; wwwAuthenticate := ts_WwwAuthenticate( { ts_Challenge_digestCln(digestCln) } ); return valueof(wwwAuthenticate); } type enumerated IMS_register_early_return { IMS_REG_EARLY_RET_BEFORE_None, IMS_REG_EARLY_RET_BEFORE_Initial_100Trying, IMS_REG_EARLY_RET_BEFORE_Initial_401Unauthorized, IMS_REG_EARLY_RET_BEFORE_Resync_401Unauthorized, IMS_REG_EARLY_RET_BEFORE_Protected_100Trying, IMS_REG_EARLY_RET_BEFORE_Protected_200OK } /* Peer is issuing 1st register, accept it: */ altstep as_IMS_register(boolean exp_auth_resync := false, IMS_register_early_return early_ret := IMS_REG_EARLY_RET_BEFORE_None, boolean fail_others := true) runs on IMS_ConnHdlr { var template (present) PDU_SIP_Request exp_req := tr_SIP_REGISTER(g_pars.registrar_sip_req_uri, ?, tr_From(), tr_To(), tr_Via_from(?), expires := tr_Expires(int2str(g_pars.subscr.exp_uac_expires)), require := tr_Require(superset("sec-agree")), security_client := tr_Security_client(superset(tr_Security_mechanism("ipsec-3gpp", superset(tr_Param("alg","hmac-sha-1-96"))))), supported := tr_Supported(superset("path", "sec-agree"))); var charstring sip_expect_str := log2str(exp_req); [] SIP.receive(exp_req) -> value g_rx_sip_req { var template (value) PDU_SIP_Response tx_resp; var Via via; var CallidString sip_call_id; var template (value) From from_addr; var template (value) To to_addr; var WwwAuthenticate wwwAuthenticate; var Security_server security_server; var template (value) Require require := ts_Require({"sec-agree"}); var template (value) Supported supported := ts_Supported({"sec-agree"}); var integer sip_seq_nr; if (early_ret == IMS_REG_EARLY_RET_BEFORE_Initial_100Trying) { return; /* Done */ } sip_call_id := g_rx_sip_req.msgHeader.callId.callid; via := g_rx_sip_req.msgHeader.via; via.viaBody[0].viaParams := f_sip_param_set(via.viaBody[0].viaParams, "rport", "1234"); /* TODO: set remote src port of the REGISTER */ from_addr := g_rx_sip_req.msgHeader.fromField; to_addr := g_rx_sip_req.msgHeader.toField; sip_seq_nr := g_rx_sip_req.msgHeader.cSeq.seqNumber; /* Tx 100 Tyring */ tx_resp := ts_SIP_Response_Trying(sip_call_id, from_addr, to_addr, via, sip_seq_nr, "REGISTER", allow := omit, server := g_pars.server_name, userAgent := omit); SIP.send(tx_resp); /* Validate P-Access-Network-Info: rfc7315 6.4: * "3GPP will use the P-Access-Network-Info header field to * carry relatively sensitive information like the cell ID. Therefore, * the information MUST NOT be sent outside of the 3GPP domain."" * [...] "the sensitive information carried in the * P-Access-Network-Info header field MUST NOT be sent in any initial * unauthenticated and unprotected requests (e.g., REGISTER)." */ f_ims_validate_register_P_Access_Network_info(g_rx_sip_req, exp_present := false); f_ims_validate_register_contact(g_rx_sip_req.msgHeader.contact); f_ims_parse_register_contact(g_rx_sip_req.msgHeader.contact); f_ims_parse_security_client(g_rx_sip_req.msgHeader.security_client); if (early_ret == IMS_REG_EARLY_RET_BEFORE_Initial_401Unauthorized) { return; /* Done */ } if (not exp_auth_resync) { /* Delay ipsec setup in ip xfrm, since there will be another * 1st REGISTER with potentially new ports coming in later. */ f_ims_setup_ipsec(); } to_addr.toParams := f_sip_param_set(to_addr.toParams, "tag", f_sip_rand_tag()); wwwAuthenticate := f_gen_WwwAuthenticate(); security_server := f_gen_Security_server(); /* Tx 401 Unauthorized */ tx_resp := ts_SIP_Response_Unauthorized(sip_call_id, from_addr, to_addr, via, wwwAuthenticate, sip_seq_nr, "REGISTER", p_associated_uri := g_pars.subscr.p_associated_uri, security_server := security_server, server := g_pars.server_name, supported := supported, userAgent := omit); SIP.send(tx_resp); if (exp_auth_resync) { /* Now we should receive a new non-protected REGISTER * with Authoritzation containing auts in base64: */ var template (present) Authorization authorization := f_tr_Authorization_AKAv1MD5(wwwAuthenticate, g_pars.subscr.imsi & "@" & g_pars.realm, f_sip_SipUrl_to_str(g_pars.registrar_sip_req_uri)); exp_req := tr_SIP_REGISTER(g_pars.registrar_sip_req_uri, ?, tr_From(), tr_To(), tr_Via_from(f_tr_HostPort(via.viaBody[0].sentBy.host, via.viaBody[0].sentBy.portField)), authorization := authorization); SIP.receive(exp_req) -> value g_rx_sip_req; if (early_ret == IMS_REG_EARLY_RET_BEFORE_Resync_401Unauthorized) { return; /* Done */ } via := g_rx_sip_req.msgHeader.via; from_addr := g_rx_sip_req.msgHeader.fromField; to_addr := g_rx_sip_req.msgHeader.toField; sip_seq_nr := g_rx_sip_req.msgHeader.cSeq.seqNumber; /* Tx 100 Tyring */ tx_resp := ts_SIP_Response_Trying(sip_call_id, from_addr, to_addr, via, sip_seq_nr, "REGISTER", allow := omit, server := g_pars.server_name, userAgent := omit); SIP.send(tx_resp); f_sip_param_match_value_or_fail(g_rx_sip_req.msgHeader.authorization.body.digestResponse, "auts", f_sip_str_quote(enc_MIME_Base64(g_pars.subscr.auth.auts))); f_ims_validate_register_P_Access_Network_info(g_rx_sip_req, exp_present := false); f_ims_validate_register_contact(g_rx_sip_req.msgHeader.contact); f_ims_parse_register_contact(g_rx_sip_req.msgHeader.contact); f_ims_parse_security_client(g_rx_sip_req.msgHeader.security_client); f_ims_setup_ipsec(); security_server := f_gen_Security_server(); /* Tx again 401 Unauthorized, this time our AMI interface will accept it: */ tx_resp := ts_SIP_Response_Unauthorized(sip_call_id, from_addr, to_addr, via, wwwAuthenticate, sip_seq_nr, "REGISTER", p_associated_uri := g_pars.subscr.p_associated_uri, security_server := security_server, server := g_pars.server_name, supported := supported, userAgent := omit); SIP.send(tx_resp); } /* Now we should receive a new REGISTER over ipsec: */ as_IMS_2nd_register(wwwAuthenticate, early_ret := early_ret); } [fail_others] as_SIP_fail_resp(sip_expect_str); [fail_others] as_SIP_fail_req(sip_expect_str); } /* Peer is issuing 2nd register, accept it: */ altstep as_IMS_2nd_register(WwwAuthenticate wwwAuthenticate, IMS_register_early_return early_ret := IMS_REG_EARLY_RET_BEFORE_None, boolean fail_others := true) runs on IMS_ConnHdlr { var template (present) Authorization authorization := f_tr_Authorization_AKAv1MD5(wwwAuthenticate, g_pars.subscr.imsi & "@" & g_pars.realm, f_sip_SipUrl_to_str(g_pars.registrar_sip_req_uri)); var template (present) PDU_SIP_Request exp_req := tr_SIP_REGISTER(g_pars.registrar_sip_req_uri, ?, tr_From(), tr_To(), tr_Via_from(f_tr_HostPort(g_pars.subscr.remote_sip_host, g_pars.subscr.ipsec_remote_port_s)), authorization := authorization); var charstring sip_expect_str := log2str(exp_req); [] SIP.receive(exp_req) -> value g_rx_sip_req { var template (value) PDU_SIP_Response tx_resp; var Via via; var Contact contact; var CallidString sip_call_id; var template (value) From from_addr; var template (value) To to_addr; var template (value) Require require := ts_Require({"sec-agree"}); var template (value) Supported supported := ts_Supported({"sec-agree"}); var integer sip_seq_nr; if (early_ret == IMS_REG_EARLY_RET_BEFORE_Protected_100Trying) { return; /* Done */ } sip_call_id := g_rx_sip_req.msgHeader.callId.callid; via := g_rx_sip_req.msgHeader.via; from_addr := g_rx_sip_req.msgHeader.fromField; to_addr := g_rx_sip_req.msgHeader.toField; sip_seq_nr := g_rx_sip_req.msgHeader.cSeq.seqNumber; /* Tx 100 Trying */ tx_resp := ts_SIP_Response_Trying(sip_call_id, from_addr, to_addr, via, sip_seq_nr, "REGISTER", allow := omit, server := g_pars.server_name, userAgent := omit); SIP.send(tx_resp); /* Validate Digest Response: */ f_ims_validate_Authorization_AKAv1MD5_Response(g_rx_sip_req.msgHeader.authorization, "REGISTER"); /* Validate P-Access-Network-Info: */ f_ims_validate_register_P_Access_Network_info(g_rx_sip_req, exp_present := true); if (early_ret == IMS_REG_EARLY_RET_BEFORE_Protected_200OK) { return; /* Done */ } g_pars.subscr.p_associated_uri := valueof(ts_P_Associated_Uri({ ts_P_Assoc_uri_spec(ts_NameAddr(ts_SipUrl(ts_HostPort(g_pars.realm), ts_UserInfo(g_pars.subscr.msisdn), scheme := "sip"))), ts_P_Assoc_uri_spec(ts_NameAddr(ts_SipUrl(ts_HostPort(g_pars.subscr.msisdn), omit, scheme := "tel"))), ts_P_Assoc_uri_spec(g_rx_sip_req.msgHeader.toField.addressField.nameAddr) })); contact := g_rx_sip_req.msgHeader.contact; f_ims_validate_register_contact(contact); f_ims_parse_register_contact(contact); g_pars.subscr.registered_contact := contact; /* Set expires= to configured value: RFC3261 10.3 8) */ for (var integer i := 0; i < lengthof(contact.contactBody.contactAddresses); i := i + 1) { contact.contactBody.contactAddresses[i].contactParams := valueof({ ts_Param("expires", int2str(g_pars.subscr.registrar_expires)) }); } /* Tx 200 OK */ to_addr.toParams := f_sip_param_set(to_addr.toParams, "tag", f_sip_rand_tag()); tx_resp := ts_SIP_Response(sip_call_id, from_addr, to_addr, "REGISTER", 200, sip_seq_nr, "OK", via, contact := contact, p_associated_uri := g_pars.subscr.p_associated_uri, require := require, server := g_pars.server_name, supported := supported, userAgent := omit); SIP.send(tx_resp); } [fail_others] as_SIP_fail_resp(sip_expect_str); [fail_others] as_SIP_fail_req(sip_expect_str); } /* Peer wants to unregister, accept it: */ altstep as_IMS_unregister(boolean fail_others := true) runs on IMS_ConnHdlr { var template (present) PDU_SIP_Request exp_req := tr_SIP_REGISTER(g_pars.registrar_sip_req_uri, ?, tr_From(), tr_To(), tr_Via_from(f_tr_HostPort(g_pars.subscr.remote_sip_host, g_pars.subscr.ipsec_remote_port_s)), expires := tr_Expires(int2str(0)), require := tr_Require(superset("sec-agree")), security_client := tr_Security_client(superset(tr_Security_mechanism("ipsec-3gpp", superset(tr_Param("alg","hmac-sha-1-96"))))), supported := tr_Supported(superset("path", "sec-agree"))); var charstring sip_expect_str := log2str(exp_req); [] SIP.receive(exp_req) -> value g_rx_sip_req { var template (value) PDU_SIP_Response tx_resp; var Via via; var CallidString sip_call_id; var Contact contact; var template (value) From from_addr; var template (value) To to_addr; var template (value) CommaParam_List digestCln ; var template (value) WwwAuthenticate wwwAuthenticate; var template (value) Security_server security_server; var template (value) Require require := ts_Require({"sec-agree"}); var template (value) Supported supported := ts_Supported({"sec-agree"}); var template (present) Authorization authorization; var integer sip_seq_nr; sip_call_id := g_rx_sip_req.msgHeader.callId.callid; via := g_rx_sip_req.msgHeader.via; via.viaBody[0].viaParams := f_sip_param_set(via.viaBody[0].viaParams, "rport", "1234"); /* TODO: set remote src port of the REGISTER */ from_addr := g_rx_sip_req.msgHeader.fromField; to_addr := g_rx_sip_req.msgHeader.toField; sip_seq_nr := g_rx_sip_req.msgHeader.cSeq.seqNumber; contact := g_rx_sip_req.msgHeader.contact; f_ims_validate_register_contact(contact); /* Validate P-Access-Network-Info: 3GPP TS 24.229 5.1.2A.1.1 * "If available to the UE (as defined in the access technology specific annexes for each access technology), the UE shall * insert a P-Access-Network-Info header field into any request for a dialog, any subsequent request (except CANCEL * requests) or response (except CANCEL responses) within a dialog or any request for a standalone method (see * subclause 7.2A.4). Insertion of the P-Access-Network-Info header field into the ACK request is optional." */ f_ims_validate_register_P_Access_Network_info(g_rx_sip_req, exp_present := true); /* Tx 100 Tyring */ tx_resp := ts_SIP_Response_Trying(sip_call_id, from_addr, to_addr, via, sip_seq_nr, "REGISTER", allow := omit, server := g_pars.server_name, userAgent := omit); SIP.send(tx_resp); /* Change all Contact parameters to expires=0: */ for (var integer i := 0; i < lengthof(contact.contactBody.contactAddresses); i := i + 1) { contact.contactBody.contactAddresses[i].contactParams := valueof({ ts_Param("expires", "0") }); } g_pars.subscr.registered_contact := omit; /* Tx 200 OK */ to_addr.toParams := f_sip_param_set(to_addr.toParams, "tag", f_sip_rand_tag()); tx_resp := ts_SIP_Response(sip_call_id, from_addr, to_addr, "REGISTER", 200, sip_seq_nr, "OK", via, contact := contact, p_associated_uri := g_pars.subscr.p_associated_uri, require := require, server := g_pars.server_name, supported := supported, userAgent := omit); SIP.send(tx_resp); } [fail_others] as_SIP_fail_resp(sip_expect_str); [fail_others] as_SIP_fail_req(sip_expect_str); } /* IMS Core starts a call towards the peer: 3GPP TS 24.229 5.1.3.1 */ function f_IMS_mt_call_setup() runs on IMS_ConnHdlr { var template (value) PDU_SIP_Request req; var template (present) PDU_SIP_Response exp; var template (present) From from_addr_exp; var template (present) To to_addr_exp; var Via via; var template (omit) Require require := omit; var template (omit) Supported supported := omit; var charstring tx_sdp; var default d_trying, d_ringing; var charstring branch_value; var Contact calling_contact; /* RFC 3261 8.1.1.3 From */ g_pars.subscr.cp.from_addr := valueof(ts_From(g_pars.subscr.cp.calling.addr, g_pars.subscr.cp.calling.params)); g_pars.subscr.cp.from_addr.fromParams := f_sip_param_set(g_pars.subscr.cp.from_addr.fromParams, "tag", f_sip_rand_tag()); g_pars.subscr.cp.to_addr := valueof(ts_To(g_pars.subscr.cp.called.addr, g_pars.subscr.cp.called.params)); from_addr_exp := tr_From(tr_Addr_Union_from_val(g_pars.subscr.cp.from_addr.addressField), *); to_addr_exp := tr_To(tr_Addr_Union_from_val(g_pars.subscr.cp.to_addr.addressField), *); branch_value := f_sip_gen_branch(f_sip_Addr_Union_to_str(g_pars.subscr.cp.from_addr.addressField), f_sip_Addr_Union_to_str(valueof(g_pars.subscr.cp.to_addr.addressField)), g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.sip_seq_nr); via := g_pars.local_via; via.viaBody[0].viaParams := f_sip_param_set(via.viaBody[0].viaParams, "branch", branch_value); calling_contact := valueof(ts_Contact({ ts_ContactAddress(g_pars.subscr.cp.calling.addr, omit) })); if (g_pars.subscr.cp.support_precondition_ext) { /* indicate the support for "reliable provisional response" * and "preconditions mechanism" */ supported := ts_Supported({ "100rel", "precondition" }); tx_sdp := f_gen_sdp(IMS_GEN_SDP_MO_INVITE); } else { /* Don't add any preconditions to SDP: */ tx_sdp := f_gen_sdp(IMS_GEN_SDP_None); } req := ts_SIP_INVITE(g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, via, calling_contact, g_pars.subscr.cp.sip_seq_nr, supported := supported, body := tx_sdp); SIP.send(req); /* Conditionally match and accept 100 Trying. */ exp := tr_SIP_Response_Trying(g_pars.subscr.cp.sip_call_id, from_addr_exp, to_addr_exp, f_tr_Via_response(via), g_pars.subscr.cp.sip_seq_nr, "INVITE"); d_trying := activate(as_SIP_ignore_resp(exp)); if (g_pars.subscr.cp.require_precondition_ext) { var template (present) SDP_attribute_list preconds; /* Rx 183 Session Progress */ exp := tr_SIP_Response_SessionProgress( g_pars.subscr.cp.sip_call_id, from_addr_exp, to_addr_exp, f_tr_Via_response(via), g_pars.subscr.cp.sip_seq_nr, "INVITE", require := tr_Require(superset("100rel", "precondition")), rseq := tr_RSeq(?)); alt { [] SIP.receive(exp) -> value g_rx_sip_resp; [] SIP.receive(tr_SIP_Response_Ringing(?, ?, ?)) -> value g_rx_sip_resp { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Expected 183 Session Progress instead of 180 Ringing: ", g_rx_sip_resp)); } } /* Validate SDP in Session Progress contains the preconditions: * a=curr:qos local none * a=curr:qos remote none * a=des:qos mandatory local sendrecv * a=des:qos mandatory remote sendrecv * a=conf:qos remote sendrecv */ preconds := superset( tr_SDP_curr(c_SDP_PRECON_STATUS_TYPE_local, c_SDP_PRECON_DIR_TAG_none), tr_SDP_curr(c_SDP_PRECON_STATUS_TYPE_remote, c_SDP_PRECON_DIR_TAG_none), tr_SDP_des(c_SDP_PRECON_STRENGTH_TAG_mandatory, c_SDP_PRECON_STATUS_TYPE_local, c_SDP_PRECON_DIR_TAG_sendrecv), tr_SDP_des(c_SDP_PRECON_STRENGTH_TAG_mandatory, c_SDP_PRECON_STATUS_TYPE_remote, c_SDP_PRECON_DIR_TAG_sendrecv), tr_SDP_conf(c_SDP_PRECON_STATUS_TYPE_remote, c_SDP_PRECON_DIR_TAG_sendrecv)); if (not ispresent(g_pars.subscr.cp.peer_sdp.attributes) or not match(g_pars.subscr.cp.peer_sdp.attributes, preconds)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Unexpected precondition attrs in Session Progress : ", g_pars.subscr.cp.peer_sdp.attributes)); } /* Tx PRACK */ req := ts_SIP_PRACK(g_pars.subscr.cp.to_addr.addressField.nameAddr.addrSpec, g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, via, g_rx_sip_resp.msgHeader.cSeq.seqNumber + 1, rack := ts_RAck(g_rx_sip_resp.msgHeader.rseq.response_num, g_rx_sip_resp.msgHeader.cSeq.seqNumber, "INVITE"), body := omit); SIP.send(req); if (g_pars.subscr.cp.mt.tx_coord_cmd_session_progress) { /* Signal used to inform that Dedicated bearer can be established: */ COORD.send(IMS_COORD_CMD_CALL_SESSION_PROGRESS); } /* Rx 200 OK (PRACK) */ exp := tr_SIP_Response(g_pars.subscr.cp.sip_call_id, from_addr_exp, to_addr_exp, f_tr_Via_response(via), omit, "PRACK", 200, g_rx_sip_resp.msgHeader.cSeq.seqNumber + 1, "OK", body := omit); as_SIP_expect_resp(exp, fail_others := false); /* Tx UPDATE */ tx_sdp := f_gen_sdp(IMS_GEN_SDP_MO_UPDATE); req := ts_SIP_UPDATE(g_pars.subscr.cp.to_addr.addressField.nameAddr.addrSpec, g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, via, g_rx_sip_resp.msgHeader.cSeq.seqNumber + 1, contact := calling_contact, require := ts_Require({"sec-agree", "precondition"}), body := tx_sdp); SIP.send(req); /* Rx 200 OK (UPDATE) */ exp := tr_SIP_Response(g_pars.subscr.cp.sip_call_id, from_addr_exp, to_addr_exp, f_tr_Via_response(via), omit, "UPDATE", 200, g_rx_sip_resp.msgHeader.cSeq.seqNumber + 1, "OK", body := ?); as_SIP_expect_resp(exp, fail_others := false); /* Validate SDP in 200 OK (UPDATE) contains the preconditions: * a=curr:qos local sendrecv * a=curr:qos remote sendrecv * a=des:qos mandatory local sendrecv * a=des:qos mandatory remote sendrecv */ preconds := superset( tr_SDP_curr(c_SDP_PRECON_STATUS_TYPE_local, c_SDP_PRECON_DIR_TAG_sendrecv), tr_SDP_curr(c_SDP_PRECON_STATUS_TYPE_remote, c_SDP_PRECON_DIR_TAG_sendrecv), tr_SDP_des(c_SDP_PRECON_STRENGTH_TAG_mandatory, c_SDP_PRECON_STATUS_TYPE_local, c_SDP_PRECON_DIR_TAG_sendrecv), tr_SDP_des(c_SDP_PRECON_STRENGTH_TAG_mandatory, c_SDP_PRECON_STATUS_TYPE_remote, c_SDP_PRECON_DIR_TAG_sendrecv)); if (not ispresent(g_pars.subscr.cp.peer_sdp.attributes) or not match(g_pars.subscr.cp.peer_sdp.attributes, preconds)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Unexpected precondition attrs in 200 OK (UPDATE): ", g_pars.subscr.cp.peer_sdp.attributes)); } g_pars.subscr.cp.sip_seq_nr := g_rx_sip_resp.msgHeader.cSeq.seqNumber + 1; } /* Conditionally match and accept 180 Ringing */ exp := tr_SIP_Response_Ringing(g_pars.subscr.cp.sip_call_id, from_addr_exp, to_addr_exp, f_tr_Via_response(via), g_pars.subscr.cp.sip_seq_nr, "INVITE"); d_ringing := activate(as_SIP_ignore_resp(exp)); /* Wait for 200 OK (INVITE) answer */ exp := tr_SIP_Response( g_pars.subscr.cp.sip_call_id, from_addr_exp, to_addr_exp, f_tr_Via_response(via), *, "INVITE", 200, g_pars.subscr.cp.sip_seq_nr, "OK", body := *); as_SIP_expect_resp(exp, fail_others := false); deactivate(d_trying); deactivate(d_ringing); /* Make sure "audio" tag set in Contact header */ f_ims_validate_INVITE_Contact(g_rx_sip_resp.msgHeader.contact); /* Update To with the tags received from peer: */ g_pars.subscr.cp.to_addr := g_rx_sip_resp.msgHeader.toField; /* Transmit ACK */ g_pars.subscr.cp.sip_seq_nr := g_pars.subscr.cp.sip_seq_nr + 1; req := ts_SIP_ACK(g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, via, g_pars.subscr.cp.sip_seq_nr, omit); SIP.send(req); g_pars.subscr.cp.sip_seq_nr := g_pars.subscr.cp.sip_seq_nr + 1; } /* Tx BYE: */ function f_IMS_do_call_hangup() runs on IMS_ConnHdlr { var template (value) PDU_SIP_Request req; var template (present) PDU_SIP_Response exp_resp; var Via via; var charstring branch_value; branch_value := f_sip_gen_branch(f_sip_Addr_Union_to_str(g_pars.subscr.cp.from_addr.addressField), f_sip_Addr_Union_to_str(valueof(g_pars.subscr.cp.to_addr.addressField)), g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.sip_seq_nr); via := g_pars.local_via; via.viaBody[0].viaParams := f_sip_param_set(via.viaBody[0].viaParams, "branch", branch_value); /* Transmit ACK */ req := ts_SIP_BYE(g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, via, g_pars.subscr.cp.sip_seq_nr, omit); SIP.send(req); /* Wait for OK answer */ exp_resp := tr_SIP_Response( g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, tr_To(tr_Addr_Union_from_val(g_pars.subscr.cp.to_addr.addressField), *), f_tr_Via_response(via), *, "BYE", 200, g_pars.subscr.cp.sip_seq_nr, "OK"); as_SIP_expect_resp(exp_resp); g_pars.subscr.cp.sip_seq_nr := g_pars.subscr.cp.sip_seq_nr + 1; } private function f_ConnHdlr_parse_initial_SIP_INVITE(PDU_SIP_Request rx_sip_req) runs on IMS_ConnHdlr { f_SDP_decodeMessage(rx_sip_req.messageBody, g_pars.subscr.cp.peer_sdp); log("Rx Initial MO INVITE decoded SDP: ", g_pars.subscr.cp.peer_sdp); /* Obtain params: */ g_pars.subscr.cp.sip_call_id := rx_sip_req.msgHeader.callId.callid; g_pars.subscr.cp.from_addr := rx_sip_req.msgHeader.fromField; g_pars.subscr.cp.to_addr := rx_sip_req.msgHeader.toField; g_pars.subscr.cp.to_addr.toParams := f_sip_param_set(g_pars.subscr.cp.to_addr.toParams, "tag", f_sip_rand_tag()); g_pars.subscr.cp.sip_seq_nr := rx_sip_req.msgHeader.cSeq.seqNumber; } /* Peer is calling us, accept it: */ altstep as_IMS_mo_call_accept(boolean exp_update_to_direct_rtp := false, boolean fail_others := true) runs on IMS_ConnHdlr { var template (present) PDU_SIP_Request exp_req := tr_SIP_INVITE(f_tr_SipUrl_opt_defport(ts_SipUrl_from_Addr_Union(g_pars.subscr.cp.called.addr)), ?, tr_From(tr_Addr_Union_from_val(g_pars.subscr.cp.calling.addr), *), tr_To(tr_Addr_Union_from_val(g_pars.subscr.cp.called.addr), *), tr_Via_from(f_tr_HostPort(g_pars.subscr.remote_sip_host, g_pars.subscr.ipsec_remote_port_s)), ?, ?); var charstring sip_expect_str := log2str(exp_req); [] SIP.receive(exp_req) -> value g_rx_sip_req { var template (value) PDU_SIP_Response tx_resp; var Via via; var template (omit) charstring tx_sdp; var boolean peer_support_precondition := match(g_rx_sip_req.msgHeader.supported.optionsTags, superset("100rel", "precondition")); /* Obtain params: */ f_ConnHdlr_parse_initial_SIP_INVITE(g_rx_sip_req); via := g_rx_sip_req.msgHeader.via; f_ims_validate_register_P_Access_Network_info(g_rx_sip_req, exp_present := true); f_ims_validate_invite_P_Preferred_Service(g_rx_sip_req, exp_present := true); f_ims_validate_INVITE_Contact(g_rx_sip_req.msgHeader.contact); f_ims_validate_INVITE_SDP(g_pars.subscr.cp.peer_sdp); if (g_pars.subscr.cp.require_precondition_ext and not peer_support_precondition) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Missing '100rel,precondition' in INVITE Supported header: ", g_rx_sip_req.msgHeader.supported)); } /* Tx 100 Tyring */ tx_resp := ts_SIP_Response_Trying(g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, via, g_pars.subscr.cp.sip_seq_nr, "INVITE", allow := omit, server := g_pars.server_name, userAgent := omit); SIP.send(tx_resp); if (g_pars.subscr.cp.mo.tx_coord_cmd_invite_trying) { /* Signal used to inform that Dedicated bearer can be established: */ COORD.send(IMS_COORD_CMD_CALL_TRYING); } /* Use precondition ? */ if (g_pars.subscr.cp.require_precondition_ext or (g_pars.subscr.cp.support_precondition_ext and peer_support_precondition)) { var template (present) SDP_attribute_list preconds; /* Validate SDP in INVITE contains the preconditions: * a=curr:qos local none * a=curr:qos remote none * a=des:qos mandatory local sendrecv * a=des:qos optional remote sendrecv */ preconds := superset( tr_SDP_curr(c_SDP_PRECON_STATUS_TYPE_local, c_SDP_PRECON_DIR_TAG_none), tr_SDP_curr(c_SDP_PRECON_STATUS_TYPE_remote, c_SDP_PRECON_DIR_TAG_none), tr_SDP_des(c_SDP_PRECON_STRENGTH_TAG_mandatory, c_SDP_PRECON_STATUS_TYPE_local, c_SDP_PRECON_DIR_TAG_sendrecv), tr_SDP_des(c_SDP_PRECON_STRENGTH_TAG_optional, c_SDP_PRECON_STATUS_TYPE_remote, c_SDP_PRECON_DIR_TAG_sendrecv)); if (not ispresent(g_pars.subscr.cp.peer_sdp.attributes) or not match(g_pars.subscr.cp.peer_sdp.attributes, preconds)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Unexpected precondition attrs in INVITE : ", g_pars.subscr.cp.peer_sdp.attributes)); } /* Tx 183 Session Progress */ tx_sdp := f_gen_sdp(IMS_GEN_SDP_MT_Session_Progress); tx_resp := ts_SIP_Response_SessionProgress(g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, via, g_pars.subscr.cp.sip_seq_nr, rseq := ts_RSeq(1), body := tx_sdp); SIP.send(tx_resp); /* Rx PRACK */ exp_req := tr_SIP_PRACK(f_tr_SipUrl_opt_defport(ts_SipUrl_from_Addr_Union(g_pars.subscr.cp.called.addr)), g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, f_tr_Via_response(via), g_pars.subscr.cp.sip_seq_nr + 1, rack := tr_RAck(1, g_pars.subscr.cp.sip_seq_nr, "INVITE"), body := omit); as_SIP_expect_req(exp_req); /* Tx 200 OK (PRACK) */ tx_resp := ts_SIP_Response(g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, "PRACK", 200, g_pars.subscr.cp.sip_seq_nr, "OK", via, body := omit); SIP.send(tx_resp); /* Rx UPDATE */ exp_req := tr_SIP_UPDATE(f_tr_SipUrl_opt_defport(ts_SipUrl_from_Addr_Union(g_pars.subscr.cp.called.addr)), g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, f_tr_Via_response(via), g_pars.subscr.cp.sip_seq_nr + 1, require := tr_Require(superset("precondition")), body := ?); as_SIP_expect_req(exp_req); /* Validate SDP in UPDATE contains the preconditions: * a=curr:qos local sendrecv * a=curr:qos remote none * a=des:qos mandatory local sendrecv * a=des:qos mandatory remote sendrecv */ preconds := superset( tr_SDP_curr(c_SDP_PRECON_STATUS_TYPE_local, c_SDP_PRECON_DIR_TAG_sendrecv), tr_SDP_curr(c_SDP_PRECON_STATUS_TYPE_remote, c_SDP_PRECON_DIR_TAG_none), tr_SDP_des(c_SDP_PRECON_STRENGTH_TAG_mandatory, c_SDP_PRECON_STATUS_TYPE_local, c_SDP_PRECON_DIR_TAG_sendrecv), tr_SDP_des(c_SDP_PRECON_STRENGTH_TAG_mandatory, c_SDP_PRECON_STATUS_TYPE_remote, c_SDP_PRECON_DIR_TAG_sendrecv)); if (not ispresent(g_pars.subscr.cp.peer_sdp.attributes) or not match(g_pars.subscr.cp.peer_sdp.attributes, preconds)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Unexpected precondition attrs in UPDATE: ", g_pars.subscr.cp.peer_sdp.attributes)); } /* Tx 200 OK (UPDATE) */ tx_sdp := f_gen_sdp(IMS_GEN_SDP_MT_UPDATE_200OK); tx_resp := ts_SIP_Response(g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, "UPDATE", 200, g_pars.subscr.cp.sip_seq_nr, "OK", via, body := tx_sdp); SIP.send(tx_resp); g_pars.subscr.cp.sip_seq_nr := g_pars.subscr.cp.sip_seq_nr + 1; /* 200 OK (INVITE) has no SDP if we use precondition: */ tx_sdp := omit; } else { /* Check no precondition is sent in SDP */ if (ispresent(g_pars.subscr.cp.peer_sdp.attributes) and match (g_pars.subscr.cp.peer_sdp.attributes, superset(tr_SDP_curr_present, tr_SDP_des_present, tr_SDP_conf_present))) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(g_name & ": Unexpected precondition attrs in INVITE: ", g_pars.subscr.cp.peer_sdp.attributes)); } /* 200 OK (INVITE) has SDP if no precondition is used: */ tx_sdp := f_gen_sdp(); } /* Tx 180 Ringing */ tx_resp := ts_SIP_Response_Ringing(g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, via, g_pars.subscr.cp.sip_seq_nr); SIP.send(tx_resp); /* Tx 200 OK */ tx_sdp := f_gen_sdp(); tx_resp := ts_SIP_Response(g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, "INVITE", 200, g_pars.subscr.cp.sip_seq_nr, "OK", via, body := tx_sdp); SIP.send(tx_resp); /* Wait for ACK */ exp_req := tr_SIP_ACK(f_tr_SipUrl_opt_defport(ts_SipUrl_from_Addr_Union(g_pars.subscr.cp.called.addr)), g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, f_tr_Via_response(via), g_pars.subscr.cp.sip_seq_nr, *); as_SIP_expect_req(exp_req); } [fail_others] as_SIP_fail_resp(sip_expect_str); [fail_others] as_SIP_fail_req(sip_expect_str); } /* Call is terminated by peer: */ altstep as_IMS_exp_call_hangup(template (present) integer exp_seq_nr := ?, boolean fail_others := true) runs on IMS_ConnHdlr { var template (present) PDU_SIP_Request exp_req := tr_SIP_BYE(f_tr_SipUrl_opt_defport(ts_SipUrl_from_Addr_Union(g_pars.subscr.cp.called.addr)), g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, tr_Via_from(f_tr_HostPort(g_pars.subscr.remote_sip_host, g_pars.subscr.ipsec_remote_port_s)), exp_seq_nr); var charstring sip_expect_str := log2str(exp_req); [] SIP.receive(exp_req) -> value g_rx_sip_req { var template (value) PDU_SIP_Response tx_resp; var charstring tx_sdp; var Via via; /* Update parameters: */ g_pars.subscr.cp.sip_seq_nr := g_rx_sip_req.msgHeader.cSeq.seqNumber; /* "branch" has changed: */ via := g_rx_sip_req.msgHeader.via; /* Tx 200 OK */ tx_sdp := f_gen_sdp(); tx_resp := ts_SIP_Response(g_pars.subscr.cp.sip_call_id, g_pars.subscr.cp.from_addr, g_pars.subscr.cp.to_addr, "BYE", 200, g_pars.subscr.cp.sip_seq_nr, "OK", via, body := tx_sdp); SIP.send(tx_resp); } [fail_others] as_SIP_fail_resp(sip_expect_str); [fail_others] as_SIP_fail_req(sip_expect_str); } }