WIP: Volte support for outgoing SIP registration

This commit is contained in:
Andreas Eversberg 2024-05-07 15:20:42 +02:00
parent c4b8397f70
commit 16792e3f1c
10 changed files with 487 additions and 22 deletions

View File

@ -60,6 +60,7 @@ SPEEX=@PBX_SPEEX@
SPEEXDSP=@PBX_SPEEXDSP@
SPEEX_PREPROCESS=@PBX_SPEEX_PREPROCESS@
VEVS=@PBX_VEVS@
LIBMNL=@PBX_LIBMNL@
SQLITE3=@PBX_SQLITE3@
SRTP=@PBX_SRTP@
SS7=@PBX_SS7@

View File

@ -584,6 +584,7 @@ AST_EXT_LIB_SETUP([BEANSTALK], [Beanstalk Job Queue], [beanstalk])
#FIXME
AST_EXT_LIB_SETUP([VEVS], [Vocal EVS Audio Decoder/Encoder], [bluetooth])
#AST_EXT_LIB_SETUP([VEVS], [Vocal EVS Audio Decoder/Encoder], [vocal-evs])
AST_EXT_LIB_SETUP([LIBMNL], [MNL library], [mnl])
if test "x${PBX_PJPROJECT}" != "x1" ; then
AST_EXT_LIB_SETUP([PJPROJECT], [PJPROJECT], [pjproject])
@ -2418,6 +2419,7 @@ AST_EXT_LIB_CHECK([BEANSTALK], [beanstalk], [bs_version], [beanstalk.h])
#FIXME
AST_EXT_LIB_CHECK([VEVS], [bluetooth], [ba2str], [bluetooth/bluetooth.h])
AST_EXT_LIB_CHECK([LIBMNL], [mnl], [mnl_socket_open], [libmnl/libmnl.h])
#AST_EXT_LIB_CHECK([VEVS], [vocal-evs], [vevs_enc], [vocal-evs/evs.h])
PG_CONFIG=":"

View File

@ -297,6 +297,8 @@ struct ast_sip_transport {
int allow_reload;
/*! Automatically send requests out the same transport requests have come in on */
int symmetric_transport;
/*! Local ports for client and server to use with IMS */
int ims_port_c, ims_port_s;
/*! This is a flow to another target */
int flow;
};
@ -4209,4 +4211,6 @@ const int ast_sip_hangup_sip2cause(int cause);
*/
int ast_sip_str2rc(const char *name);
extern unsigned char *volte_auth;
#endif /* _RES_PJSIP_H */

View File

@ -360,4 +360,7 @@ BEANSTALK_LIB=@BEANSTALK_LIB@
VEVS_INCLUDE=@VEVS_INCLUDE@
VEVS_LIB=@VEVS_LIB@
LIBMNL_INCLUDE=@LIBMNL_INCLUDE@
LIBMNL_LIB=@LIBMNL_LIB@
HAVE_SBIN_LAUNCHD=@PBX_LAUNCHD@

View File

