asterisk: Initial IMS registration

This patch is a step towards testing IMS.
So far only the code infrstrastructure to handle the 1st REGISTER
(outside IPSEC) is provided. It can already be seen how Asterisk
sends the second REGISTER over IPSEC, but there's no means to test it
yet in TTCN-3. This will be done in a follow-up patch, which may take
some work.

Change-Id: Idb3b19ccd82cad25948106b2c72aa424d7f79cd8
This commit is contained in:
Pau Espin 2024-05-10 20:30:44 +02:00
parent 5c36a657d7
commit a2812ec0da
3 changed files with 416 additions and 3 deletions

View File

@ -37,6 +37,7 @@ modulepar {
charstring mp_local_ims_host := "127.0.0.3";
integer mp_local_ims_port := 5060;
charstring mp_ims_imsi := "238010000090828";
/* Asterisk AMI: */
charstring mp_ami_remote_host := "127.0.0.1";
@ -45,6 +46,7 @@ modulepar {
integer mp_ami_local_port := 0;
charstring mp_ami_user := "test_user";
charstring mp_ami_secret := "1234";
charstring mp_volte_ims_outbound_registration := "volte_ims";
}
type component test_CT {
@ -74,6 +76,15 @@ function f_init_ConnHdlrPars(integer idx := 1) runs on test_CT return SIPConnHdl
return valueof(pars);
}
function f_init_IMS_ConnHdlrPars(integer idx := 1) runs on test_CT return IMS_ConnHdlrPars {
var template (value) IMS_CallPars cp := t_IMS_CallPars(mp_local_sip_host, 1234 + 2*idx);
var template (value) IMS_ConnHdlrPars pars := t_IMS_Pars(mp_local_ims_host,
mp_local_ims_port,
mp_ims_imsi,
cp := cp);
return valueof(pars);
}
/* Initialize connection towards Asterisk AMI */
private function f_init_ami() runs on test_CT {
var charstring id := "Asterisk_Tests_AMI_EMU";
@ -139,6 +150,22 @@ runs on test_CT return SIPConnHdlr {
return vc_conn;
}
function f_start_handler_IMS(ims_void_fn fn, IMS_ConnHdlrPars pars)
runs on test_CT return IMS_ConnHdlr {
var IMS_ConnHdlr vc_conn;
var charstring id := testcasename() & "-IMS_ConnHdlr-" & pars.user;
vc_conn := IMS_ConnHdlr.create(id) alive;
connect(vc_conn:SIP, vc_IMS:CLIENT);
connect(vc_conn:SIP_PROC, vc_IMS:CLIENT_PROC);
connect(vc_conn:COORD, self:IMS_COORD);
vc_conn.start(f_ims_handler_init(fn, id, pars));
return vc_conn;
}
/* Test SIP registration of local clients */
private function f_TC_internal_registration(charstring id) runs on SIPConnHdlr {
@ -322,12 +349,51 @@ testcase TC_selftest() runs on test_CT {
setverdict(pass);
}
/* Test SIP registration of local clients */
private function f_TC_ims_registration(charstring id) runs on IMS_ConnHdlr {
f_create_sip_expect(valueof(ts_SipUrl_from_Addr_Union(g_pars.registrar_sip_record.addr)));
as_IMS_register();
setverdict(pass);
}
testcase TC_ims_registration() runs on test_CT {
var IMS_ConnHdlrPars pars;
var IMS_ConnHdlr vc_conn;
f_init();
pars := f_init_IMS_ConnHdlrPars();
vc_conn := f_start_handler_IMS(refers(f_TC_ims_registration), pars);
/* Give some time for IMS_ConnHdlr to register SIP expect. This could be done through IMS_COORD. */
f_sleep(1.0);
/* Clear events: */
AMI_CLIENT.clear;
/* Trigger registration: */
f_ami_action_PJSIPRegister(AMI_CLIENT, mp_volte_ims_outbound_registration);
/* TODO: Rx "Event: AuthRequest" */
/* TODO: Tx "Action: AuthResponse" */
/* TODO: Rx "Response: Success" */
/* TODO: once registration is successful, rx:
* Event: Registry
* ChannelType: pjsip
* Username: <value>
* Domain: <value>
* Status: <value>
* Cause: <value> */
/* TODO: test "Action: PJSIPUnregister" */
/* TODO: in another test emulating a call, test "Action: DedicatedBearerStatus" */
vc_conn.done;
f_shutdown();
}
control {
execute( TC_internal_registration() );
execute( TC_internal_call_momt() );
execute( TC_internal_call_all_2registered() );
execute( TC_internal_call_all_3registered() );
execute( TC_internal_call_all_4registered() );
execute( TC_ims_registration() );
}
}

View File

@ -23,6 +23,9 @@ import from SIP_Emulation all;
import from SIPmsg_Types all;
import from SIP_Templates all;
const char c_sip_server_name := "osmo-ttcn3-hacks/0.23";
type port IMSCoord_PT message
{
inout charstring;
@ -43,11 +46,18 @@ type record of IMS_ConnHdlr IMS_ConnHdlrList;
type record IMS_ConnHdlrPars {
float t_guard,
charstring remote_sip_host,
uint16_t remote_sip_port,
charstring realm,
charstring local_sip_host,
uint16_t local_sip_port,
charstring remote_sip_host optional,
uint16_t remote_sip_port optional,
charstring user,
charstring display_name,
charstring password,
integer ipsec_local_spi_c,
integer ipsec_local_spi_s,
integer ipsec_remote_spi_c optional,
integer ipsec_remote_spi_s optional,
SipUrl registrar_sip_req_uri,
SipAddr registrar_sip_record,
CallidString registrar_sip_call_id,
@ -66,6 +76,10 @@ type record IMS_CallParsMT {
/* Whether to expect CANCEL instead of ACK as answer to our OK */
boolean exp_cancel
}
template (value) IMS_CallParsMT t_IMS_CallParsMT := {
wait_coord_cmd_pickup := false,
exp_cancel := false
}
type record IMS_CallPars {
SipAddr calling optional,
@ -85,4 +99,300 @@ type record IMS_CallPars {
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,
peer_sdp := omit,
mt := t_IMS_CallParsMT
}
template (value) IMS_ConnHdlrPars t_IMS_Pars(charstring local_sip_host,
uint16_t local_sip_port,
charstring user,
charstring display_name := "Anonymous",
charstring password := "secret",
template (omit) IMS_CallPars cp := omit) := {
t_guard := 30.0,
realm := local_sip_host,
local_sip_host := local_sip_host,
local_sip_port := local_sip_port,
remote_sip_host := omit,
remote_sip_port := omit,
user := user,
display_name := f_sip_str_quote(display_name),
password := password,
ipsec_local_spi_c := 4142,
ipsec_local_spi_s := 4143,
ipsec_remote_spi_c := omit,
ipsec_remote_spi_s := omit,
registrar_sip_req_uri := valueof(ts_SipUrlHost(local_sip_host)),
registrar_sip_record := ts_SipAddr(ts_HostPort(local_sip_host),
ts_UserInfo(user),
f_sip_str_quote(display_name)),
registrar_sip_call_id := hex2str(f_rnd_hexstring(15)) & "@" & local_sip_host,
registrar_sip_seq_nr := f_sip_rand_seq_nr(),
local_via := ts_Via_from(ts_HostPort(local_sip_host, local_sip_port)),
local_sip_url_ext := ts_SipUrl(ts_HostPort(local_sip_host, local_sip_port),
ts_UserInfo(user)),
local_sip_record := ts_SipAddr(ts_HostPort(local_sip_host),
ts_UserInfo(user)),
local_contact := valueof(ts_Contact({
ts_ContactAddress(
ts_Addr_Union_SipUrl(ts_SipUrl(ts_HostPort(
local_sip_host,
local_sip_port),
ts_UserInfo(user))),
omit)
})),
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);
}
private 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));
}
}
private 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));
}
}
private function f_ims_validate_register_contact(Contact rx_contact)
{
/* IMS contact shows up like this:
* 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>"
*/
/* TODO: "that the UE must include the IMS Communication Service Identifier (ICSI)
in the contact: header to indicate IMS Multimedia Telephony." */
/* TODO: "The UE must include an IMEI URN in the +sip.instance header field
parameter of the contact: header." */
/* TODO: "If the UE supports SMS over IP, it must include the feature tag
“+g.3gpp.smsip” in the contact: header." */
/* TODO: "If the UE supports conversational audio and video service, then this must
be indicated by adding a “video” media feature tag to the contact: header." */
}
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.ipsec_remote_spi_c := str2int(par_val);
par_val := f_sip_param_get_value_present_or_fail(sec_pars, "spi-s");
g_pars.ipsec_remote_spi_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.ipsec_remote_spi_c, " remote_spi_s=", g_pars.ipsec_remote_spi_s,
"local_spi_c=", g_pars.ipsec_local_spi_c, " local_spi_s=", g_pars.ipsec_local_spi_s);
}
private function f_ims_setup_ipsec(PDU_SIP_Request req_req) runs on IMS_ConnHdlr
{
var Security_client security_client := req_req.msgHeader.security_client;
f_ims_parse_security_client(security_client);
}
/* Peer is calling us, accept it: */
altstep as_IMS_register(boolean exp_update_to_direct_rtp := true,
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_SipAddr(),
tr_SipAddr(),
tr_Via_from(?),
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) SipAddr from_addr;
var template (value) SipAddr to_addr;
var template (value) CommaParam_List digestCln ;
var template (value) WwwAuthenticate wwwAuthenticate;
var template (value) Security_server security_server;
var template (value) Server server_name := ts_Server({c_sip_server_name});
var template (value) Supported supported := ts_Supported({"sec-agree"});
var Authorization authorization;
var integer sip_seq_nr;
var charstring tx_sdp;
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 := ts_SipAddr_from_Addr_Union(g_rx_sip_req.msgHeader.fromField.addressField,
g_rx_sip_req.msgHeader.fromField.fromParams);
to_addr := ts_SipAddr_from_Addr_Union(g_rx_sip_req.msgHeader.toField.addressField,
g_rx_sip_req.msgHeader.toField.toParams);
sip_seq_nr := g_rx_sip_req.msgHeader.cSeq.seqNumber;
contact := g_rx_sip_req.msgHeader.contact;
f_ims_validate_register_contact(contact);
/* TODO: Validate "Expires" is 600000 */
/* TODO: validate presence of:
* 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
*/
/* Tx 100 Tyring */
tx_resp := ts_SIP_Response_Trying(sip_call_id,
from_addr,
to_addr,
via,
sip_seq_nr,
"REGISTER",
allow := omit,
server := server_name,
userAgent := omit);
SIP.send(tx_resp);
f_ims_setup_ipsec(g_rx_sip_req);
to_addr.params := f_sip_param_set(to_addr.params, "tag", f_sip_rand_tag());
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("FJh2MfZfjjeIoHmLbrzQjvbhmnzLAoAAoGsZyVRFFuU="))
/* "opaque not needed in IMS "*/
};
wwwAuthenticate := ts_WwwAuthenticate( { ts_Challenge_digestCln(digestCln) } )
/* 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.ipsec_local_spi_c)),
ts_Param("spi-s", int2str(g_pars.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)
});
/* Tx 401 Unauthorized
* TODO: with IMS params */
tx_resp := ts_SIP_Response_Unauthorized(sip_call_id,
from_addr,
to_addr,
via,
wwwAuthenticate,
sip_seq_nr,
"REGISTER",
security_server := security_server,
server := server_name,
supported := supported,
userAgent := omit);
SIP.send(tx_resp);
/* TODO: Generate expected Authoritzation based on AKAv1-MD5: */
/*authorization := f_sip_digest_gen_Authorization(valueof(wwwAuthenticate),
g_pars.user, g_pars.password,
"REGISTER",
f_sip_SipUrl_to_str(g_pars.registrar_sip_record.addr.nameAddr.addrSpec))
*/
/* TODO: match Authorization from above: */
exp_req :=
tr_SIP_REGISTER(g_pars.registrar_sip_req_uri,
?,
tr_SipAddr(),
tr_SipAddr(),
tr_Via_from(?));
SIP.receive(exp_req) -> value g_rx_sip_req;
sip_call_id := g_rx_sip_req.msgHeader.callId.callid;
via := g_rx_sip_req.msgHeader.via;
from_addr := ts_SipAddr_from_Addr_Union(g_rx_sip_req.msgHeader.fromField.addressField,
g_rx_sip_req.msgHeader.fromField.fromParams);
to_addr := ts_SipAddr_from_Addr_Union(g_rx_sip_req.msgHeader.toField.addressField,
g_rx_sip_req.msgHeader.toField.toParams);
to_addr.params := f_sip_param_set(to_addr.params, "tag", f_sip_rand_tag());
sip_seq_nr := g_rx_sip_req.msgHeader.cSeq.seqNumber;
/* TODO: Add following fields:
* Supported: sec-agree
* 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
* */
tx_resp := ts_SIP_Response(sip_call_id,
from_addr,
to_addr,
"REGISTER", 200,
sip_seq_nr,
"OK",
via);
SIP.send(tx_resp);
}
[fail_others] as_SIP_fail_resp(sip_expect_str);
[fail_others] as_SIP_fail_req(sip_expect_str);
}
}

