res_pjsip: Use reasonable buffer lengths for endpoint identification
[asterisk/asterisk.git] / res / res_pjsip_outbound_registration.c
index 88007d1..d9afcd2 100644 (file)
@@ -19,6 +19,7 @@
 /*** MODULEINFO
        <depend>pjproject</depend>
        <depend>res_pjsip</depend>
+       <use type="module">res_statsd</use>
        <support_level>core</support_level>
  ***/
 
                                        <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>
                <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>
@@ -346,6 +357,10 @@ struct sip_outbound_registration_client_state {
        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) */
@@ -366,6 +381,9 @@ static struct ast_serializer_shutdown_group *shutdown_group;
 #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)
 {
@@ -444,7 +462,7 @@ static int line_identify_relationship(void *obj, void *arg, int flags)
        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)
@@ -508,15 +526,24 @@ static pj_status_t registration_client_send(struct sip_outbound_registration_cli
 {
        pj_status_t status;
        int *callback_invoked;
+       pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
 
        callback_invoked = ast_threadstorage_get(&register_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
@@ -534,20 +561,21 @@ static int handle_client_registration(void *data)
 {
        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(1, "Outbound 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;
@@ -557,6 +585,7 @@ static int handle_client_registration(void *data)
                        /* insert a new Supported header */
                        hdr = pjsip_supported_hdr_create(tdata->pool);
                        if (!hdr) {
+                               pjsip_tx_data_dec_ref(tdata);
                                return -1;
                        }
 
@@ -615,15 +644,30 @@ static void schedule_registration(struct sip_outbound_registration_client_state
 
 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,
-               sip_outbound_registration_status_str(client_state->status));
+               status_old);
        ast_statsd_log_string_va("PJSIP.registrations.state.%s", AST_STATSD_GAUGE, "+1", 1.0,
-               sip_outbound_registration_status_str(status));
-       client_state->status = status;
+               status_new);
 }
 
 /*! \brief Callback function for unregistering (potentially) and destroying state */
@@ -754,6 +798,82 @@ static void schedule_retry(struct registration_response *response, unsigned int
        }
 }
 
+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)
 {
@@ -822,9 +942,15 @@ static int handle_registration_response(void *data)
                                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);
                        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. */
@@ -947,7 +1073,8 @@ static void sip_outbound_registration_state_destroy(void *obj)
        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) {
@@ -971,12 +1098,15 @@ static void sip_outbound_registration_client_state_destroy(void *obj)
                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) {
@@ -989,19 +1119,34 @@ static struct sip_outbound_registration_state *sip_outbound_registration_state_a
                return NULL;
        }
 
-       state->client_state->serializer = ast_sip_create_serializer_group(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->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->registration = ao2_bump(registration);
        return state;
 }
@@ -1038,7 +1183,7 @@ static int sip_dialog_create_contact(pj_pool_t *pool, pj_str_t *contact, const c
        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);
@@ -1050,20 +1195,20 @@ static int sip_dialog_create_contact(pj_pool_t *pool, pj_str_t *contact, const c
 
        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()),
@@ -1072,13 +1217,13 @@ static int sip_dialog_create_contact(pj_pool_t *pool, pj_str_t *contact, const c
        }
 
        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,
@@ -1163,28 +1308,20 @@ static int sip_outbound_registration_regc_alloc(void *data)
                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;
                }
        }
 