@ -61,6 +61,7 @@ $(call MOD_ADD_C,res_snmp,snmp/agent.c)
$(call MOD_ADD_C,res_parking,$(wildcard parking/*.c))
$(call MOD_ADD_C,res_pjsip,$(wildcard res_pjsip/*.c))
$(call MOD_ADD_C,res_pjsip_session,$(wildcard res_pjsip_session/*.c))
$(call MOD_ADD_C,res_pjsip_outbound_registration,$(wildcard res_pjsip_outbound_registration/*.c))
$(call MOD_ADD_C,res_prometheus,$(wildcard prometheus/*.c))
$(call MOD_ADD_C,res_ari,ari/cli.c ari/config.c ari/ari_websockets.c)
$(call MOD_ADD_C,res_ari_model,ari/ari_model_validators.c)

View File

@ -4095,3 +4095,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_
.requires = "dnsmgr,res_pjproject,res_sorcery_config,res_sorcery_memory,res_sorcery_astdb",
.optional_modules = "res_geolocation,res_statsd",
);
unsigned char *volte_auth = NULL;

View File

@ -6,6 +6,7 @@
LINKER_SYMBOL_PREFIXast_copy_pj_str;
LINKER_SYMBOL_PREFIXast_copy_pj_str2;
LINKER_SYMBOL_PREFIXast_pjsip_rdata_get_endpoint;
LINKER_SYMBOL_PREFIXvolte_auth;
local:
*;
};

View File

@ -1769,6 +1769,8 @@ int ast_sip_initialize_sorcery_transport(void)
ast_sorcery_object_field_register(sorcery, "transport", "websocket_write_timeout", AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT_STR, OPT_INT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_transport, write_timeout), 1, INT_MAX);
ast_sorcery_object_field_register(sorcery, "transport", "allow_reload", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_transport, allow_reload));
ast_sorcery_object_field_register(sorcery, "transport", "symmetric_transport", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_transport, symmetric_transport));
ast_sorcery_object_field_register(sorcery, "transport", "ims_port_c", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, ims_port_c));
ast_sorcery_object_field_register(sorcery, "transport", "ims_port_s", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, ims_port_s));
ast_sip_register_endpoint_formatter(&endpoint_transport_formatter);

View File

@ -33,7 +33,9 @@
#include "asterisk/vector.h"
pj_str_t supported_digest_algorithms[] = {
{ "MD5", 3}
{ "MD5", 3},
{ "AKAv1-MD5", 9},
{ "AKAv2-MD5", 9}
};
/*!

View File

@ -17,6 +17,7 @@
*/
/*** MODULEINFO
<depend>libmnl</depend>
<depend>pjproject</depend>
<depend>res_pjsip</depend>
<use type="module">res_statsd</use>
@ -40,6 +41,7 @@
#include "res_pjsip/include/res_pjsip_private.h"
#include "asterisk/vector.h"
#include "asterisk/pbx.h"
#include "res_pjsip_outbound_registration/volte.h"
/*** DOCUMENTATION
<configInfo name="res_pjsip_outbound_registration" language="en_US">
@ -212,6 +214,9 @@
<configOption name="manual_register">
<synopsis>Perform registration only upon request over AMI interface.</synopsis>
</configOption>
<configOption name="ims_aka">
<synopsis>Perform Voice over LTE SIP registration process.</synopsis>
</configOption>
</configObject>
</configFile>
</configInfo>
@ -268,6 +273,8 @@
/* forward declarations */
static int set_outbound_initial_authentication_credentials(pjsip_regc *regc,
const struct ast_sip_auth_vector *auth_vector);
static int volte_add_outbound_initial_authorization(pjsip_tx_data *tdata, const char *fromdomain,
const struct ast_sip_auth_vector *auth_vector);
/*! \brief Some thread local storage used to determine if the running thread invoked the callback */
AST_THREADSTORAGE(register_callback_invoked);
@ -373,6 +380,28 @@ struct sip_outbound_registration {
unsigned int support_outbound;
/*! \brief Do not trigger registration automatically. */
unsigned int manual_register;
/*! \brief VoLTE support */
unsigned int ims_aka;
};
/*! \brief States of the VoLTE registration process */
enum volte_state {
/* !\brief Send first registration. */
VOLTE_STATE_REGISTER,
/* !\brief Wait for SIM keys to be provided. */
VOLTE_STATE_SIM_REQUEST,
/* !\brief SIM responded with keys. */
VOLTE_STATE_SIM_RESPONSE,
/* !\brief SIM responded with resync token. */
VOLTE_STATE_SIM_RESYNC,
/* !\brief SIM responded with failure. */
VOLTE_STATE_SIM_FAILED,
/* !\brief Send registration with authentication response. */
VOLTE_STATE_RESPONSE,
/* !\brief Send registration with resync token. */
VOLTE_STATE_RESYNC,
/* !\breif IMS registration process failed. */
VOLTE_STATE_FAILED,
};
/*! \brief Outbound registration client state information (persists for lifetime of regc) */
@ -438,6 +467,16 @@ struct sip_outbound_registration_client_state {
char *registration_name;
/*! \brief Expected time of registration lapse/expiration */
unsigned int registration_expires;
/*! \brief VoLTE support */
unsigned int ims_aka;
/*! \brief Current state of registration process */
enum volte_state volte_state;
/*! \brief Current pending respones, while waiting for USIM data */
struct registration_response *volte_response;
/*! \brief Current states related to VoLTE process */
struct volte_states volte;
/*! \brief Non-zero if we have attempted sending a REGISTER with resync */
unsigned int resync_attempted:1;
};
/*! \brief Outbound registration state information (persists for lifetime that registration should exist) */
@ -721,6 +760,76 @@ static void add_security_headers(struct sip_outbound_registration_client_state *
ao2_cleanup(reg);
}
static pj_status_t volte_registration_client(struct sip_outbound_registration_client_state *client_state,
pjsip_tx_data *tdata)
{
struct sip_outbound_registration *reg = NULL;
struct ast_sip_endpoint *endpt = NULL;
struct ast_sip_transport *transp = NULL;
int rc = -1;
reg = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "registration",
client_state->registration_name);
if (!reg) {
ast_log(LOG_ERROR, "Internal error\n");
goto out;
}
if (!reg->endpoint ||
(!(endpt = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", reg->endpoint)))) {
ast_log(LOG_ERROR, "No endpoint configured in registration config for '%s'\n",
client_state->registration_name);
goto out;
}
if (!reg->transport ||
(!(transp = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", reg->transport)))) {
ast_log(LOG_ERROR, "No transport configured in registration config for '%s'\n",
client_state->registration_name);
goto out;
}
if (!endpt->fromdomain) {
ast_log(LOG_ERROR, "No from_domain defined in endpoint config for '%s'\n", reg->endpoint);
goto out;
}
if (!transp->ims_port_c || !transp->ims_port_s) {
ast_log(LOG_ERROR, "No ims_port_c or ims_port_s defined in transport config for '%s'\n",
reg->transport);
goto out;
}
if (volte_del_authorization(tdata)) {
ast_log(LOG_ERROR, "Failed to remove authorization header.\n");
goto out;
}
if (volte_add_outbound_initial_authorization(tdata, endpt->fromdomain, &client_state->outbound_auths)) {
ast_log(LOG_ERROR, "Failed to add initial authorization header.\n");
goto out;
}
if (volte_add_sec_agree(tdata)) {
ast_log(LOG_ERROR, "Failed to add sec agree header.\n");
goto out;
}
if (volte_reset_transport(&client_state->volte)) {
ast_log(LOG_ERROR, "Failed to reset transport. Ignoring!\n");
goto out;
}
if (volte_add_security_client(&client_state->volte, tdata, transp->ims_port_c, transp->ims_port_s)) {
ast_log(LOG_ERROR, "Failed to add security client header.\n");
goto out;
}
rc = 0;
out:
ao2_cleanup(reg);
ao2_cleanup(endpt);
ao2_cleanup(transp);
return rc;
}
/*! \brief Helper function which sends a message and cleans up, if needed, on failure */
static pj_status_t registration_client_send(struct sip_outbound_registration_client_state *client_state,
pjsip_tx_data *tdata)
@ -757,6 +866,15 @@ static pj_status_t registration_client_send(struct sip_outbound_registration_cli
pjsip_regc_set_transport(client_state->client, &selector);
ast_sip_tpselector_unref(&selector);
/* Create initial IMS headers and reset transport. */
if (client_state->ims_aka && client_state->volte_state == VOLTE_STATE_REGISTER) {
if (volte_registration_client(client_state, tdata)) {
pjsip_tx_data_dec_ref(tdata);
ao2_ref(client_state, -1);
return -1;
}
}
status = pjsip_regc_send(client_state->client, tdata);
/*
@ -865,29 +983,13 @@ static int handle_client_registration(void *data)
return -1;
}
client_state->volte_state = VOLTE_STATE_REGISTER;
registration_client_send(client_state, tdata);
return 0;
}
/*! \brief Timer callback function, used just for registrations */
static void sip_outbound_registration_timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
{
struct sip_outbound_registration_client_state *client_state = entry->user_data;
entry->id = 0;
/*
* Transfer client_state reference to serializer task so the
* nominal path will not dec the client_state ref in this
* pjproject callback thread.
*/
if (ast_sip_push_task(client_state->serializer, handle_client_registration, client_state)) {
ast_log(LOG_WARNING, "Scheduled outbound registration could not be executed.\n");
ao2_ref(client_state, -1);
}
}
/*! \brief Helper function which sets up the timer to re-register in a specific amount of time */
static void schedule_registration(struct sip_outbound_registration_client_state *client_state, unsigned int seconds)
{
@ -947,6 +1049,9 @@ static int handle_client_state_destruction(void *data)
cancel_registration(client_state);
/* Cleanup IPSec translation. */
volte_cleanup_xfrm(&client_state->volte);
if (client_state->client) {
pjsip_regc_info info;
pjsip_tx_data *tdata;
@ -1018,8 +1123,23 @@ struct registration_response {
pjsip_tx_data *old_request;
/*! \brief Key for the reliable transport in use */
char transport_key[IP6ADDR_COLON_PORT_BUFLEN];
/*! \breif USIM authentication response */
uint8_t sim_res[8];
uint8_t sim_ik[16];
uint8_t sim_ck[16];
uint8_t sim_auts[14];
/*! \brief Timer for USIM reply timeout */
pj_timer_entry sim_timer;
};
/*! \brief Helper function which cancels the timer on registration response */
static void cancel_sim_timer(struct registration_response *response)
{
if (pj_timer_heap_cancel_if_active(pjsip_endpt_get_timer_heap(ast_sip_get_pjsip_endpoint()),
&response->sim_timer, response->sim_timer.id)) {
}
}
/*! \brief Registration response structure destructor */
static void registration_response_destroy(void *obj)
{
@ -1033,9 +1153,30 @@ static void registration_response_destroy(void *obj)
pjsip_tx_data_dec_ref(response->old_request);
}
if (response == response->client_state->volte_response)
response->client_state->volte_response = NULL;
cancel_sim_timer(response);
ao2_cleanup(response->client_state);
}
/*! \brief Timer callback function, used just for registrations */
static void sim_timeout_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
{
struct registration_response *response = entry->user_data;
ast_log(LOG_ERROR, "Sim did not respond, authentication failed.\n");
if (response->client_state->destroy) {
/* We have a pending deferred destruction to complete now. */
ao2_ref(response->client_state, +1);
handle_client_state_destruction(response->client_state);
}
ao2_ref(response, -1);
}
/*! \brief Helper function which determines if a response code is temporal or not */
static int sip_outbound_registration_is_temporal(unsigned int code,
struct sip_outbound_registration_client_state *client_state)
@ -1222,6 +1363,153 @@ static void save_response_fields_to_transport(struct registration_response *resp
}
}
static struct ast_sip_auth *volte_get_sip_auth(const struct ast_sip_auth_vector *auth_vector)
{
size_t auth_size = AST_VECTOR_SIZE(auth_vector);
struct ast_sip_auth *auths[auth_size], *auth = NULL;
int idx;
memset(auths, 0, sizeof(auths));
if (ast_sip_retrieve_auths(auth_vector, auths)) {
ast_log(LOG_ERROR, "No authentication vector found. Please configure authentication for IMS\n");
goto cleanup;
}
for (idx = 0; idx < auth_size; ++idx) {
if (auths[idx]->type == AST_SIP_AUTH_TYPE_IMS_AKA)
break;
}
if (idx == auth_size) {
ast_log(LOG_ERROR, "No authentication vector found with type=ims_aka. Please fix config.\n");
goto cleanup;
}
auth = auths[idx];
cleanup:
ast_sip_cleanup_auths(auths, auth_size);
return auth;
}
static int handle_volte_unauthorized(struct registration_response *response, uint8_t *out_auts)
{
struct security_server sec;
struct ast_sip_auth *auth;
pj_str_t algo;
uint8_t rand[16], autn[16], out_ik[16], out_ck[16];
int rc;
if (!(auth = volte_get_sip_auth(&response->client_state->outbound_auths)))
return -1;
switch (response->client_state->volte_state) {
case VOLTE_STATE_SIM_RESPONSE:
ast_debug(1, "Processing Authentication response from SIM\n");
/* Registration response from SIM */
memcpy(auth->ims_res, response->sim_res, 8);
memcpy(out_ik, response->sim_ik, 16);
memcpy(out_ck, response->sim_ck, 16);
response->client_state->volte_response = NULL;
rc = 0;
break;
case VOLTE_STATE_SIM_RESYNC:
ast_debug(1, "Processing resync response from SIM\n");
response->client_state->volte_response = NULL;
memcpy(out_auts, response->sim_auts, 14);
rc = -EAGAIN;
break;
case VOLTE_STATE_SIM_FAILED:
ast_debug(1, "Processing failure response from SIM\n");
response->client_state->volte_response = NULL;
rc = -EINVAL;
break;
default:
ast_debug(1, "Processing REGISTER response from IMS\n");
/* Remove existing autorization header. */
if (volte_del_authorization(response->old_request)) {
ast_log(LOG_ERROR, "Failed to remove authorization header.\n");
return -1;
}
/* Get security server */
if (volte_get_security_server(&response->client_state->volte, response->rdata, &sec)) {
ast_log(LOG_ERROR, "Failed to parse the security server header.\n");
return -1;
}
if (volte_get_auth(&response->client_state->volte, response->rdata,
(response->code == 401) ? PJSIP_H_WWW_AUTHENTICATE : PJSIP_H_PROXY_AUTHENTICATE,
&algo, rand, autn)) {
ast_log(LOG_ERROR, "Failed to parse the authenticate header.\n");
return -1;
}
if (auth->usim_ami) {
pj_time_val delay = { .sec = 1, };
ast_debug(1, "Asking SIM card via AMI to authenticate with the callenge.\n");
volte_send_authrequest(response->client_state->registration_name, &algo, rand, autn);
response->client_state->volte_response = response;
response->client_state->volte_state = VOLTE_STATE_SIM_REQUEST;
if (pjsip_endpt_schedule_timer(ast_sip_get_pjsip_endpoint(), &response->sim_timer, &delay) != PJ_SUCCESS) {
ast_log(LOG_WARNING, "Failed to schedule SIM response timer\n");
return -1;
}
return 0;
}
rc = volte_authenticate(&response->client_state->volte, auth->usim_opc, auth->usim_k,
auth->usim_sqn, rand, autn, (uint8_t *)auth->ims_res,
out_ik, out_ck, out_auts);
}
if (rc == -EAGAIN) {
if (response->client_state->resync_attempted) {
ast_log(LOG_ERROR, "SQN out of sequence again, aborting.\n");
return -1;
}
ast_log(LOG_WARNING, "SQN out of sequence, syncing.\n");
auth->ims_res_len = 0;
response->client_state->volte_state = VOLTE_STATE_RESYNC;
response->client_state->auth_attempted = 0;
response->client_state->resync_attempted = 1;
return 0;
}
if (rc) {
ast_log(LOG_ERROR, "Authentication failed.\n");
return -1;
}
auth->ims_res_len = 8;
if (volte_set_transport(&response->client_state->volte, response->old_request, &sec.alg, &sec.ealg,
out_ik, pj_strtoul(&sec.spi_c), pj_strtoul(&sec.spi_s),
pj_strtoul(&sec.port_c), pj_strtoul(&sec.port_s))) {
ast_log(LOG_ERROR, "Failed to set transport.\n");
return -1;
}
if (volte_add_security_verify(&response->client_state->volte, response->old_request)) {
ast_log(LOG_ERROR, "Failed to add security verify.\n");
return -1;
}
response->client_state->volte_state = VOLTE_STATE_RESPONSE;
return 0;
}
/*! \brief Timer callback function, used just for registrations */
static void sip_outbound_registration_timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
{
struct sip_outbound_registration_client_state *client_state = entry->user_data;
entry->id = 0;
/*
* Transfer client_state reference to serializer task so the
* nominal path will not dec the client_state ref in this
* pjproject callback thread.
*/
if (ast_sip_push_task(client_state->serializer, handle_client_registration, client_state)) {
ast_log(LOG_WARNING, "Scheduled outbound registration could not be executed.\n");
ao2_ref(client_state, -1);
}
}
/*! \brief Callback function for handling a response to a registration attempt */
static int handle_registration_response(void *data)
@ -1230,6 +1518,7 @@ static int handle_registration_response(void *data)
pjsip_regc_info info;
char server_uri[PJSIP_MAX_URL_SIZE];
char client_uri[PJSIP_MAX_URL_SIZE];
uint8_t auts[14];
if (response->client_state->status == SIP_REGISTRATION_STOPPED) {
ao2_ref(response, -1);
@ -1244,6 +1533,18 @@ static int handle_registration_response(void *data)
ast_debug(1, "Processing REGISTER response %d from server '%s' for client '%s'\n",
response->code, server_uri, client_uri);
if ((response->code == 401 || response->code == 407) && response->client_state->ims_aka) {
if (handle_volte_unauthorized(response, auts)) {
response->client_state->volte_state = VOLTE_STATE_FAILED;
goto volte_failed;
}
/* Wait for the SIM to respond. Store registration_response to client state. */
if (response->client_state->volte_state == VOLTE_STATE_SIM_REQUEST) {
response->client_state->volte_response = response;
return 0;
}
}
if (response->code == 408 || response->code == 503) {
if ((ast_sip_failover_request(response->old_request))) {
int res = registration_client_send(response->client_state, response->old_request);
@ -1303,7 +1604,14 @@ static int handle_registration_response(void *data)
return 0;
} else if (!ast_sip_create_request_with_auth(&response->client_state->outbound_auths,
response->rdata, response->old_request, &tdata)) {
response->client_state->auth_attempted = 1;
if (response->client_state->volte_state == VOLTE_STATE_RESYNC) {
if (volte_add_auts(&response->client_state->volte, tdata, auts)) {
ast_log(LOG_ERROR, "Failed to add authentication token.\n");
goto volte_failed;
}
}
if (!response->client_state->resync_attempted)
response->client_state->auth_attempted = 1;
ast_debug(1, "Sending authenticated REGISTER to server '%s' from client '%s'\n",
server_uri, client_uri);
pjsip_tx_data_add_ref(tdata);
@ -1325,8 +1633,11 @@ static int handle_registration_response(void *data)
}
/* Otherwise, fall through so the failure is processed appropriately */
}
volte_failed:
response->client_state->volte_state = VOLTE_STATE_REGISTER;
response->client_state->auth_attempted = 0;
response->client_state->resync_attempted = 0;
if (PJSIP_IS_STATUS_IN_CLASS(response->code, 200)) {
/* Check if this is in regards to registering or unregistering */
@ -1420,6 +1731,17 @@ static int handle_registration_response(void *data)
return 0;
}
static int queue_authresponse(struct sip_outbound_registration_state *state)
{
if (ast_sip_push_task(state->client_state->serializer, handle_registration_response, state->client_state->volte_response)) {
ast_log(LOG_WARNING, "Failed to pass incoming registration response to threadpool\n");
ao2_cleanup(state->client_state->volte_response);
return -1;
}
return 0;
}
/*! \brief Callback function for outbound registration client */
static void sip_outbound_registration_response_cb(struct pjsip_regc_cbparam *param)
{
@ -1434,6 +1756,12 @@ static void sip_outbound_registration_response_cb(struct pjsip_regc_cbparam *par
*callback_invoked = 1;
/* Cleanup pending ims response. */
if (client_state->volte_response) {
ao2_cleanup(client_state->volte_response);
client_state->volte_response = NULL;
}
response = ao2_alloc(sizeof(*response), registration_response_destroy);
if (!response) {
ao2_ref(client_state, -1);
@ -1447,6 +1775,7 @@ static void sip_outbound_registration_response_cb(struct pjsip_regc_cbparam *par
* pjproject callback thread.
*/
response->client_state = client_state;
pj_timer_entry_init(&response->sim_timer, 0, response, sim_timeout_cb);
ast_debug(1, "Received REGISTER response %d(%.*s)\n",
param->code, (int) param->reason.slen, param->reason.ptr);
@ -1522,6 +1851,10 @@ static void sip_outbound_registration_client_state_destroy(void *obj)
ast_statsd_log_string_va("PJSIP.registrations.state.%s", AST_STATSD_GAUGE, "-1", 1.0,
sip_outbound_registration_status_str(client_state->status));
/* In case there is an unfinished response, destroy it. */
if (client_state->volte_response)
ao2_cleanup(client_state->volte_response);
ast_taskprocessor_unreference(client_state->serializer);
ast_free(client_state->transport_name);
ast_free(client_state->registration_name);
@ -1553,6 +1886,7 @@ static struct sip_outbound_registration_state *sip_outbound_registration_state_a
state->client_state->transport_name = ast_strdup(registration->transport);
state->client_state->registration_name =
ast_strdup(ast_sorcery_object_get_id(registration));
state->client_state->ims_aka = registration->ims_aka;
ast_statsd_log_string("PJSIP.registrations.count", AST_STATSD_GAUGE, "+1", 1.0);
ast_statsd_log_string_va("PJSIP.registrations.state.%s", AST_STATSD_GAUGE, "+1", 1.0,
@ -1820,6 +2154,20 @@ cleanup:
return res;
}
/* Add intial authorization header for IMS AKA */
static int volte_add_outbound_initial_authorization(pjsip_tx_data *tdata, const char *fromdomain,
const struct ast_sip_auth_vector *auth_vector)
{
struct ast_sip_auth *auth;
if (!(auth = volte_get_sip_auth(auth_vector)))
return -1;
volte_init_authorization(tdata, fromdomain, auth->auth_user);
return 0;
}
/*! \brief Helper function that allocates a pjsip registration client and configures it */
static int sip_outbound_registration_regc_alloc(void *data)
{
@ -1993,8 +2341,12 @@ static int sip_outbound_registration_apply(const struct ast_sorcery *sorcery, vo
ast_log(LOG_ERROR, "Line support has been enabled on outbound registration '%s' without providing an endpoint\n",
ast_sorcery_object_get_id(applied));
return -1;
} else if (!ast_strlen_zero(applied->endpoint) && !applied->line) {
ast_log(LOG_ERROR, "An endpoint has been specified on outbound registration '%s' without enabling line support\n",
} else if (applied->ims_aka && ast_strlen_zero(applied->endpoint)) {
ast_log(LOG_ERROR, "IMS AKA support has been enabled on outbound registration '%s' without providing an endpoint\n",
ast_sorcery_object_get_id(applied));
return -1;
} else if (!ast_strlen_zero(applied->endpoint) && !applied->line && !applied->ims_aka) {
ast_log(LOG_ERROR, "An endpoint has been specified on outbound registration '%s' without enabling line or IMS AKA support\n",
ast_sorcery_object_get_id(applied));
return -1;
}
@ -2380,6 +2732,90 @@ static int ami_register(struct mansession *s, const struct message *m)
return 0;
}
static int ami_authresponse(struct mansession *s, const struct message *m)
{
const char *registration_name = astman_get_header(m, "Registration");
const char *res_str = astman_get_header(m, "RES");
const char *ik_str = astman_get_header(m, "IK");
const char *ck_str = astman_get_header(m, "CK");
const char *auts_str = astman_get_header(m, "AUTS");
struct sip_outbound_registration_state *state;
struct registration_response *response;
if (ast_strlen_zero(registration_name)) {
ast_log(LOG_ERROR, "SIM card responded: Registration parameter missing.\n");
astman_send_error(s, m, "Registration parameter missing");
return 0;
}
state = get_state(registration_name);
if (!state) {
ast_log(LOG_ERROR, "SIM card responded: Unable to retrieve registration entry.\n");
astman_send_error(s, m, "Unable to retrieve registration entry\n");
return 0;
}
if (!state->client_state || !state->client_state->volte_response) {
ast_debug(1, "SIM card responded: No pending AuthRequest.\n");
astman_send_error(s, m, "No pending AuthRequest\n");
ao2_ref(state, -1);
return 0;
}
response = state->client_state->volte_response;
ast_debug(1, "SIM card responded. RES=%s IK=%s CK=%s AUTS=%s\n", res_str, ik_str, ck_str, auts_str);
cancel_sim_timer(response);
if (res_str[0] && ik_str[0] && ck_str[0] && !auts_str[0]) {
if (volte_hex_to_octet_string("RES", res_str, response->sim_res, sizeof(response->sim_res))) {
ast_log(LOG_ERROR, "SIM card responded: RES value invalid.\n");
astman_send_error(s, m, "RES value invalid\n");
ao2_ref(state, -1);
return 0;
}
if (volte_hex_to_octet_string("IK", ik_str, response->sim_ik, sizeof(response->sim_ik))) {
ast_log(LOG_ERROR, "SIM card responded: IK value invalid.\n");
astman_send_error(s, m, "IK value invalid\n");
ao2_ref(state, -1);
return 0;
}
if (volte_hex_to_octet_string("CK", ck_str, response->sim_ck, sizeof(response->sim_ck))) {
ast_log(LOG_ERROR, "SIM card responded: CK value invalid.\n");
astman_send_error(s, m, "CK value invalid\n");
ao2_ref(state, -1);
return 0;
}
response->client_state->volte_state = VOLTE_STATE_SIM_RESPONSE;
} else if (!res_str[0] && !ik_str[0] && !ck_str[0] && auts_str[0]) {
if (volte_hex_to_octet_string("AUTS", auts_str, response->sim_auts, sizeof(response->sim_auts))) {
ast_log(LOG_ERROR, "SIM card responded: AUTS value invalid.\n");
astman_send_error(s, m, "AUTS value invalid\n");
ao2_ref(state, -1);
return 0;
}
response->client_state->volte_state = VOLTE_STATE_SIM_RESYNC;
} else if (!res_str[0] && !ik_str[0] && !ck_str[0] && !auts_str[0]) {
response->client_state->volte_state = VOLTE_STATE_SIM_FAILED;
} else {
ast_log(LOG_ERROR, "SIM card responded: Missing or too many AuthResponse values.\n");
astman_send_error(s, m, "Missing or too many AuthResponse values\n");
ao2_ref(state, -1);
return 0;
}
/* We need to serialize the unregister and register so they need
* to be queued as separate tasks.
*/
if (queue_authresponse(state)) {
astman_send_ack(s, m, "Failed to queue AuthResponse");
} else {
astman_send_ack(s, m, "AuthResponse sent");
}
ao2_ref(state, -1);
return 0;
}
struct sip_ami_outbound {
struct ast_sip_ami *ami;
int registered;
@ -2723,6 +3159,7 @@ static int unload_module(void)
ast_manager_unregister("PJSIPShowRegistrationsOutbound");
ast_manager_unregister("PJSIPUnregister");
ast_manager_unregister("PJSIPRegister");
ast_manager_unregister("AuthResponse");
ast_cli_unregister_multiple(cli_outbound_registration, ARRAY_LEN(cli_outbound_registration));
ast_sip_unregister_cli_formatter(cli_formatter);
@ -2758,6 +3195,8 @@ static int unload_module(void)
ao2_cleanup(shutdown_group);
shutdown_group = NULL;
g_volte_exit();
return 0;
}
@ -2814,6 +3253,7 @@ static int load_module(void)
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "line", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, line));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "endpoint", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, endpoint));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "manual_register", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, manual_register));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "ims_aka", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, ims_aka));
/*
* Register sorcery observers.
@ -2849,10 +3289,17 @@ static int load_module(void)
ast_sip_register_cli_formatter(cli_formatter);
ast_cli_register_multiple(cli_outbound_registration, ARRAY_LEN(cli_outbound_registration));
/* Init VoLTE process. */
if (g_volte_init()) {
unload_module();
return AST_MODULE_LOAD_DECLINE;
}
/* Register AMI actions. */
ast_manager_register_xml("PJSIPUnregister", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, ami_unregister);
ast_manager_register_xml("PJSIPRegister", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, ami_register);
ast_manager_register_xml("PJSIPShowRegistrationsOutbound", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, ami_show_outbound_registrations);
ast_manager_register_xml_core("AuthResponse", 0, ami_authresponse);
/* Clear any previous statsd gauges in case we weren't shutdown cleanly */
ast_statsd_log("PJSIP.registrations.count", AST_STATSD_GAUGE, 0);