View File

@ -853,12 +853,18 @@ ts_SIP_Response_Trying(
Via via,
integer seq_nr,
charstring method := "INVITE",
template (omit) Allow allow := omit,
template (omit) Server server := omit,
template (omit) UserAgent userAgent := omit,
template (omit) charstring body := omit) := {
statusLine := ts_SIP_StatusLine(100, "Trying"),
msgHeader := ts_SIP_msgh_std(call_id, from_addr, to_addr, omit, method, seq_nr,
via,
content_length := f_ContentLength(body),
content_type := f_ContentTypeOrOmit(ts_CT_SDP, body)),
content_type := f_ContentTypeOrOmit(ts_CT_SDP, body),
allow := allow,
server := server,
userAgent := userAgent),
messageBody := body,
payload := omit
}
@ -881,6 +887,37 @@ ts_SIP_Response_Ringing(
payload := omit
}
/* 401 Unauthorized */
template (value) PDU_SIP_Response
ts_SIP_Response_Unauthorized(
template (value) CallidString call_id,
template (value) SipAddr from_addr,
template (value) SipAddr to_addr,
Via via,
template (value) WwwAuthenticate wwwAuthenticate,
integer seq_nr,
charstring method := "REGISTER",
template (omit) Allow allow := omit,
template (omit) Security_server security_server := omit,
template (omit) Server server := omit,
template (omit) Supported supported := omit,
template (omit) UserAgent userAgent := omit,
template (omit) charstring body := omit) := {
statusLine := ts_SIP_StatusLine(401, "Unauthorized"),
msgHeader := ts_SIP_msgh_std(call_id, from_addr, to_addr, omit, method, seq_nr,
via,
content_length := f_ContentLength(body),
content_type := f_ContentTypeOrOmit(ts_CT_SDP, body),
allow := allow,
security_server := security_server,
server := server,
supported := supported,
userAgent := userAgent,
wwwAuthenticate := wwwAuthenticate),
messageBody := body,
payload := omit
}
template (present) PDU_SIP_Response
tr_SIP_Response(template CallidString call_id,
template SipAddr from_addr,