+       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,
@@ -1192,6 +1329,7 @@ static int sip_outbound_registration_regc_alloc(void *data)
                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)) {
@@ -1247,10 +1385,10 @@ static int sip_outbound_registration_perform(void *data)
 
        AST_VECTOR_INIT(&state->client_state->outbound_auths, AST_VECTOR_SIZE(&registration->outbound_auths));
        for (i = 0; i < AST_VECTOR_SIZE(&registration->outbound_auths); ++i) {
-               const char *name = ast_strdup(AST_VECTOR_GET(&registration->outbound_auths, i));
+               char *name = ast_strdup(AST_VECTOR_GET(&registration->outbound_auths, i));
 
-               if (name) {
-                       AST_VECTOR_APPEND(&state->client_state->outbound_auths, name);
+               if (name && AST_VECTOR_APPEND(&state->client_state->outbound_auths, name)) {
+                       ast_free(name);
                }
        }
        state->client_state->retry_interval = registration->retry_interval;
@@ -1290,10 +1428,18 @@ static int sip_outbound_registration_apply(const struct ast_sorcery *sorcery, vo
                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));
@@ -1422,6 +1568,26 @@ static int queue_register(struct sip_outbound_registration_state *state)
        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)
 {
@@ -1437,6 +1603,10 @@ static char *cli_complete_registration(const char *line, const char *word,
        }
 
        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) {
@@ -1471,8 +1641,9 @@ static char *cli_unregister(struct ast_cli_entry *e, int cmd, struct ast_cli_arg
        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);
@@ -1484,6 +1655,12 @@ static char *cli_unregister(struct ast_cli_entry *e, int cmd, struct ast_cli_arg
 
        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);
@@ -1507,9 +1684,9 @@ static char *cli_register(struct ast_cli_entry *e, int cmd, struct ast_cli_args
        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);
@@ -1521,6 +1698,12 @@ static char *cli_register(struct ast_cli_entry *e, int cmd, struct ast_cli_args
 
        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);
@@ -1550,6 +1733,12 @@ static int ami_unregister(struct mansession *s, const struct message *m)
                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");
@@ -1576,6 +1765,12 @@ static int ami_register(struct mansession *s, const struct message *m)
                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");
@@ -1751,10 +1946,6 @@ static int cli_print_body(void *obj, void *arg, int flags)
 
        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)),
@@ -1763,8 +1954,8 @@ static int cli_print_body(void *obj, void *arg, int flags)
                AST_VECTOR_SIZE(&registration->outbound_auths)
                        ? AST_VECTOR_GET(&registration->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)) {
@@ -1904,10 +2095,43 @@ static const struct ast_sorcery_instance_observer observer_callbacks_registratio
        .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)
 {
        int remaining;
 
+       network_change_sub = stasis_unsubscribe_and_join(network_change_sub);
+
        ast_manager_unregister("PJSIPShowRegistrationsOutbound");
        ast_manager_unregister("PJSIPUnregister");
        ast_manager_unregister("PJSIPRegister");
@@ -1925,6 +2149,8 @@ static int unload_module(void)
 
        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);
@@ -1955,7 +2181,7 @@ static int load_module(void)
 
        shutdown_group = ast_serializer_shutdown_group_alloc();
        if (!shutdown_group) {
-               return AST_MODULE_LOAD_FAILURE;
+               return AST_MODULE_LOAD_DECLINE;
        }
 
        /* Create outbound registration states container. */
@@ -1964,7 +2190,7 @@ static int load_module(void)
        if (!new_states) {
                ast_log(LOG_ERROR, "Unable to allocate registration states container\n");
                unload_module();
-               return AST_MODULE_LOAD_FAILURE;
+               return AST_MODULE_LOAD_DECLINE;
        }
        ao2_global_obj_replace_unref(current_states, new_states);
        ao2_ref(new_states, -1);
@@ -2003,10 +2229,12 @@ static int load_module(void)
        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)) {
+                       &observer_callbacks_auth)
+               || ast_sorcery_observer_add(ast_sip_get_sorcery(), "registration",
+                       &registration_observer)) {
                ast_log(LOG_ERROR, "Unable to register observers.\n");
                unload_module();
-               return AST_MODULE_LOAD_FAILURE;
+               return AST_MODULE_LOAD_DECLINE;
        }
 
        /* Register how this module identifies endpoints. */
@@ -2017,7 +2245,7 @@ static int load_module(void)
        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;
@@ -2043,6 +2271,9 @@ static int load_module(void)
        /* Load configuration objects */
        ast_sorcery_load_object(ast_sip_get_sorcery(), "registration");
 
+       network_change_sub = stasis_subscribe(ast_system_topic(),
+               network_change_stasis_cb, NULL);
+
        return AST_MODULE_LOAD_SUCCESS;
 }