diff options
-rw-r--r-- | CHANGES | 7 | ||||
-rw-r--r-- | configs/samples/pjsip.conf.sample | 10 | ||||
-rwxr-xr-x | contrib/scripts/sip_to_pjsip/sip_to_pjsip.py | 30 | ||||
-rw-r--r-- | include/asterisk/vector.h | 8 | ||||
-rw-r--r-- | main/cdr.c | 127 | ||||
-rw-r--r-- | res/res_pjsip.c | 32 | ||||
-rw-r--r-- | res/res_pjsip_registrar.c | 156 |
7 files changed, 295 insertions, 75 deletions
@@ -34,6 +34,13 @@ res_pjsip unsolicited MWI NOTIFY requests and make them available to other modules via the stasis message bus. + * The "remove_existing" option now allows a registration to succeed by + displacing any existing contacts that now exceed the "max_contacts" count. + Any removed contacts are the next to expire. The behaviour change is + beneficial when "rewrite_contact" is enabled and "max_contacts" is greater + than one. The removed contact is likely the old contact created by + "rewrite_contact" that the device is refreshing. + res_musiconhold ------------------ * By default, when res_musiconhold reloads or unloads, it sends a HUP signal diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index 536c9f1ec..8f739d4a0 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -902,7 +902,13 @@ ;max_contacts=0 ; Maximum number of contacts that can bind to an AoR (default: ; "0") ;minimum_expiration=60 ; Minimum keep alive time for an AoR (default: "60") -;remove_existing=no ; Determines whether new contacts replace existing ones +;remove_existing=no ; Allow a registration to succeed by displacing any existing + ; contacts that now exceed the max_contacts count. Any + ; removed contacts are the next to expire. The behaviour is + ; beneficial when rewrite_contact is enabled and max_contacts + ; is greater than one. The removed contact is likely the old + ; contact created by rewrite_contact that the device is + ; refreshing. ; (default: "no") ;type= ; Must be of type aor (default: "") ;qualify_frequency=0 ; Interval at which to qualify an AoR (default: "0") @@ -1141,7 +1147,7 @@ ;outbound_auth= ; Authentication object(s) to be used for outbound ; publishes. - ; This is a comma-delimited list of auth sections + ; This is a comma-delimited list of auth sections ; defined in pjsip.conf used to respond to outbound ; authentication challenges. ; Using the same auth section for inbound and diff --git a/contrib/scripts/sip_to_pjsip/sip_to_pjsip.py b/contrib/scripts/sip_to_pjsip/sip_to_pjsip.py index 98a5e9546..eb3aab3b8 100755 --- a/contrib/scripts/sip_to_pjsip/sip_to_pjsip.py +++ b/contrib/scripts/sip_to_pjsip/sip_to_pjsip.py @@ -116,6 +116,27 @@ def set_dtmfmode(key, val, section, pjsip, nmapped): set_value(key, 'none', section, pjsip, nmapped) +def setup_udptl(section, pjsip, nmapped): + """Sets values from udptl into the appropriate pjsip.conf options.""" + try: + val = sip.get(section, 't38pt_udptl')[0] + except LookupError: + try: + val = sip.get('general', 't38pt_udptl')[0] + except LookupError: + return + + ec = 'none' + if 'yes' in val: + set_value('t38_udptl', 'yes', section, pjsip, nmapped) + if 'no' in val: + set_value('t38_udptl', 'no', section, pjsip, nmapped) + if 'redundancy' in val: + ec = 'redundancy' + if 'fec' in val: + ec = 'fec' + set_value('t38_udptl_ec', ec, section, pjsip, nmapped) + def from_nat(key, val, section, pjsip, nmapped): """Sets values from nat into the appropriate pjsip.conf options.""" # nat from sip.conf can be comma separated list of values: @@ -387,6 +408,7 @@ peer_map = [ ['allow', merge_value], ['nat', from_nat], # rtp_symmetric, force_rport, # rewrite_contact + ['rtptimeout', set_value('rtp_timeout')], ['icesupport', set_value('ice_support')], ['autoframing', set_value('use_ptime')], ['outboundproxy', set_value('outbound_proxy')], @@ -1068,6 +1090,7 @@ def map_peer(sip, section, pjsip, nmapped): except LookupError: pass # key not found in sip.conf + setup_udptl(section, pjsip, nmapped) def find_non_mapped(sections, nmapped): """ @@ -1101,6 +1124,13 @@ def map_system(sip, pjsip, nmapped): except LookupError: pass + + try: + sipdebug = sip.get('general', 'sipdebug')[0] + set_value('debug', sipdebug, 'global', pjsip, nmapped, 'global') + except LookupError: + pass + try: useroption_parsing = sip.get('general', 'legacy_useroption_parsing')[0] set_value('ignore_uri_user_options', useroption_parsing, 'global', pjsip, nmapped, 'global') diff --git a/include/asterisk/vector.h b/include/asterisk/vector.h index 1e6fe038c..68ce13065 100644 --- a/include/asterisk/vector.h +++ b/include/asterisk/vector.h @@ -548,6 +548,14 @@ AST_VECTOR(ast_vector_int, int); #define AST_VECTOR_SIZE(vec) (vec)->current /*! + * \brief Get the maximum number of elements the vector can currently hold. + * + * \param vec Vector to query. + * \return Maximum number of elements the vector can currently hold. + */ +#define AST_VECTOR_MAX_SIZE(vec) (vec)->max + +/*! * \brief Reset vector. * * \param vec Vector to reset. diff --git a/main/cdr.c b/main/cdr.c index df5d99b9b..ddbed5fd0 100644 --- a/main/cdr.c +++ b/main/cdr.c @@ -1567,11 +1567,10 @@ static enum process_bridge_enter_results single_state_process_bridge_enter(struc for (it_cdrs = ao2_iterator_init(bridge->channels, 0); !success && (channel_id = ao2_iterator_next(&it_cdrs)); ao2_ref(channel_id, -1)) { - RAII_VAR(struct cdr_object *, cand_cdr_master, - ao2_find(active_cdrs_by_channel, channel_id, OBJ_SEARCH_KEY), - ao2_cleanup); + struct cdr_object *cand_cdr_master; struct cdr_object *cand_cdr; + cand_cdr_master = ao2_find(active_cdrs_by_channel, channel_id, OBJ_SEARCH_KEY); if (!cand_cdr_master) { continue; } @@ -1593,6 +1592,7 @@ static enum process_bridge_enter_results single_state_process_bridge_enter(struc break; } ao2_unlock(cand_cdr_master); + ao2_cleanup(cand_cdr_master); } ao2_iterator_destroy(&it_cdrs); @@ -1715,11 +1715,10 @@ static enum process_bridge_enter_results dial_state_process_bridge_enter(struct for (it_cdrs = ao2_iterator_init(bridge->channels, 0); !success && (channel_id = ao2_iterator_next(&it_cdrs)); ao2_ref(channel_id, -1)) { - RAII_VAR(struct cdr_object *, cand_cdr_master, - ao2_find(active_cdrs_by_channel, channel_id, OBJ_SEARCH_KEY), - ao2_cleanup); + struct cdr_object *cand_cdr_master; struct cdr_object *cand_cdr; + cand_cdr_master = ao2_find(active_cdrs_by_channel, channel_id, OBJ_SEARCH_KEY); if (!cand_cdr_master) { continue; } @@ -1754,6 +1753,7 @@ static enum process_bridge_enter_results dial_state_process_bridge_enter(struct break; } ao2_unlock(cand_cdr_master); + ao2_cleanup(cand_cdr_master); } ao2_iterator_destroy(&it_cdrs); @@ -1924,7 +1924,7 @@ static int dial_status_end(const char *dialstatus) static void handle_dial_message(void *data, struct stasis_subscription *sub, struct stasis_message *message) { RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); - RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup); + struct cdr_object *cdr; struct ast_multi_channel_blob *payload = stasis_message_data(message); struct ast_channel_snapshot *caller; struct ast_channel_snapshot *peer; @@ -1960,7 +1960,6 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str } else { cdr = ao2_find(active_cdrs_by_channel, peer->uniqueid, OBJ_SEARCH_KEY); } - if (!cdr) { ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", caller ? caller->name : peer->name); ast_assert(0); @@ -2000,15 +1999,12 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str struct cdr_object *new_cdr; new_cdr = cdr_object_create_and_append(cdr); - if (!new_cdr) { - ao2_unlock(cdr); - return; + if (new_cdr) { + new_cdr->fn_table->process_dial_begin(new_cdr, caller, peer); } - new_cdr->fn_table->process_dial_begin(new_cdr, - caller, - peer); } ao2_unlock(cdr); + ao2_cleanup(cdr); } static int cdr_object_finalize_party_b(void *obj, void *arg, int flags) @@ -2082,7 +2078,7 @@ static int check_new_cdr_needed(struct ast_channel_snapshot *old_snapshot, */ static void handle_channel_cache_message(void *data, struct stasis_subscription *sub, struct stasis_message *message) { - RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup); + struct cdr_object *cdr; RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); struct stasis_cache_update *update = stasis_message_data(message); struct ast_channel_snapshot *old_snapshot; @@ -2110,19 +2106,19 @@ static void handle_channel_cache_message(void *data, struct stasis_subscription } cdr->is_root = 1; ao2_link(active_cdrs_by_channel, cdr); + } else { + cdr = ao2_find(active_cdrs_by_channel, uniqueid, OBJ_SEARCH_KEY); } /* Handle Party A */ if (!cdr) { - cdr = ao2_find(active_cdrs_by_channel, uniqueid, OBJ_SEARCH_KEY); - } - if (!cdr) { ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", name); ast_assert(0); } else { ao2_lock(cdr); if (new_snapshot) { int all_reject = 1; + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { if (!it_cdr->fn_table->process_party_a) { continue; @@ -2132,6 +2128,7 @@ static void handle_channel_cache_message(void *data, struct stasis_subscription if (all_reject && check_new_cdr_needed(old_snapshot, new_snapshot)) { /* We're not hung up and we have a new snapshot - we need a new CDR */ struct cdr_object *new_cdr; + new_cdr = cdr_object_create_and_append(cdr); if (new_cdr) { new_cdr->fn_table->process_party_a(new_cdr, new_snapshot); @@ -2157,6 +2154,7 @@ static void handle_channel_cache_message(void *data, struct stasis_subscription old_snapshot); } + ao2_cleanup(cdr); } struct bridge_leave_data { @@ -2219,9 +2217,7 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription * struct ast_channel_snapshot *channel = update->channel; RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); - RAII_VAR(struct cdr_object *, cdr, - ao2_find(active_cdrs_by_channel, channel->uniqueid, OBJ_SEARCH_KEY), - ao2_cleanup); + struct cdr_object *cdr; struct cdr_object *it_cdr; struct bridge_leave_data leave_data = { .bridge = bridge, @@ -2242,6 +2238,7 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription * (unsigned int)stasis_message_timestamp(message)->tv_sec, (unsigned int)stasis_message_timestamp(message)->tv_usec); + cdr = ao2_find(active_cdrs_by_channel, channel->uniqueid, OBJ_SEARCH_KEY); if (!cdr) { ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name); ast_assert(0); @@ -2262,16 +2259,16 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription * } } ao2_unlock(cdr); - if (!left_bridge) { - return; - } - if (strcmp(bridge->subclass, "parking")) { - /* Party B */ + /* Party B */ + if (left_bridge + && strcmp(bridge->subclass, "parking")) { ao2_callback(active_cdrs_by_channel, OBJ_NODATA, - cdr_object_party_b_left_bridge_cb, - &leave_data); + cdr_object_party_b_left_bridge_cb, + &leave_data); } + + ao2_cleanup(cdr); } /*! @@ -2376,17 +2373,14 @@ static void handle_bridge_pairings(struct cdr_object *cdr, struct ast_bridge_sna it_channels = ao2_iterator_init(bridge->channels, 0); while ((channel_id = ao2_iterator_next(&it_channels))) { - RAII_VAR(struct cdr_object *, cand_cdr, - ao2_find(active_cdrs_by_channel, channel_id, OBJ_SEARCH_KEY), - ao2_cleanup); + struct cdr_object *cand_cdr; - if (!cand_cdr) { - ao2_ref(channel_id, -1); - continue; + cand_cdr = ao2_find(active_cdrs_by_channel, channel_id, OBJ_SEARCH_KEY); + if (cand_cdr) { + bridge_candidate_process(cdr, cand_cdr); + ao2_ref(cand_cdr, -1); } - bridge_candidate_process(cdr, cand_cdr); - ao2_ref(channel_id, -1); } ao2_iterator_destroy(&it_channels); @@ -2522,9 +2516,7 @@ static void handle_bridge_enter_message(void *data, struct stasis_subscription * struct ast_bridge_blob *update = stasis_message_data(message); struct ast_bridge_snapshot *bridge = update->bridge; struct ast_channel_snapshot *channel = update->channel; - RAII_VAR(struct cdr_object *, cdr, - ao2_find(active_cdrs_by_channel, channel->uniqueid, OBJ_SEARCH_KEY), - ao2_cleanup); + struct cdr_object *cdr; RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); @@ -2541,6 +2533,7 @@ static void handle_bridge_enter_message(void *data, struct stasis_subscription * (unsigned int)stasis_message_timestamp(message)->tv_sec, (unsigned int)stasis_message_timestamp(message)->tv_usec); + cdr = ao2_find(active_cdrs_by_channel, channel->uniqueid, OBJ_SEARCH_KEY); if (!cdr) { ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name); ast_assert(0); @@ -2552,6 +2545,7 @@ static void handle_bridge_enter_message(void *data, struct stasis_subscription * } else { handle_standard_bridge_enter_message(cdr, bridge, channel); } + ao2_cleanup(cdr); } /*! @@ -2566,7 +2560,7 @@ static void handle_parked_call_message(void *data, struct stasis_subscription *s { struct ast_parked_call_payload *payload = stasis_message_data(message); struct ast_channel_snapshot *channel = payload->parkee; - RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup); + struct cdr_object *cdr; RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); int unhandled = 1; @@ -2608,7 +2602,9 @@ static void handle_parked_call_message(void *data, struct stasis_subscription *s if (unhandled) { /* Nothing handled the messgae - we need a new one! */ - struct cdr_object *new_cdr = cdr_object_create_and_append(cdr); + struct cdr_object *new_cdr; + + new_cdr = cdr_object_create_and_append(cdr); if (new_cdr) { /* As the new CDR is created in the single state, it is guaranteed * to have a function for the parked call message and will handle @@ -2619,6 +2615,7 @@ static void handle_parked_call_message(void *data, struct stasis_subscription *s ao2_unlock(cdr); + ao2_cleanup(cdr); } /*! @@ -3111,15 +3108,16 @@ static struct cdr_object *cdr_object_get_by_name(const char *name) int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size_t length) { - RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup); + struct cdr_object *cdr; struct cdr_object *cdr_obj; - if (!cdr) { - ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name); + if (ast_strlen_zero(name)) { return 1; } - if (ast_strlen_zero(name)) { + cdr = cdr_object_get_by_name(channel_name); + if (!cdr) { + ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name); return 1; } @@ -3133,18 +3131,20 @@ int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size ao2_unlock(cdr); + ao2_cleanup(cdr); return 0; } int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf, char delim, char sep) { - RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup); + struct cdr_object *cdr; struct cdr_object *it_cdr; struct ast_var_t *variable; const char *var; char workspace[256]; int total = 0, x = 0, i; + cdr = cdr_object_get_by_name(channel_name); if (!cdr) { RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); @@ -3193,6 +3193,7 @@ int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf, } } ao2_unlock(cdr); + ao2_cleanup(cdr); return total; } @@ -3260,7 +3261,7 @@ static int cdr_object_update_party_b_userfield_cb(void *obj, void *arg, int flag void ast_cdr_setuserfield(const char *channel_name, const char *userfield) { - RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup); + struct cdr_object *cdr; struct party_b_userfield_update party_b_info = { .channel_name = channel_name, .userfield = userfield, @@ -3268,6 +3269,7 @@ void ast_cdr_setuserfield(const char *channel_name, const char *userfield) struct cdr_object *it_cdr; /* Handle Party A */ + cdr = cdr_object_get_by_name(channel_name); if (cdr) { ao2_lock(cdr); for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { @@ -3284,6 +3286,7 @@ void ast_cdr_setuserfield(const char *channel_name, const char *userfield) cdr_object_update_party_b_userfield_cb, &party_b_info); + ao2_cleanup(cdr); } static void post_cdr(struct ast_cdr *cdr) @@ -3322,9 +3325,10 @@ static void post_cdr(struct ast_cdr *cdr) int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option) { - RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup); + struct cdr_object *cdr; struct cdr_object *it_cdr; + cdr = cdr_object_get_by_name(channel_name); if (!cdr) { return -1; } @@ -3342,14 +3346,16 @@ int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option) } ao2_unlock(cdr); + ao2_cleanup(cdr); return 0; } int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option) { - RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup); + struct cdr_object *cdr; struct cdr_object *it_cdr; + cdr = cdr_object_get_by_name(channel_name); if (!cdr) { return -1; } @@ -3363,15 +3369,17 @@ int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option } ao2_unlock(cdr); + ao2_cleanup(cdr); return 0; } int ast_cdr_reset(const char *channel_name, int keep_variables) { - RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup); + struct cdr_object *cdr; struct ast_var_t *vardata; struct cdr_object *it_cdr; + cdr = cdr_object_get_by_name(channel_name); if (!cdr) { return -1; } @@ -3399,6 +3407,7 @@ int ast_cdr_reset(const char *channel_name, int keep_variables) } ao2_unlock(cdr); + ao2_cleanup(cdr); return 0; } @@ -3808,7 +3817,7 @@ static void cli_show_channel(struct ast_cli_args *a) char answer_time_buffer[64]; char end_time_buffer[64]; const char *channel_name = a->argv[3]; - RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup); + struct cdr_object *cdr; #define TITLE_STRING "%-10.10s %-20.20s %-25.25s %-15.15s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8s %-8.8s\n" #define FORMAT_STRING "%-10.10s %-20.20s %-25.25s %-15.15s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8ld %-8.8ld\n" @@ -3853,6 +3862,9 @@ static void cli_show_channel(struct ast_cli_args *a) (long)ast_tvdiff_ms(end, it_cdr->start) / 1000); } ao2_unlock(cdr); + + ao2_cleanup(cdr); + #undef FORMAT_STRING #undef TITLE_STRING } @@ -4267,8 +4279,6 @@ int ast_cdr_engine_init(void) void ast_cdr_engine_term(void) { RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); - RAII_VAR(void *, payload, NULL, ao2_cleanup); - RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup); /* Since this is called explicitly during process shutdown, we might not have ever * been initialized. If so, the config object will be NULL. @@ -4278,9 +4288,16 @@ void ast_cdr_engine_term(void) } if (cdr_sync_message_type()) { + void *payload; + struct stasis_message *message; + + if (!stasis_router) { + return; + } + /* Make sure we have the needed items */ payload = ao2_alloc(sizeof(*payload), NULL); - if (!stasis_router || !payload) { + if (!payload) { return; } @@ -4290,6 +4307,8 @@ void ast_cdr_engine_term(void) if (message) { stasis_message_router_publish_sync(stasis_router, message); } + ao2_cleanup(message); + ao2_cleanup(payload); } if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) { diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 844d8a1cc..9e436ae3c 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -1411,6 +1411,18 @@ It only limits contacts added through external interaction, such as registration. </para> + <note><para>The <replaceable>rewrite_contact</replaceable> option + registers the source address as the contact address to help with + NAT and reusing connection oriented transports such as TCP and + TLS. Unfortunately, refreshing a registration may register a + different contact address and exceed + <replaceable>max_contacts</replaceable>. The + <replaceable>remove_existing</replaceable> option can help by + removing the soonest to expire contact(s) over + <replaceable>max_contacts</replaceable> which is likely the + old <replaceable>rewrite_contact</replaceable> contact source + address being refreshed. + </para></note> <note><para>This should be set to <literal>1</literal> and <replaceable>remove_existing</replaceable> set to <literal>yes</literal> if you wish to stick with the older <literal>chan_sip</literal> behaviour. @@ -1420,15 +1432,29 @@ <configOption name="minimum_expiration" default="60"> <synopsis>Minimum keep alive time for an AoR</synopsis> <description><para> - Minimum time to keep a peer with an explict expiration. Time in seconds. + Minimum time to keep a peer with an explicit expiration. Time in seconds. </para></description> </configOption> <configOption name="remove_existing" default="no"> <synopsis>Determines whether new contacts replace existing ones.</synopsis> <description><para> - On receiving a new registration to the AoR should it remove - the existing contact that was registered against it? + On receiving a new registration to the AoR should it remove enough + existing contacts not added or updated by the registration to + satisfy <replaceable>max_contacts</replaceable>? Any removed + contacts will expire the soonest. </para> + <note><para>The <replaceable>rewrite_contact</replaceable> option + registers the source address as the contact address to help with + NAT and reusing connection oriented transports such as TCP and + TLS. Unfortunately, refreshing a registration may register a + different contact address and exceed + <replaceable>max_contacts</replaceable>. The + <replaceable>remove_existing</replaceable> option can help by + removing the soonest to expire contact(s) over + <replaceable>max_contacts</replaceable> which is likely the + old <replaceable>rewrite_contact</replaceable> contact source + address being refreshed. + </para></note> <note><para>This should be set to <literal>yes</literal> and <replaceable>max_contacts</replaceable> set to <literal>1</literal> if you wish to stick with the older <literal>chan_sip</literal> behaviour. diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c index ba1c074b3..32906011a 100644 --- a/res/res_pjsip_registrar.c +++ b/res/res_pjsip_registrar.c @@ -129,7 +129,8 @@ static int registrar_find_contact(void *obj, void *arg, int flags) /*! \brief Internal function which validates provided Contact headers to confirm that they are acceptable, and returns number of contacts */ static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_container *contacts, struct ast_sip_aor *aor, int *added, int *updated, int *deleted) { - pjsip_contact_hdr *previous = NULL, *contact = (pjsip_contact_hdr *)&rdata->msg_info.msg->hdr; + pjsip_contact_hdr *previous = NULL; + pjsip_contact_hdr *contact = (pjsip_contact_hdr *)&rdata->msg_info.msg->hdr; struct registrar_contact_details details = { .pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Contact Comparison", 256, 256), }; @@ -140,15 +141,18 @@ static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_co while ((contact = (pjsip_contact_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact->next))) { int expiration = registrar_get_expiration(aor, contact, rdata); - RAII_VAR(struct ast_sip_contact *, existing, NULL, ao2_cleanup); + struct ast_sip_contact *existing; char contact_uri[pjsip_max_url_size]; if (contact->star) { /* The expiration MUST be 0 when a '*' contact is used and there must be no other contact */ - if ((expiration != 0) || previous) { + if (expiration != 0 || previous) { pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool); return -1; } + /* Count all contacts to delete */ + *deleted = ao2_container_count(contacts); + previous = contact; continue; } else if (previous && previous->star) { /* If there is a previous contact and it is a '*' this is a deal breaker */ @@ -177,14 +181,16 @@ static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_co } /* Determine if this is an add, update, or delete for policy enforcement purposes */ - if (!(existing = ao2_callback(contacts, 0, registrar_find_contact, &details))) { + existing = ao2_callback(contacts, 0, registrar_find_contact, &details); + ao2_cleanup(existing); + if (!existing) { if (expiration) { - (*added)++; + ++*added; } } else if (expiration) { - (*updated)++; + ++*updated; } else { - (*deleted)++; + ++*deleted; } } @@ -219,7 +225,7 @@ static int registrar_delete_contact(void *obj, void *arg, int flags) contact->user_agent); } - return 0; + return CMP_MATCH; } /*! \brief Internal function which adds a contact to a response */ @@ -351,6 +357,96 @@ static void register_contact_transport_shutdown_cb(void *data) ao2_ref(aor, -1); } +AST_VECTOR(excess_contact_vector, struct ast_sip_contact *); + +static int vec_contact_cmp(struct ast_sip_contact *left, struct ast_sip_contact *right) +{ + struct ast_sip_contact *left_contact = left; + struct ast_sip_contact *right_contact = right; + + /* Sort from soonest to expire to last to expire */ + return ast_tvcmp(left_contact->expiration_time, right_contact->expiration_time); +} + +static int vec_contact_add(void *obj, void *arg, int flags) +{ + struct ast_sip_contact *contact = obj; + struct excess_contact_vector *contact_vec = arg; + + /* + * Performance wise, an insertion sort is fine because we + * shouldn't need to remove more than a handful of contacts. + * I expect we'll typically be removing only one contact. + */ + AST_VECTOR_ADD_SORTED(contact_vec, contact, vec_contact_cmp); + if (AST_VECTOR_SIZE(contact_vec) == AST_VECTOR_MAX_SIZE(contact_vec)) { + /* + * We added a contact over the number we need to remove. + * Remove the longest to expire contact from the vector + * which is the last element in the vector. It may be + * the one we just added or the one we just added pushed + * out an earlier contact from removal consideration. + */ + --AST_VECTOR_SIZE(contact_vec); + } + return 0; +} + +/*! + * \internal + * \brief Remove excess existing contacts that expire the soonest. + * \since 13.18.0 + * + * \param contacts Container of unmodified contacts that could remove. + * \param to_remove Maximum number of contacts to remove. + * + * \return Nothing + */ +static void remove_excess_contacts(struct ao2_container *contacts, unsigned int to_remove) +{ + struct excess_contact_vector contact_vec; + + /* + * Create a sorted vector to hold the to_remove soonest to + * expire contacts. The vector has an extra space to + * temporarily hold the longest to expire contact that we + * won't remove. + */ + if (AST_VECTOR_INIT(&contact_vec, to_remove + 1)) { + return; + } + ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, vec_contact_add, &contact_vec); + + /* + * The vector should always be populated with the number + * of contacts we need to remove. Just in case, we will + * remove all contacts in the vector even if the contacts + * container had fewer contacts than there should be. + */ + ast_assert(AST_VECTOR_SIZE(&contact_vec) == to_remove); + to_remove = AST_VECTOR_SIZE(&contact_vec); + + /* Remove the excess contacts that expire the soonest */ + while (to_remove--) { + struct ast_sip_contact *contact; + + contact = AST_VECTOR_GET(&contact_vec, to_remove); + + ast_sip_location_delete_contact(contact); + ast_verb(3, "Removed contact '%s' from AOR '%s' due to remove_existing\n", + contact->uri, contact->aor); + ast_test_suite_event_notify("AOR_CONTACT_REMOVED", + "Contact: %s\r\n" + "AOR: %s\r\n" + "UserAgent: %s", + contact->uri, + contact->aor, + contact->user_agent); + } + + AST_VECTOR_FREE(&contact_vec); +} + static int register_aor_core(pjsip_rx_data *rdata, struct ast_sip_endpoint *endpoint, struct ast_sip_aor *aor, @@ -359,7 +455,10 @@ static int register_aor_core(pjsip_rx_data *rdata, { static const pj_str_t USER_AGENT = { "User-Agent", 10 }; - int added = 0, updated = 0, deleted = 0; + int added = 0; + int updated = 0; + int deleted = 0; + int contact_count; pjsip_contact_hdr *contact_hdr = NULL; struct registrar_contact_details details = { 0, }; pjsip_tx_data *tdata; @@ -396,7 +495,14 @@ static int register_aor_core(pjsip_rx_data *rdata, return PJ_TRUE; } - if ((MAX(added - deleted, 0) + (!aor->remove_existing ? ao2_container_count(contacts) : 0)) > aor->max_contacts) { + if (aor->remove_existing) { + /* Cumulative number of contacts affected by this registration */ + contact_count = MAX(updated + added - deleted, 0); + } else { + /* Total contacts after this registration */ + contact_count = ao2_container_count(contacts) + added - deleted; + } + if (contact_count > aor->max_contacts) { /* Enforce the maximum number of contacts */ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL); ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_exceeds_maximum_configured_contacts"); @@ -405,7 +511,9 @@ static int register_aor_core(pjsip_rx_data *rdata, return PJ_TRUE; } - if (!(details.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Contact Comparison", 256, 256))) { + details.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), + "Contact Comparison", 256, 256); + if (!details.pool) { pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); return PJ_TRUE; } @@ -446,7 +554,8 @@ static int register_aor_core(pjsip_rx_data *rdata, if (contact_hdr->star) { /* A star means to unregister everything, so do so for the possible contacts */ - ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, registrar_delete_contact, (void *)aor_name); + ao2_callback(contacts, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, + registrar_delete_contact, (void *)aor_name); break; } @@ -459,7 +568,8 @@ static int register_aor_core(pjsip_rx_data *rdata, details.uri = pjsip_uri_get_uri(contact_hdr->uri); pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, details.uri, contact_uri, sizeof(contact_uri)); - if (!(contact = ao2_callback(contacts, OBJ_UNLINK, registrar_find_contact, &details))) { + contact = ao2_callback(contacts, OBJ_UNLINK, registrar_find_contact, &details); + if (!contact) { int prune_on_boot = 0; pj_str_t host_name; @@ -600,15 +710,29 @@ static int register_aor_core(pjsip_rx_data *rdata, pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool); - /* If the AOR is configured to remove any existing contacts that have not been updated/added as a result of this REGISTER - * do so + /* + * If the AOR is configured to remove any contacts over max_contacts + * that have not been updated/added/deleted as a result of this + * REGISTER do so. + * + * The contacts container currently holds the existing contacts that + * were not affected by this REGISTER. */ if (aor->remove_existing) { - ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, registrar_delete_contact, NULL); + /* Total contacts after this registration */ + contact_count = ao2_container_count(contacts) + updated + added; + if (contact_count > aor->max_contacts) { + /* Remove excess existing contacts that expire the soonest */ + remove_excess_contacts(contacts, contact_count - aor->max_contacts); + } } /* Re-retrieve contacts. Caller will clean up the original container. */ contacts = ast_sip_location_retrieve_aor_contacts_nolock(aor); + if (!contacts) { + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); + return PJ_TRUE; + } response_contact = ao2_callback(contacts, 0, NULL, NULL); /* Send a response containing all of the contacts (including static) that are present on this AOR */ |