/*** MODULEINFO
<depend>pjproject</depend>
<depend>res_pjsip</depend>
+ <use type="module">res_statsd</use>
<support_level>core</support_level>
***/
#include "asterisk/cli.h"
#include "asterisk/stasis_system.h"
#include "asterisk/threadstorage.h"
+#include "asterisk/threadpool.h"
+#include "asterisk/statsd.h"
#include "res_pjsip/include/res_pjsip_private.h"
/*** DOCUMENTATION
<synopsis>Maximum number of registration attempts.</synopsis>
</configOption>
<configOption name="outbound_auth" default="">
- <synopsis>Authentication object to be used for outbound registrations.</synopsis>
+ <synopsis>Authentication object(s) to be used for outbound registrations.</synopsis>
+ <description><para>
+ This is a comma-delimited list of <replaceable>auth</replaceable>
+ sections defined in <filename>pjsip.conf</filename> used to respond
+ to outbound authentication challenges.</para>
+ <note><para>
+ Using the same auth section for inbound and outbound
+ authentication is not recommended. There is a difference in
+ meaning for an empty realm setting between inbound and outbound
+ authentication uses. See the auth realm description for details.
+ </para></note>
+ </description>
</configOption>
<configOption name="outbound_proxy" default="">
- <synopsis>Outbound Proxy used to send registrations</synopsis>
+ <synopsis>Full SIP URI of the outbound proxy used to send registrations</synopsis>
</configOption>
<configOption name="retry_interval" default="60">
<synopsis>Interval in seconds between retries if outbound registration is unsuccessful</synopsis>
buggy registrars.
</para></description>
</configOption>
+ <configOption name="fatal_retry_interval" default="0">
+ <synopsis>Interval used when receiving a Fatal response.</synopsis>
+ <description><para>
+ If a fatal response is received, chan_pjsip will wait
+ <replaceable>fatal_retry_interval</replaceable> seconds before
+ attempting registration again. If 0 is specified, chan_pjsip will not
+ retry after receiving a fatal (non-temporary 4xx, 5xx, 6xx) response.
+ Setting this to a non-zero value may go against a "SHOULD NOT" in RFC3261,
+ but can be used to work around buggy registrars.</para>
+ <note><para>if also set the <replaceable>forbidden_retry_interval</replaceable>
+ takes precedence over this one when a 403 is received.
+ Also, if <replaceable>auth_rejection_permanent</replaceable> equals 'yes' then
+ a 401 and 407 become subject to this retry interval.</para></note>
+ </description>
+ </configOption>
<configOption name="server_uri">
<synopsis>SIP URI of the server to register against</synopsis>
<description><para>
<syntax>
<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
<parameter name="Registration" required="true">
- <para>The outbound registration to unregister.</para>
+ <para>The outbound registration to unregister or '*all' to unregister them all.</para>
</parameter>
</syntax>
<description>
<para>
- Unregisters the specified outbound registration and stops future registration attempts.
+ Unregisters the specified (or all) outbound registration(s) and stops future registration attempts.
Call PJSIPRegister to start registration and schedule re-registrations according to configuration.
</para>
</description>
<syntax>
<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
<parameter name="Registration" required="true">
- <para>The outbound registration to register.</para>
+ <para>The outbound registration to register or '*all' to register them all.</para>
</parameter>
</syntax>
<description>
<para>
- Unregisters the specified outbound registration then starts registration and schedules re-registrations
+ Unregisters the specified (or all) outbound registration(s) then starts registration and schedules re-registrations
according to configuration.
- future registrations.
</para>
</description>
</manager>
SIP_REGISTRATION_REJECTED_TEMPORARY,
/*! \brief Registration was rejected, permanently */
SIP_REGISTRATION_REJECTED_PERMANENT,
+ /*! \brief Registration is stopping. */
+ SIP_REGISTRATION_STOPPING,
/*! \brief Registration has been stopped */
SIP_REGISTRATION_STOPPED,
};
-static const char *sip_outbound_registration_status_str[] = {
- [SIP_REGISTRATION_UNREGISTERED] = "Unregistered",
- [SIP_REGISTRATION_REGISTERED] = "Registered",
- [SIP_REGISTRATION_REJECTED_TEMPORARY] = "Rejected",
- [SIP_REGISTRATION_REJECTED_PERMANENT] = "Rejected",
- [SIP_REGISTRATION_STOPPED] = "Stopped",
-};
+/*!
+ * \internal
+ * \brief Convert the internal registration state to an external status string.
+ * \since 13.5.0
+ *
+ * \param state Current outbound registration state.
+ *
+ * \return External registration status string.
+ */
+static const char *sip_outbound_registration_status_str(enum sip_outbound_registration_status state)
+{
+ const char *str;
+
+ str = "Unregistered";
+ switch (state) {
+ case SIP_REGISTRATION_STOPPING:
+ case SIP_REGISTRATION_STOPPED:
+ case SIP_REGISTRATION_UNREGISTERED:
+ break;
+ case SIP_REGISTRATION_REGISTERED:
+ str = "Registered";
+ break;
+ case SIP_REGISTRATION_REJECTED_TEMPORARY:
+ case SIP_REGISTRATION_REJECTED_PERMANENT:
+ str = "Rejected";
+ break;
+ }
+ return str;
+}
/*! \brief Outbound registration information */
struct sip_outbound_registration {
unsigned int retry_interval;
/*! \brief Interval at which retries should occur for permanent responses */
unsigned int forbidden_retry_interval;
+ /*! \brief Interval at which retries should occur for all permanent responses */
+ unsigned int fatal_retry_interval;
/*! \brief Treat authentication challenges that we cannot handle as permanent failures */
unsigned int auth_rejection_permanent;
/*! \brief Maximum number of retries permitted */
/*! \brief Outbound registration client state information (persists for lifetime of regc) */
struct sip_outbound_registration_client_state {
- /*! \brief Current status of this registration */
+ /*! \brief Current state of this registration */
enum sip_outbound_registration_status status;
- /*! \brief Outbound registration client */
+ /*!
+ * \brief Outbound registration client
+ * \note May only be accessed within the serializer thread
+ * because it might get destroyed and set to NULL for
+ * module unload.
+ */
pjsip_regc *client;
/*! \brief Timer entry for retrying on temporal responses */
pj_timer_entry timer;
unsigned int retry_interval;
/*! \brief Interval at which retries should occur for permanent responses */
unsigned int forbidden_retry_interval;
+ /*! \brief Interval at which retries should occur for all permanent responses */
+ unsigned int fatal_retry_interval;
/*! \brief Treat authentication challenges that we cannot handle as permanent failures */
unsigned int auth_rejection_permanent;
/*! \brief Determines whether SIP Path support should be advertised */
unsigned int support_path;
+ /*! CSeq number of last sent auth request. */
+ unsigned int auth_cseq;
/*! \brief Serializer for stuff and things */
struct ast_taskprocessor *serializer;
/*! \brief Configured authentication credentials */
unsigned int destroy:1;
/*! \brief Non-zero if we have attempted sending a REGISTER with authentication */
unsigned int auth_attempted:1;
+ /*! \brief The name of the transport to be used for the registration */
+ char *transport_name;
+ /*! \brief The name of the registration sorcery object */
+ char *registration_name;
};
/*! \brief Outbound registration state information (persists for lifetime that registration should exist) */
struct sip_outbound_registration_client_state *client_state;
};
+/*! Time needs to be long enough for a transaction to timeout if nothing replies. */
+#define MAX_UNLOAD_TIMEOUT_TIME 35 /* Seconds */
+
+/*! Shutdown group to monitor sip_outbound_registration_client_state serializers. */
+static struct ast_serializer_shutdown_group *shutdown_group;
+
/*! \brief Default number of state container buckets */
#define DEFAULT_STATE_BUCKETS 53
static AO2_GLOBAL_OBJ_STATIC(current_states);
+/*! subscription id for network change events */
+static struct stasis_subscription *network_change_sub;
+
/*! \brief hashing function for state objects */
static int registration_state_hash(const void *obj, const int flags)
{
static struct sip_outbound_registration_state *get_state(const char *id)
{
- RAII_VAR(struct ao2_container *, states,
- ao2_global_obj_ref(current_states), ao2_cleanup);
- return states ? ao2_find(states, id, OBJ_SEARCH_KEY) : NULL;
+ struct sip_outbound_registration_state *state = NULL;
+ struct ao2_container *states;
+
+ states = ao2_global_obj_ref(current_states);
+ if (states) {
+ state = ao2_find(states, id, OBJ_SEARCH_KEY);
+ ao2_ref(states, -1);
+ }
+ return state;
}
static struct ao2_container *get_registrations(void)
struct sip_outbound_registration_state *state = obj;
pjsip_param *line = arg;
- return !pj_strcmp2(&line->value, state->client_state->line) ? CMP_MATCH | CMP_STOP : 0;
+ return !pj_strcmp2(&line->value, state->client_state->line) ? CMP_MATCH : 0;
}
static struct pjsip_param *get_uri_option_line(const void *uri)
{
pj_status_t status;
int *callback_invoked;
+ pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
callback_invoked = ast_threadstorage_get(®ister_callback_invoked, sizeof(int));
if (!callback_invoked) {
+ pjsip_tx_data_dec_ref(tdata);
return PJ_ENOMEM;
}
*callback_invoked = 0;
/* Due to the message going out the callback may now be invoked, so bump the count */
ao2_ref(client_state, +1);
+ /*
+ * Set the transport in case transports were reloaded.
+ * When pjproject removes the extraneous error messages produced,
+ * we can check status and only set the transport and resend if there was an error
+ */
+ ast_sip_set_tpselector_from_transport_name(client_state->transport_name, &selector);
+ pjsip_regc_set_transport(client_state->client, &selector);
status = pjsip_regc_send(client_state->client, tdata);
/* If the attempt to send the message failed and the callback was not invoked we need to
{
RAII_VAR(struct sip_outbound_registration_client_state *, client_state, data, ao2_cleanup);
pjsip_tx_data *tdata;
- pjsip_regc_info info;
- char server_uri[PJSIP_MAX_URL_SIZE];
- char client_uri[PJSIP_MAX_URL_SIZE];
if (client_state->status == SIP_REGISTRATION_STOPPED
|| pjsip_regc_register(client_state->client, PJ_FALSE, &tdata) != PJ_SUCCESS) {
return 0;
}
- pjsip_regc_get_info(client_state->client, &info);
- ast_copy_pj_str(server_uri, &info.server_uri, sizeof(server_uri));
- ast_copy_pj_str(client_uri, &info.client_uri, sizeof(client_uri));
- ast_debug(3, "REGISTER attempt %u to '%s' with client '%s'\n",
- client_state->retries + 1, server_uri, client_uri);
+ if (DEBUG_ATLEAST(1)) {
+ pjsip_regc_info info;
+
+ pjsip_regc_get_info(client_state->client, &info);
+ ast_log(LOG_DEBUG, "Outbound REGISTER attempt %u to '%.*s' with client '%.*s'\n",
+ client_state->retries + 1,
+ (int) info.server_uri.slen, info.server_uri.ptr,
+ (int) info.client_uri.slen, info.client_uri.ptr);
+ }
if (client_state->support_path) {
pjsip_supported_hdr *hdr;
/* insert a new Supported header */
hdr = pjsip_supported_hdr_create(tdata->pool);
if (!hdr) {
+ pjsip_tx_data_dec_ref(tdata);
return -1;
}
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;
- pjsip_regc_info info;
- pjsip_regc_get_info(client_state->client, &info);
- ast_debug(1, "Attempting scheduled outbound registration attempt to server '%.*s' from client '%.*s'\n",
- (int) info.server_uri.slen, info.server_uri.ptr,
- (int) info.client_uri.slen, info.client_uri.ptr);
+ entry->id = 0;
- ao2_ref(client_state, +1);
+ /*
+ * 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 to server '%.*s' from client '%.*s' could not be executed\n",
- (int) info.server_uri.slen, info.server_uri.ptr,
- (int) info.client_uri.slen, info.client_uri.ptr);
+ ast_log(LOG_WARNING, "Scheduled outbound registration could not be executed.\n");
ao2_ref(client_state, -1);
}
- ao2_ref(client_state, -1);
-
- entry->id = 0;
}
/*! \brief Helper function which sets up the timer to re-register in a specific amount of time */
}
}
+static void update_client_state_status(struct sip_outbound_registration_client_state *client_state, enum sip_outbound_registration_status status)
+{
+ const char *status_old;
+ const char *status_new;
+
+ if (client_state->status == status) {
+ /* Status state did not change at all. */
+ return;
+ }
+
+ status_old = sip_outbound_registration_status_str(client_state->status);
+ status_new = sip_outbound_registration_status_str(status);
+ client_state->status = status;
+
+ if (!strcmp(status_old, status_new)) {
+ /*
+ * The internal status state may have changed but the status
+ * state we tell the world did not change at all.
+ */
+ return;
+ }
+
+ ast_statsd_log_string_va("PJSIP.registrations.state.%s", AST_STATSD_GAUGE, "-1", 1.0,
+ status_old);
+ ast_statsd_log_string_va("PJSIP.registrations.state.%s", AST_STATSD_GAUGE, "+1", 1.0,
+ status_new);
+}
+
/*! \brief Callback function for unregistering (potentially) and destroying state */
static int handle_client_state_destruction(void *data)
{
- RAII_VAR(struct sip_outbound_registration_client_state *, client_state, data, ao2_cleanup);
+ struct sip_outbound_registration_client_state *client_state = data;
cancel_registration(client_state);
if (client_state->client) {
pjsip_regc_info info;
+ pjsip_tx_data *tdata;
pjsip_regc_get_info(client_state->client, &info);
if (info.is_busy == PJ_TRUE) {
/* If a client transaction is in progress we defer until it is complete */
+ ast_debug(1,
+ "Registration transaction is busy with server '%.*s' from client '%.*s'.\n",
+ (int) info.server_uri.slen, info.server_uri.ptr,
+ (int) info.client_uri.slen, info.client_uri.ptr);
client_state->destroy = 1;
+ ao2_ref(client_state, -1);
return 0;
}
- if (client_state->status != SIP_REGISTRATION_UNREGISTERED
- && client_state->status != SIP_REGISTRATION_REJECTED_PERMANENT) {
- pjsip_tx_data *tdata;
+ switch (client_state->status) {
+ case SIP_REGISTRATION_UNREGISTERED:
+ break;
+ case SIP_REGISTRATION_REGISTERED:
+ ast_debug(1,
+ "Trying to unregister with server '%.*s' from client '%.*s' before destruction.\n",
+ (int) info.server_uri.slen, info.server_uri.ptr,
+ (int) info.client_uri.slen, info.client_uri.ptr);
- if (pjsip_regc_unregister(client_state->client, &tdata) == PJ_SUCCESS) {
- pjsip_regc_send(client_state->client, tdata);
+ update_client_state_status(client_state, SIP_REGISTRATION_STOPPING);
+ client_state->destroy = 1;
+ if (pjsip_regc_unregister(client_state->client, &tdata) == PJ_SUCCESS
+ && registration_client_send(client_state, tdata) == PJ_SUCCESS) {
+ ao2_ref(client_state, -1);
+ return 0;
}
+ break;
+ case SIP_REGISTRATION_REJECTED_TEMPORARY:
+ case SIP_REGISTRATION_REJECTED_PERMANENT:
+ case SIP_REGISTRATION_STOPPING:
+ case SIP_REGISTRATION_STOPPED:
+ break;
}
pjsip_regc_destroy(client_state->client);
+ client_state->client = NULL;
}
- client_state->status = SIP_REGISTRATION_STOPPED;
+ update_client_state_status(client_state, SIP_REGISTRATION_STOPPED);
ast_sip_auth_vector_destroy(&client_state->outbound_auths);
+ ao2_ref(client_state, -1);
return 0;
}
static void schedule_retry(struct registration_response *response, unsigned int interval,
const char *server_uri, const char *client_uri)
{
- response->client_state->status = SIP_REGISTRATION_REJECTED_TEMPORARY;
+ update_client_state_status(response->client_state, SIP_REGISTRATION_REJECTED_TEMPORARY);
schedule_registration(response->client_state, interval);
if (response->rdata) {
}
}
+static int reregister_immediately_cb(void *obj)
+{
+ struct sip_outbound_registration_state *state = obj;
+
+ if (state->client_state->status != SIP_REGISTRATION_REGISTERED) {
+ ao2_ref(state, -1);
+ return 0;
+ }
+
+ if (DEBUG_ATLEAST(1)) {
+ pjsip_regc_info info;
+
+ pjsip_regc_get_info(state->client_state->client, &info);
+ ast_log(LOG_DEBUG,
+ "Outbound registration transport to server '%.*s' from client '%.*s' shutdown\n",
+ (int) info.server_uri.slen, info.server_uri.ptr,
+ (int) info.client_uri.slen, info.client_uri.ptr);
+ }
+
+ cancel_registration(state->client_state);
+
+ ao2_ref(state->client_state, +1);
+ handle_client_registration(state->client_state);
+
+ ao2_ref(state, -1);
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief The reliable transport we registered using has shutdown.
+ * \since 13.18.0
+ *
+ * \param obj What is needed to initiate a reregister attempt.
+ *
+ * \return Nothing
+ */
+static void registration_transport_shutdown_cb(void *obj)
+{
+ const char *registration_name = obj;
+ struct sip_outbound_registration_state *state;
+
+ state = get_state(registration_name);
+ if (!state) {
+ /* Registration no longer exists or shutting down. */
+ return;
+ }
+ if (ast_sip_push_task(state->client_state->serializer, reregister_immediately_cb, state)) {
+ ao2_ref(state, -1);
+ }
+}
+
+static void registration_transport_monitor_setup(pjsip_transport *transport, const char *registration_name)
+{
+ char *monitor;
+
+ if (!PJSIP_TRANSPORT_IS_RELIABLE(transport)) {
+ return;
+ }
+ monitor = ao2_alloc_options(strlen(registration_name) + 1, NULL,
+ AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!monitor) {
+ return;
+ }
+ strcpy(monitor, registration_name);/* Safe */
+
+ /*
+ * We'll ignore if the transport has already been shutdown before we
+ * register the monitor. We might get into a message spamming infinite
+ * loop of registration, shutdown, reregistration...
+ */
+ ast_sip_transport_monitor_register(transport, registration_transport_shutdown_cb,
+ monitor);
+ ao2_ref(monitor, -1);
+}
+
/*! \brief Callback function for handling a response to a registration attempt */
static int handle_registration_response(void *data)
{
- RAII_VAR(struct registration_response *, response, data, ao2_cleanup);
+ struct registration_response *response = data;
pjsip_regc_info info;
char server_uri[PJSIP_MAX_URL_SIZE];
char client_uri[PJSIP_MAX_URL_SIZE];
- pjsip_regc_get_info(response->client_state->client, &info);
- ast_copy_pj_str(server_uri, &info.server_uri, sizeof(server_uri));
- ast_copy_pj_str(client_uri, &info.client_uri, sizeof(client_uri));
-
if (response->client_state->status == SIP_REGISTRATION_STOPPED) {
- ast_debug(1, "Not handling registration response from server '%s' for client '%s'. Registration already stopped\n",
- server_uri, client_uri);
+ ao2_ref(response, -1);
return 0;
}
+ pjsip_regc_get_info(response->client_state->client, &info);
+ ast_copy_pj_str(server_uri, &info.server_uri, sizeof(server_uri));
+ ast_copy_pj_str(client_uri, &info.client_uri, sizeof(client_uri));
+
ast_debug(1, "Processing REGISTER response %d from server '%s' for client '%s'\n",
response->code, server_uri, client_uri);
- if (!response->client_state->auth_attempted &&
- (response->code == 401 || response->code == 407)) {
+ if ((response->code == 401 || response->code == 407)
+ && (!response->client_state->auth_attempted
+ || response->rdata->msg_info.cseq->cseq != response->client_state->auth_cseq)) {
+ int res;
+ pjsip_cseq_hdr *cseq_hdr;
pjsip_tx_data *tdata;
+
if (!ast_sip_create_request_with_auth(&response->client_state->outbound_auths,
response->rdata, response->old_request, &tdata)) {
response->client_state->auth_attempted = 1;
ast_debug(1, "Sending authenticated REGISTER to server '%s' from client '%s'\n",
server_uri, client_uri);
- if (registration_client_send(response->client_state, tdata) != PJ_SUCCESS) {
- response->client_state->auth_attempted = 0;
+ pjsip_tx_data_add_ref(tdata);
+ res = registration_client_send(response->client_state, tdata);
+
+ /* Save the cseq that actually got sent. */
+ cseq_hdr = (pjsip_cseq_hdr *) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ,
+ NULL);
+ response->client_state->auth_cseq = cseq_hdr->cseq;
+ pjsip_tx_data_dec_ref(tdata);
+ if (res == PJ_SUCCESS) {
+ ao2_ref(response, -1);
+ return 0;
}
- return 0;
} else {
ast_log(LOG_WARNING, "Failed to create authenticated REGISTER request to server '%s' from client '%s'\n",
server_uri, client_uri);
if (PJSIP_IS_STATUS_IN_CLASS(response->code, 200)) {
/* Check if this is in regards to registering or unregistering */
if (response->expiration) {
+ int next_registration_round;
+
/* If the registration went fine simply reschedule registration for the future */
ast_debug(1, "Outbound registration to '%s' with client '%s' successful\n", server_uri, client_uri);
- response->client_state->status = SIP_REGISTRATION_REGISTERED;
+ update_client_state_status(response->client_state, SIP_REGISTRATION_REGISTERED);
response->client_state->retries = 0;
- schedule_registration(response->client_state, response->expiration - REREGISTER_BUFFER_TIME);
+ next_registration_round = response->expiration - REREGISTER_BUFFER_TIME;
+ if (next_registration_round < 0) {
+ /* Re-register immediately. */
+ next_registration_round = 0;
+ }
+ schedule_registration(response->client_state, next_registration_round);
+
+ /* See if we should monitor for transport shutdown */
+ registration_transport_monitor_setup(response->rdata->tp_info.transport,
+ response->client_state->registration_name);
} else {
ast_debug(1, "Outbound unregistration to '%s' with client '%s' successful\n", server_uri, client_uri);
- response->client_state->status = SIP_REGISTRATION_UNREGISTERED;
+ update_client_state_status(response->client_state, SIP_REGISTRATION_UNREGISTERED);
+ ast_sip_transport_monitor_unregister(response->rdata->tp_info.transport,
+ registration_transport_shutdown_cb);
}
+ } else if (response->client_state->destroy) {
+ /* We need to deal with the pending destruction instead. */
} else if (response->retry_after) {
/* If we have been instructed to retry after a period of time, schedule it as such */
schedule_retry(response, response->retry_after, server_uri, client_uri);
&& sip_outbound_registration_is_temporal(response->code, response->client_state)) {
if (response->client_state->retries == response->client_state->max_retries) {
/* If we received enough temporal responses to exceed our maximum give up permanently */
- response->client_state->status = SIP_REGISTRATION_REJECTED_PERMANENT;
+ update_client_state_status(response->client_state, SIP_REGISTRATION_REJECTED_PERMANENT);
ast_log(LOG_WARNING, "Maximum retries reached when attempting outbound registration to '%s' with client '%s', stopping registration attempt\n",
server_uri, client_uri);
} else {
&& response->client_state->forbidden_retry_interval
&& response->client_state->retries < response->client_state->max_retries) {
/* A forbidden response retry interval is configured and there are retries remaining */
- response->client_state->status = SIP_REGISTRATION_REJECTED_TEMPORARY;
+ update_client_state_status(response->client_state, SIP_REGISTRATION_REJECTED_TEMPORARY);
response->client_state->retries++;
schedule_registration(response->client_state, response->client_state->forbidden_retry_interval);
ast_log(LOG_WARNING, "403 Forbidden fatal response received from '%s' on registration attempt to '%s', retrying in '%u' seconds\n",
server_uri, client_uri, response->client_state->forbidden_retry_interval);
+ } else if (response->client_state->fatal_retry_interval
+ && response->client_state->retries < response->client_state->max_retries) {
+ /* Some kind of fatal failure response received, so retry according to configured interval */
+ update_client_state_status(response->client_state, SIP_REGISTRATION_REJECTED_TEMPORARY);
+ response->client_state->retries++;
+ schedule_registration(response->client_state, response->client_state->fatal_retry_interval);
+ ast_log(LOG_WARNING, "'%d' fatal response received from '%s' on registration attempt to '%s', retrying in '%u' seconds\n",
+ response->code, server_uri, client_uri, response->client_state->fatal_retry_interval);
} else {
/* Finally if there's no hope of registering give up */
- response->client_state->status = SIP_REGISTRATION_REJECTED_PERMANENT;
+ update_client_state_status(response->client_state, SIP_REGISTRATION_REJECTED_PERMANENT);
if (response->rdata) {
ast_log(LOG_WARNING, "Fatal response '%d' received from '%s' on registration attempt to '%s', stopping outbound registration\n",
response->code, server_uri, client_uri);
}
ast_system_publish_registry("PJSIP", client_uri, server_uri,
- sip_outbound_registration_status_str[response->client_state->status], NULL);
+ sip_outbound_registration_status_str(response->client_state->status), NULL);
- /* If deferred destruction is in use see if we need to destroy now */
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);
return 0;
}
/*! \brief Callback function for outbound registration client */
static void sip_outbound_registration_response_cb(struct pjsip_regc_cbparam *param)
{
- RAII_VAR(struct sip_outbound_registration_client_state *, client_state, param->token, ao2_cleanup);
+ struct sip_outbound_registration_client_state *client_state = param->token;
struct registration_response *response;
- pjsip_regc_info info;
int *callback_invoked;
callback_invoked = ast_threadstorage_get(®ister_callback_invoked, sizeof(int));
response = ao2_alloc(sizeof(*response), registration_response_destroy);
if (!response) {
+ ao2_ref(client_state, -1);
return;
}
response->code = param->code;
response->expiration = param->expiration;
+ /*
+ * Transfer client_state reference to response so the
+ * nominal path will not dec the client_state ref in this
+ * pjproject callback thread.
+ */
response->client_state = client_state;
- ao2_ref(response->client_state, +1);
- pjsip_regc_get_info(client_state->client, &info);
- ast_debug(1, "Received REGISTER response %d(%.*s) from server '%.*s' for client '%.*s\n",
- param->code, (int) param->reason.slen, param->reason.ptr,
- (int) info.server_uri.slen, info.server_uri.ptr,
- (int) info.client_uri.slen, info.client_uri.ptr);
+ ast_debug(1, "Received REGISTER response %d(%.*s)\n",
+ param->code, (int) param->reason.slen, param->reason.ptr);
if (param->rdata) {
struct pjsip_retry_after_hdr *retry_after;
pjsip_rx_data_clone(param->rdata, 0, &response->rdata);
}
+ /*
+ * Transfer response reference to serializer task so the
+ * nominal path will not dec the response ref in this
+ * pjproject callback thread.
+ */
if (ast_sip_push_task(client_state->serializer, handle_registration_response, response)) {
ast_log(LOG_WARNING, "Failed to pass incoming registration response to threadpool\n");
ao2_cleanup(response);
struct sip_outbound_registration_state *state = obj;
ast_debug(3, "Destroying registration state for registration to server '%s' from client '%s'\n",
- state->registration->server_uri, state->registration->client_uri);
-
+ state->registration ? state->registration->server_uri : "",
+ state->registration ? state->registration->client_uri : "");
ao2_cleanup(state->registration);
if (!state->client_state) {
{
struct sip_outbound_registration_client_state *client_state = obj;
+ 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,
+ sip_outbound_registration_status_str(client_state->status));
+
ast_taskprocessor_unreference(client_state->serializer);
+ ast_free(client_state->transport_name);
+ ast_free(client_state->registration_name);
}
/*! \brief Allocator function for registration state */
static struct sip_outbound_registration_state *sip_outbound_registration_state_alloc(struct sip_outbound_registration *registration)
{
struct sip_outbound_registration_state *state;
+ char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
state = ao2_alloc(sizeof(*state), sip_outbound_registration_state_destroy);
if (!state) {
return NULL;
}
- state->client_state->serializer = ast_sip_create_serializer();
+ state->client_state->status = SIP_REGISTRATION_UNREGISTERED;
+ state->client_state->timer.user_data = state->client_state;
+ state->client_state->timer.cb = sip_outbound_registration_timer_cb;
+ state->client_state->transport_name = ast_strdup(registration->transport);
+ state->client_state->registration_name =
+ ast_strdup(ast_sorcery_object_get_id(registration));
+
+ 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,
+ sip_outbound_registration_status_str(state->client_state->status));
+
+ if (!state->client_state->transport_name
+ || !state->client_state->registration_name) {
+ ao2_cleanup(state);
+ return NULL;
+ }
+
+ /* Create name with seq number appended. */
+ ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/outreg/%s",
+ ast_sorcery_object_get_id(registration));
+
+ state->client_state->serializer = ast_sip_create_serializer_group(tps_name,
+ shutdown_group);
if (!state->client_state->serializer) {
ao2_cleanup(state);
return NULL;
}
- state->client_state->status = SIP_REGISTRATION_UNREGISTERED;
- state->client_state->timer.user_data = state->client_state;
- state->client_state->timer.cb = sip_outbound_registration_timer_cb;
state->registration = ao2_bump(registration);
return state;
pj_str_t tmp, local_addr;
pjsip_uri *uri;
pjsip_sip_uri *sip_uri;
- pjsip_transport_type_e type = PJSIP_TRANSPORT_UNSPECIFIED;
+ pjsip_transport_type_e type;
int local_port;
pj_strdup_with_null(pool, &tmp, target);
sip_uri = pjsip_uri_get_uri(uri);
+ type = pjsip_transport_get_type_from_name(&sip_uri->transport_param);
if (PJSIP_URI_SCHEME_IS_SIPS(sip_uri)) {
- type = PJSIP_TRANSPORT_TLS;
+ if (type == PJSIP_TRANSPORT_UNSPECIFIED
+ || !(pjsip_transport_get_flag_from_type(type) & PJSIP_TRANSPORT_SECURE)) {
+ type = PJSIP_TRANSPORT_TLS;
+ }
} else if (!sip_uri->transport_param.slen) {
type = PJSIP_TRANSPORT_UDP;
- } else {
- type = pjsip_transport_get_type_from_name(&sip_uri->transport_param);
- }
-
- if (type == PJSIP_TRANSPORT_UNSPECIFIED) {
+ } else if (type == PJSIP_TRANSPORT_UNSPECIFIED) {
return -1;
}
if (pj_strchr(&sip_uri->host, ':')) {
- type = (pjsip_transport_type_e)(((int)type) + PJSIP_TRANSPORT_IPV6);
+ type |= PJSIP_TRANSPORT_IPV6;
}
if (pjsip_tpmgr_find_local_addr(pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()),
}
if (!pj_strchr(&sip_uri->host, ':') && pj_strchr(&local_addr, ':')) {
- type = (pjsip_transport_type_e)(((int)type) + PJSIP_TRANSPORT_IPV6);
+ type |= PJSIP_TRANSPORT_IPV6;
}
contact->ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
contact->slen = pj_ansi_snprintf(contact->ptr, PJSIP_MAX_URL_SIZE,
"<%s:%s@%s%.*s%s:%d%s%s%s%s>",
- (pjsip_transport_get_flag_from_type(type) & PJSIP_TRANSPORT_SECURE) ? "sips" : "sip",
+ ((pjsip_transport_get_flag_from_type(type) & PJSIP_TRANSPORT_SECURE) && PJSIP_URI_SCHEME_IS_SIPS(uri)) ? "sips" : "sip",
user,
(type & PJSIP_TRANSPORT_IPV6) ? "[" : "",
(int)local_addr.slen,
return -1;
}
- pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
-
- if (!ast_strlen_zero(registration->transport)) {
- RAII_VAR(struct ast_sip_transport *, transport, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", registration->transport), ao2_cleanup);
-
- if (!transport || !transport->state) {
- ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport '%s' "
- " for outbound registration", registration->transport);
- return -1;
- }
-
- if (transport->state->transport) {
- selector.type = PJSIP_TPSELECTOR_TRANSPORT;
- selector.u.transport = transport->state->transport;
- } else if (transport->state->factory) {
- selector.type = PJSIP_TPSELECTOR_LISTENER;
- selector.u.listener = transport->state->factory;
- } else {
+ if (!ast_strlen_zero(registration->outbound_proxy)) {
+ pj_strdup2_with_null(pool, &tmp, registration->outbound_proxy);
+ uri = pjsip_parse_uri(pool, tmp.ptr, tmp.slen, 0);
+ if (!uri) {
+ ast_log(LOG_ERROR, "Invalid outbound proxy URI '%s' specified on outbound registration '%s'\n",
+ registration->outbound_proxy, ast_sorcery_object_get_id(registration));
+ pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
return -1;
}
}
- if (!state->client_state->client
- && pjsip_regc_create(ast_sip_get_pjsip_endpoint(), state->client_state,
+ pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+
+
+ ast_assert(state->client_state->client == NULL);
+ if (pjsip_regc_create(ast_sip_get_pjsip_endpoint(), state->client_state,
sip_outbound_registration_response_cb,
&state->client_state->client) != PJ_SUCCESS) {
return -1;
}
+ ast_sip_set_tpselector_from_transport_name(registration->transport, &selector);
pjsip_regc_set_transport(state->client_state->client, &selector);
if (!ast_strlen_zero(registration->outbound_proxy)) {
/*! \brief Helper function which performs a single registration */
static int sip_outbound_registration_perform(void *data)
{
- RAII_VAR(struct sip_outbound_registration_state *, state, data, ao2_cleanup);
- RAII_VAR(struct sip_outbound_registration *, registration, ao2_bump(state->registration), ao2_cleanup);
+ struct sip_outbound_registration_state *state = data;
+ struct sip_outbound_registration *registration = ao2_bump(state->registration);
size_t i;
/* Just in case the client state is being reused for this registration, free the auth information */
AST_VECTOR_INIT(&state->client_state->outbound_auths, AST_VECTOR_SIZE(®istration->outbound_auths));
for (i = 0; i < AST_VECTOR_SIZE(®istration->outbound_auths); ++i) {
- const char *name = ast_strdup(AST_VECTOR_GET(®istration->outbound_auths, i));
- AST_VECTOR_APPEND(&state->client_state->outbound_auths, name);
+ char *name = ast_strdup(AST_VECTOR_GET(®istration->outbound_auths, i));
+
+ if (name && AST_VECTOR_APPEND(&state->client_state->outbound_auths, name)) {
+ ast_free(name);
+ }
}
state->client_state->retry_interval = registration->retry_interval;
state->client_state->forbidden_retry_interval = registration->forbidden_retry_interval;
+ state->client_state->fatal_retry_interval = registration->fatal_retry_interval;
state->client_state->max_retries = registration->max_retries;
state->client_state->retries = 0;
state->client_state->support_path = registration->support_path;
schedule_registration(state->client_state, (ast_random() % 10) + 1);
+ ao2_ref(registration, -1);
+ ao2_ref(state, -1);
return 0;
}
ast_debug(4, "Applying configuration to outbound registration '%s'\n", ast_sorcery_object_get_id(applied));
if (ast_strlen_zero(applied->server_uri)) {
- ast_log(LOG_ERROR, "No server URI specified on outbound registration '%s'",
+ ast_log(LOG_ERROR, "No server URI specified on outbound registration '%s'\n",
ast_sorcery_object_get_id(applied));
return -1;
+ } else if (ast_sip_validate_uri_length(applied->server_uri)) {
+ ast_log(LOG_ERROR, "Server URI or hostname length exceeds pjproject limit or is not a sip(s) uri: '%s'\n",
+ ast_sorcery_object_get_id(applied));
+ return -1;
} else if (ast_strlen_zero(applied->client_uri)) {
ast_log(LOG_ERROR, "No client URI specified on outbound registration '%s'\n",
ast_sorcery_object_get_id(applied));
return -1;
+ } else if (ast_sip_validate_uri_length(applied->client_uri)) {
+ ast_log(LOG_ERROR, "Client URI or hostname length exceeds pjproject limit or is not a sip(s) uri: '%s'\n",
+ ast_sorcery_object_get_id(applied));
+ return -1;
} else if (applied->line && ast_strlen_zero(applied->endpoint)) {
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;
}
- if (ast_sip_push_task_synchronous(NULL, sip_outbound_registration_regc_alloc, new_state)) {
+ if (ast_sip_push_task_synchronous(new_state->client_state->serializer,
+ sip_outbound_registration_regc_alloc, new_state)) {
return -1;
}
}
ao2_lock(states);
-
if (state) {
ao2_unlink(states, state);
}
-
ao2_link(states, new_state);
ao2_unlock(states);
static int unregister_task(void *obj)
{
- RAII_VAR(struct sip_outbound_registration_state*, state, obj, ao2_cleanup);
+ struct sip_outbound_registration_state *state = obj;
struct pjsip_regc *client = state->client_state->client;
pjsip_tx_data *tdata;
pjsip_regc_info info;
pjsip_regc_get_info(client, &info);
ast_debug(1, "Unregistering contacts with server '%s' from client '%s'\n",
- state->registration->server_uri, state->registration->client_uri);
+ state->registration->server_uri, state->registration->client_uri);
cancel_registration(state->client_state);
- if (pjsip_regc_unregister(client, &tdata) != PJ_SUCCESS) {
- return 0;
+ if (pjsip_regc_unregister(client, &tdata) == PJ_SUCCESS) {
+ registration_client_send(state->client_state, tdata);
}
- registration_client_send(state->client_state, tdata);
-
+ ao2_ref(state, -1);
return 0;
}
return 0;
}
+static void unregister_all(void)
+{
+ struct ao2_container *states;
+
+ states = ao2_global_obj_ref(current_states);
+ if (!states) {
+ return;
+ }
+
+ /* Clean out all the states and let sorcery handle recreating the registrations */
+ ao2_callback(states, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
+ ao2_ref(states, -1);
+}
+
+static void reregister_all(void)
+{
+ unregister_all();
+ ast_sorcery_load_object(ast_sip_get_sorcery(), "registration");
+}
+
static char *cli_complete_registration(const char *line, const char *word,
int pos, int state)
{
int wordlen;
int which = 0;
struct sip_outbound_registration *registration;
- RAII_VAR(struct ao2_container *, registrations, NULL, ao2_cleanup);
+ struct ao2_container *registrations;
struct ao2_iterator i;
if (pos != 3) {
}
wordlen = strlen(word);
+ if (wordlen == 0 && ++which > state) {
+ return ast_strdup("*all");
+ }
+
registrations = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "registration",
AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
if (!registrations) {
i = ao2_iterator_init(registrations, 0);
while ((registration = ao2_iterator_next(&i))) {
const char *name = ast_sorcery_object_get_id(registration);
+
if (!strncasecmp(word, name, wordlen) && ++which > state) {
result = ast_strdup(name);
}
- ao2_cleanup(registration);
+ ao2_ref(registration, -1);
if (result) {
break;
}
}
ao2_iterator_destroy(&i);
+
+ ao2_ref(registrations, -1);
return result;
}
static char *cli_unregister(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
- RAII_VAR(struct sip_outbound_registration_state *, state, NULL, ao2_cleanup);
+ struct sip_outbound_registration_state *state;
const char *registration_name;
switch (cmd) {
case CLI_INIT:
e->command = "pjsip send unregister";
e->usage =
- "Usage: pjsip send unregister <registration>\n"
- " Unregisters the specified outbound registration and stops future registration attempts.\n";
+ "Usage: pjsip send unregister <registration> | *all\n"
+ " Unregisters the specified (or all) outbound registration(s) "
+ "and stops future registration attempts.\n";
return NULL;
case CLI_GENERATE:
return cli_complete_registration(a->line, a->word, a->pos, a->n);
registration_name = a->argv[3];
+ if (strcmp(registration_name, "*all") == 0) {
+ unregister_all();
+ ast_cli(a->fd, "Unregister all queued\n");
+ return CLI_SUCCESS;
+ }
+
state = get_state(registration_name);
if (!state) {
ast_cli(a->fd, "Unable to retrieve registration %s\n", registration_name);
}
if (queue_unregister(state)) {
- ast_cli(a->fd, "Failed to queue unregistration");
- return 0;
+ ast_cli(a->fd, "Failed to queue unregistration\n");
}
+ ao2_ref(state, -1);
return CLI_SUCCESS;
}
static char *cli_register(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
- RAII_VAR(struct sip_outbound_registration_state *, state, NULL, ao2_cleanup);
+ struct sip_outbound_registration_state *state;
const char *registration_name;
switch (cmd) {
case CLI_INIT:
e->command = "pjsip send register";
e->usage =
- "Usage: pjsip send register <registration>\n"
- " Unregisters the specified outbound "
- "registration then re-registers and re-schedules it.\n";
+ "Usage: pjsip send register <registration> | *all \n"
+ " Unregisters the specified (or all) outbound "
+ "registration(s) then starts registration(s) and schedules re-registrations.\n";
return NULL;
case CLI_GENERATE:
return cli_complete_registration(a->line, a->word, a->pos, a->n);
registration_name = a->argv[3];
+ if (strcmp(registration_name, "*all") == 0) {
+ reregister_all();
+ ast_cli(a->fd, "Re-register all queued\n");
+ return CLI_SUCCESS;
+ }
+
state = get_state(registration_name);
if (!state) {
ast_cli(a->fd, "Unable to retrieve registration %s\n", registration_name);
* to be queued as separate tasks.
*/
if (queue_unregister(state)) {
- ast_cli(a->fd, "Failed to queue unregistration");
- return 0;
- }
- if (queue_register(state)) {
- ast_cli(a->fd, "Failed to queue registration");
- return 0;
+ ast_cli(a->fd, "Failed to queue unregistration\n");
+ } else if (queue_register(state)) {
+ ast_cli(a->fd, "Failed to queue registration\n");
}
+ ao2_ref(state, -1);
return CLI_SUCCESS;
}
static int ami_unregister(struct mansession *s, const struct message *m)
{
const char *registration_name = astman_get_header(m, "Registration");
- RAII_VAR(struct sip_outbound_registration_state *, state, NULL, ao2_cleanup);
+ struct sip_outbound_registration_state *state;
if (ast_strlen_zero(registration_name)) {
astman_send_error(s, m, "Registration parameter missing.");
return 0;
}
+ if (strcmp(registration_name, "*all") == 0) {
+ unregister_all();
+ astman_send_ack(s, m, "Unregistrations queued.");
+ return 0;
+ }
+
state = get_state(registration_name);
if (!state) {
astman_send_error(s, m, "Unable to retrieve registration entry\n");
if (queue_unregister(state)) {
astman_send_ack(s, m, "Failed to queue unregistration");
- return 0;
+ } else {
+ astman_send_ack(s, m, "Unregistration sent");
}
- astman_send_ack(s, m, "Unregistration sent");
+ ao2_ref(state, -1);
return 0;
}
static int ami_register(struct mansession *s, const struct message *m)
{
const char *registration_name = astman_get_header(m, "Registration");
- RAII_VAR(struct sip_outbound_registration_state *, state, NULL, ao2_cleanup);
+ struct sip_outbound_registration_state *state;
if (ast_strlen_zero(registration_name)) {
astman_send_error(s, m, "Registration parameter missing.");
return 0;
}
+ if (strcmp(registration_name, "*all") == 0) {
+ reregister_all();
+ astman_send_ack(s, m, "Reregistrations queued.");
+ return 0;
+ }
+
state = get_state(registration_name);
if (!state) {
astman_send_error(s, m, "Unable to retrieve registration entry\n");
*/
if (queue_unregister(state)) {
astman_send_ack(s, m, "Failed to queue unregistration");
- return 0;
- }
- if (queue_register(state)) {
+ } else if (queue_register(state)) {
astman_send_ack(s, m, "Failed to queue unregistration");
- return 0;
+ } else {
+ astman_send_ack(s, m, "Reregistration sent");
}
- astman_send_ack(s, m, "Reregistration sent");
+ ao2_ref(state, -1);
return 0;
}
static int ami_outbound_registration_task(void *obj)
{
struct sip_ami_outbound *ami = obj;
- RAII_VAR(struct ast_str *, buf, NULL, ast_free);
+ struct ast_str *buf;
struct sip_outbound_registration_state *state;
buf = ast_sip_create_ami_event("OutboundRegistrationDetail", ami->ami);
}
ast_str_append(&buf, 0, "Status: %s\r\n",
- sip_outbound_registration_status_str[state->client_state->status]);
+ sip_outbound_registration_status_str(state->client_state->status));
pjsip_regc_get_info(state->client_state->client, &info);
ast_str_append(&buf, 0, "NextReg: %d\r\n", info.next_reg);
}
astman_append(ami->ami->s, "%s\r\n", ast_str_buffer(buf));
+ ast_free(buf);
+
return ast_sip_format_auths_ami(&ami->registration->outbound_auths, ami->ami);
}
{
struct ast_sip_ami ami = { .s = s, .m = m, .action_id = astman_get_header(m, "ActionID"), };
struct sip_ami_outbound ami_outbound = { .ami = &ami };
- RAII_VAR(struct ao2_container *, regs, get_registrations(), ao2_cleanup);
+ struct ao2_container *regs;
+ regs = get_registrations();
if (!regs) {
astman_send_error(s, m, "Unable to retrieve "
"outbound registrations\n");
ami_outbound.registered,
ami_outbound.not_registered);
astman_send_list_complete_end(s);
+
+ ao2_ref(regs, -1);
return 0;
}
-static struct ao2_container *cli_get_container(void)
+static struct ao2_container *cli_get_container(const char *regex)
{
- RAII_VAR(struct ao2_container *, container, get_registrations(), ao2_cleanup);
+ RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
struct ao2_container *s_container;
+ container = ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "registration", regex);
if (!container) {
return NULL;
}
ast_assert(context->output_buffer != NULL);
- if (!state) {
- return 0;
- }
-
ast_str_append(&context->output_buffer, 0, " %-s/%-*.*s %-16s %-16s\n",
id,
(int) (REGISTRATION_URI_FIELD_LEN - strlen(id)),
AST_VECTOR_SIZE(®istration->outbound_auths)
? AST_VECTOR_GET(®istration->outbound_auths, 0)
: "n/a",
- sip_outbound_registration_status_str[state->client_state->status]);
- ao2_ref(state, -1);
+ (state ? sip_outbound_registration_status_str(state->client_state->status) : "Unregistered"));
+ ao2_cleanup(state);
if (context->show_details
|| (context->show_details_only_level_0 && context->indent_level == 0)) {
AST_CLI_DEFINE(cli_register, "Registers an outbound registration target"),
AST_CLI_DEFINE(my_cli_traverse_objects, "List PJSIP Registrations",
.command = "pjsip list registrations",
- .usage = "Usage: pjsip list registrations\n"
- " List the configured PJSIP Registrations\n"),
+ .usage = "Usage: pjsip list registrations [ like <pattern> ]\n"
+ " List the configured PJSIP Registrations\n"
+ " Optional regular expression pattern is used to filter the list.\n"),
AST_CLI_DEFINE(my_cli_traverse_objects, "Show PJSIP Registrations",
.command = "pjsip show registrations",
- .usage = "Usage: pjsip show registrations\n"
- " Show the configured PJSIP Registrations\n"),
+ .usage = "Usage: pjsip show registrations [ like <pattern> ]\n"
+ " Show the configured PJSIP Registrations\n"
+ " Optional regular expression pattern is used to filter the list.\n"),
AST_CLI_DEFINE(my_cli_traverse_objects, "Show PJSIP Registration",
.command = "pjsip show registration",
.usage = "Usage: pjsip show registration <id>\n"
return;
}
+ /*
+ * Refresh the current configured registrations. We don't need to hold
+ * onto the objects, as the apply handler will cause their states to
+ * be created appropriately.
+ */
+ ao2_cleanup(get_registrations());
+
/* Now to purge dead registrations. */
ao2_callback(states, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, check_state, NULL);
ao2_ref(states, -1);
.object_type_loaded = registration_loaded_observer,
};
+static void registration_deleted_observer(const void *obj)
+{
+ const struct sip_outbound_registration *registration = obj;
+ struct ao2_container *states;
+
+ states = ao2_global_obj_ref(current_states);
+ if (!states) {
+ /* Global container has gone. Likely shutting down. */
+ return;
+ }
+
+ ao2_find(states, ast_sorcery_object_get_id(registration), OBJ_UNLINK | OBJ_NODATA | OBJ_SEARCH_KEY);
+
+ ao2_ref(states, -1);
+}
+
+static const struct ast_sorcery_observer registration_observer = {
+ .deleted = registration_deleted_observer,
+};
+
+static void network_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message)
+{
+ /* This callback is only concerned with network change messages from the system topic. */
+ if (stasis_message_type(message) != ast_network_change_type()) {
+ return;
+ }
+ ast_debug(3, "Received network change event\n");
+
+ reregister_all();
+}
+
static int unload_module(void)
{
- ast_sip_unregister_endpoint_identifier(&line_identifier);
- ast_sorcery_observer_remove(ast_sip_get_sorcery(), "auth", &observer_callbacks_auth);
- ast_sorcery_instance_observer_remove(ast_sip_get_sorcery(), &observer_callbacks_registrations);
- ast_cli_unregister_multiple(cli_outbound_registration, ARRAY_LEN(cli_outbound_registration));
- ast_sip_unregister_cli_formatter(cli_formatter);
+ int remaining;
+
+ network_change_sub = stasis_unsubscribe_and_join(network_change_sub);
+
ast_manager_unregister("PJSIPShowRegistrationsOutbound");
ast_manager_unregister("PJSIPUnregister");
ast_manager_unregister("PJSIPRegister");
+ ast_cli_unregister_multiple(cli_outbound_registration, ARRAY_LEN(cli_outbound_registration));
+ ast_sip_unregister_cli_formatter(cli_formatter);
+ cli_formatter = NULL;
+
+ ast_sip_unregister_endpoint_identifier(&line_identifier);
+
+ ast_sorcery_observer_remove(ast_sip_get_sorcery(), "auth", &observer_callbacks_auth);
+ ast_sorcery_instance_observer_remove(ast_sip_get_sorcery(), &observer_callbacks_registrations);
+
+ ast_sorcery_object_unregister(ast_sip_get_sorcery(), "registration");
+
ao2_global_obj_release(current_states);
+ ast_sip_transport_monitor_unregister_all(registration_transport_shutdown_cb);
+
+ /* Wait for registration serializers to get destroyed. */
+ ast_debug(2, "Waiting for registration transactions to complete for unload.\n");
+ remaining = ast_serializer_shutdown_group_join(shutdown_group, MAX_UNLOAD_TIMEOUT_TIME);
+ if (remaining) {
+ /*
+ * NOTE: We probably have a sip_outbound_registration_client_state
+ * ref leak if the remaining count cannot reach zero after a few
+ * minutes of trying to unload.
+ */
+ ast_log(LOG_WARNING, "Unload incomplete. Could not stop %d outbound registrations. Try again later.\n",
+ remaining);
+ return -1;
+ }
+
+ ast_debug(2, "Successful shutdown.\n");
+
+ ao2_cleanup(shutdown_group);
+ shutdown_group = NULL;
+
return 0;
}
CHECK_PJSIP_MODULE_LOADED();
+ shutdown_group = ast_serializer_shutdown_group_alloc();
+ if (!shutdown_group) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ /* Create outbound registration states container. */
+ new_states = ao2_container_alloc(DEFAULT_STATE_BUCKETS,
+ registration_state_hash, registration_state_cmp);
+ if (!new_states) {
+ ast_log(LOG_ERROR, "Unable to allocate registration states container\n");
+ unload_module();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ ao2_global_obj_replace_unref(current_states, new_states);
+ ao2_ref(new_states, -1);
+
+ /*
+ * Register sorcery object descriptions.
+ */
ast_sorcery_apply_config(ast_sip_get_sorcery(), "res_pjsip_outbound_registration");
ast_sorcery_apply_default(ast_sip_get_sorcery(), "registration", "config", "pjsip.conf,criteria=type=registration");
if (ast_sorcery_object_register(ast_sip_get_sorcery(), "registration", sip_outbound_registration_alloc, NULL, sip_outbound_registration_apply)) {
+ unload_module();
return AST_MODULE_LOAD_DECLINE;
}
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "expiration", "3600", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, expiration));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "retry_interval", "60", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, retry_interval));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "forbidden_retry_interval", "0", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, forbidden_retry_interval));
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "fatal_retry_interval", "0", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, fatal_retry_interval));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "max_retries", "10", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, max_retries));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "auth_rejection_permanent", "yes", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, auth_rejection_permanent));
ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "outbound_auth", "", outbound_auth_handler, outbound_auths_to_str, outbound_auths_to_var_list, 0, 0);
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "support_path", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, support_path));
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_sip_register_endpoint_identifier(&line_identifier);
- 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);
+ /*
+ * Register sorcery observers.
+ */
+ if (ast_sorcery_instance_observer_add(ast_sip_get_sorcery(),
+ &observer_callbacks_registrations)
+ || ast_sorcery_observer_add(ast_sip_get_sorcery(), "auth",
+ &observer_callbacks_auth)
+ || ast_sorcery_observer_add(ast_sip_get_sorcery(), "registration",
+ ®istration_observer)) {
+ ast_log(LOG_ERROR, "Unable to register observers.\n");
+ unload_module();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ /* Register how this module identifies endpoints. */
+ ast_sip_register_endpoint_identifier(&line_identifier);
+
+ /* Register CLI commands. */
cli_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
if (!cli_formatter) {
ast_log(LOG_ERROR, "Unable to allocate memory for cli formatter\n");
unload_module();
- return AST_MODULE_LOAD_FAILURE;
+ return AST_MODULE_LOAD_DECLINE;
}
cli_formatter->name = "registration";
cli_formatter->print_header = cli_print_header;
cli_formatter->iterate = cli_iterator;
cli_formatter->get_id = ast_sorcery_object_get_id;
cli_formatter->retrieve_by_id = cli_retrieve_by_id;
-
ast_sip_register_cli_formatter(cli_formatter);
ast_cli_register_multiple(cli_outbound_registration, ARRAY_LEN(cli_outbound_registration));
- new_states = ao2_container_alloc(DEFAULT_STATE_BUCKETS,
- registration_state_hash, registration_state_cmp);
- if (!new_states) {
- ast_log(LOG_ERROR, "Unable to allocate registration states container\n");
- unload_module();
- return AST_MODULE_LOAD_FAILURE;
- }
- ao2_global_obj_replace_unref(current_states, new_states);
- ao2_ref(new_states, -1);
+ /* 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);
- if (ast_sorcery_instance_observer_add(ast_sip_get_sorcery(),
- &observer_callbacks_registrations)) {
- unload_module();
- return AST_MODULE_LOAD_FAILURE;
- }
+ /* Clear any previous statsd gauges in case we weren't shutdown cleanly */
+ ast_statsd_log("PJSIP.registrations.count", AST_STATSD_GAUGE, 0);
+ ast_statsd_log("PJSIP.registrations.state.Registered", AST_STATSD_GAUGE, 0);
+ ast_statsd_log("PJSIP.registrations.state.Unregistered", AST_STATSD_GAUGE, 0);
+ ast_statsd_log("PJSIP.registrations.state.Rejected", AST_STATSD_GAUGE, 0);
+ /* Load configuration objects */
ast_sorcery_load_object(ast_sip_get_sorcery(), "registration");
- ast_sorcery_observer_add(ast_sip_get_sorcery(), "auth", &observer_callbacks_auth);
+ network_change_sub = stasis_subscribe(ast_system_topic(),
+ network_change_stasis_cb, NULL);
return AST_MODULE_LOAD_SUCCESS;
}