diff --git a/CHANGES b/CHANGES index 53accaa79891ec3023444578bf4e7e34c6d0041d..ca167fe215998094b3baf739f550846553ebeca5 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,13 @@ SIP Changes * The ATTENDED_TRANSFER_COMPLETE_SOUND can now be set using setvar to cause a given audio file to be played upon completion of an attended transfer. * Added DNS manager support to registrations for peers referencing peer entries. + * Performance improvements via using hash tables (astobj2) and doubly-linked lists to improve + load/reload of large numbers of peers/users by ~40x (for large lists of peers. + Initially, we saw 4x improvement in call setup/destruction, but at the time + of merging, this gain has disappeared; further research will be done to try + and restore this performance improvement. Astobj2 refcounting is now used + for users, peers, and dialogs. Users are encouraged to assist in regression + testing and problem reporting! IAX Changes ----------- diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 44eb1c163b37b16b6032cc268bb9c2757765ec37..df0806740fde2c1e3f102574ca852ae3f61a1882 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -1,4 +1,4 @@ -/* + /* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2006, Digium, Inc. @@ -136,6 +136,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include <signal.h> #include <sys/signal.h> #include <regex.h> +#include <time.h> #include "asterisk/network.h" #include "asterisk/paths.h" /* need ast_config_AST_SYSTEM_NAME */ @@ -163,6 +164,19 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/utils.h" #include "asterisk/file.h" #include "asterisk/astobj.h" +/* + Uncomment the define below, if you are having refcount related memory leaks. + With this uncommented, this module will generate a file, /tmp/refs, which contains + a history of the ao2_ref() calls. To be useful, all calls to ao2_* functions should + be modified to ao2_t_* calls, and include a tag describing what is happening with + enough detail, to make pairing up a reference count increment with its corresponding decrement. + The refcounter program in utils/ can be invaluable in highlighting objects that are not + balanced, along with the complete history for that object. + In normal operation, the macros defined will throw away the tags, so they do not + affect the speed of the program at all. They can be considered to be documentation. +*/ +/* #define REF_DEBUG 1 */ +#include "asterisk/astobj2.h" #include "asterisk/dnsmgr.h" #include "asterisk/devicestate.h" #include "asterisk/linkedlists.h" @@ -1161,7 +1175,6 @@ struct sip_st_cfg { */ struct sip_pvt { struct sip_pvt *next; /*!< Next dialog in chain */ - ast_mutex_t pvt_lock; /*!< Dialog private lock */ enum invitestates invitestate; /*!< Track state of SIP_INVITEs */ int method; /*!< SIP method that opened this dialog */ AST_DECLARE_STRING_FIELDS( @@ -1314,43 +1327,48 @@ struct sip_pvt { * the container and individual items, and functions to add/remove * references to the individual items. */ -static struct sip_pvt *dialoglist = NULL; +struct ao2_container *dialogs; -/*! \brief Protect the SIP dialog list (of sip_pvt's) */ -AST_MUTEX_DEFINE_STATIC(dialoglock); - -#ifndef DETECT_DEADLOCKS -/*! \brief hide the way the list is locked/unlocked */ -static void dialoglist_lock(void) +/*! + * when we create or delete references, make sure to use these + * functions so we keep track of the refcounts. + * To simplify the code, we allow a NULL to be passed to dialog_unref(). + */ +#ifdef REF_DEBUG +#define dialog_ref(arg1,arg2) dialog_ref_debug((arg1),(arg2), __FILE__, __LINE__, __PRETTY_FUNCTION__) +#define dialog_unref(arg1,arg2) dialog_unref_debug((arg1),(arg2), __FILE__, __LINE__, __PRETTY_FUNCTION__) +static struct sip_pvt *dialog_ref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func) { - ast_mutex_lock(&dialoglock); + if (p) + _ao2_ref_debug(p, 1, tag, file, line, func); + else + ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n"); + return p; } -static void dialoglist_unlock(void) +static struct sip_pvt *dialog_unref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func) { - ast_mutex_unlock(&dialoglock); + if (p) + _ao2_ref_debug(p, -1, tag, file, line, func); + return NULL; } #else -/* we don't want to HIDE the information about where the lock was requested if trying to debug - * deadlocks! So, just make these macros! */ -#define dialoglist_lock(x) ast_mutex_lock(&dialoglock) -#define dialoglist_unlock(x) ast_mutex_unlock(&dialoglock) -#endif - -/*! - * when we create or delete references, make sure to use these - * functions so we keep track of the refcounts. - * To simplify the code, we allow a NULL to be passed to dialog_unref(). - */ -static struct sip_pvt *dialog_ref(struct sip_pvt *p) +static struct sip_pvt *dialog_ref(struct sip_pvt *p, char *tag) { + if (p) + ao2_ref(p, 1); + else + ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n"); return p; } -static struct sip_pvt *dialog_unref(struct sip_pvt *p) +static struct sip_pvt *dialog_unref(struct sip_pvt *p, char *tag) { + if (p) + ao2_ref(p, -1); return NULL; } +#endif /*! \brief sip packet - raw format for outbound packets that are sent or scheduled for transmission * Packets are linked in a list, whose head is in the struct sip_pvt they belong to. @@ -1376,7 +1394,7 @@ struct sip_pkt { /*! \brief Structure for SIP user data. User's place calls to us */ struct sip_user { /* Users who can access various contexts */ - ASTOBJ_COMPONENTS(struct sip_user); + char name[80]; char secret[80]; /*!< Password */ char md5secret[80]; /*!< Password in md5 */ char context[AST_MAX_CONTEXT]; /*!< Default context for incoming calls */ @@ -1396,6 +1414,7 @@ struct sip_user { /* things that don't belong in flags */ char is_realtime; /*!< this is a 'realtime' user */ + unsigned int the_mark:1; /*!< moved out of the ASTOBJ fields; that which bears the_mark should be deleted! */ int amaflags; /*!< AMA flags for billing */ int callingpres; /*!< Calling id presentation */ @@ -1427,8 +1446,7 @@ struct sip_mailbox { /*! \brief Structure for SIP peer data, we place calls to peers if registered or fixed IP address (host) */ /* XXX field 'name' must be first otherwise sip_addrcmp() will fail */ struct sip_peer { - ASTOBJ_COMPONENTS(struct sip_peer); /*!< name, refcount, objflags, object pointers */ - /*!< peer->name is the unique name of this object */ + char name[80]; /*!< peer->name is the unique name of this object */ struct sip_socket socket; /*!< Socket used for this peer */ char secret[80]; /*!< Password */ char md5secret[80]; /*!< Password in MD5 */ @@ -1470,6 +1488,7 @@ struct sip_peer { char rt_fromcontact; /*!< P: copy fromcontact from realtime */ char host_dynamic; /*!< P: Dynamic Peers register with Asterisk */ char selfdestruct; /*!< P: Automatic peers need to destruct themselves */ + char the_mark; /*!< moved out of ASTOBJ into struct proper; That which bears the_mark should be deleted! */ int expire; /*!< When to expire this peer registration */ int capability; /*!< Codec capability */ @@ -1560,20 +1579,27 @@ struct sip_threadinfo { AST_LIST_ENTRY(sip_threadinfo) list; }; -/* --- Linked lists of various objects --------*/ +/* --- Hash tables of various objects --------*/ + +#ifdef LOW_MEMORY +static int hash_peer_size = 17; +static int hash_dialog_size = 17; +static int hash_user_size = 17; +#else +static int hash_peer_size = 563; +static int hash_dialog_size = 563; +static int hash_user_size = 563; +#endif /*! \brief The thread list of TCP threads */ static AST_LIST_HEAD_STATIC(threadl, sip_threadinfo); /*! \brief The user list: Users and friends */ -static struct ast_user_list { - ASTOBJ_CONTAINER_COMPONENTS(struct sip_user); -} userl; +static struct ao2_container *users; /*! \brief The peer list: Peers and Friends */ -static struct ast_peer_list { - ASTOBJ_CONTAINER_COMPONENTS(struct sip_peer); -} peerl; +struct ao2_container *peers; +struct ao2_container *peers_by_ip; /*! \brief The register list: Other SIP proxies we register with and place calls to */ static struct ast_register_list { @@ -1581,6 +1607,102 @@ static struct ast_register_list { int recheck; } regl; +/*! + * \note The only member of the peer used here is the name field + */ +static int peer_hash_cb(const void *obj, const int flags) +{ + const struct sip_peer *peer = obj; + + return ast_str_hash(peer->name); +} + +/*! + * \note The only member of the peer used here is the name field + */ +static int peer_cmp_cb(void *obj, void *arg, int flags) +{ + struct sip_peer *peer = obj, *peer2 = arg; + + return !strcasecmp(peer->name, peer2->name) ? CMP_MATCH : 0; +} + +/*! + * \note the peer's addr struct provides to fields combined to make a key: the sin_addr.s_addr and sin_port fields. + */ +static int peer_iphash_cb(const void *obj, const int flags) +{ + const struct sip_peer *peer = obj; + int ret1 = peer->addr.sin_addr.s_addr; + if (ret1 < 0) + ret1 = -ret1; + + if (ast_test_flag(&peer->flags[0], SIP_INSECURE_PORT)) { + return ret1; + } else { + return ret1 + peer->addr.sin_port; + } +} + +/*! + * \note the peer's addr struct provides to fields combined to make a key: the sin_addr.s_addr and sin_port fields. + */ +static int peer_ipcmp_cb(void *obj, void *arg, int flags) +{ + struct sip_peer *peer = obj, *peer2 = arg; + + if (peer->addr.sin_addr.s_addr != peer2->addr.sin_addr.s_addr) + return 0; + + if (!ast_test_flag(&peer->flags[0], SIP_INSECURE_PORT) && !ast_test_flag(&peer2->flags[0], SIP_INSECURE_PORT)) { + if (peer->addr.sin_port == peer2->addr.sin_port) + return CMP_MATCH; + else + return 0; + } + return CMP_MATCH; +} + +/*! + * \note The only member of the user used here is the name field + */ +static int user_hash_cb(const void *obj, const int flags) +{ + const struct sip_user *user = obj; + + return ast_str_hash(user->name); +} + +/*! + * \note The only member of the user used here is the name field + */ +static int user_cmp_cb(void *obj, void *arg, int flags) +{ + struct sip_user *user = obj, *user2 = arg; + + return !strcasecmp(user->name, user2->name) ? CMP_MATCH : 0; +} + +/*! + * \note The only member of the dialog used here callid string + */ +static int dialog_hash_cb(const void *obj, const int flags) +{ + const struct sip_pvt *pvt = obj; + + return ast_str_hash(pvt->callid); +} + +/*! + * \note The only member of the dialog used here callid string + */ +static int dialog_cmp_cb(void *obj, void *arg, int flags) +{ + struct sip_pvt *pvt = obj, *pvt2 = arg; + + return !strcasecmp(pvt->callid, pvt2->callid) ? CMP_MATCH : 0; +} + static int temp_pvt_init(void *); static void temp_pvt_cleanup(void *); @@ -1741,7 +1863,9 @@ static int __sip_autodestruct(const void *data); static void sip_scheddestroy(struct sip_pvt *p, int ms); static int sip_cancel_destroy(struct sip_pvt *p); static struct sip_pvt *sip_destroy(struct sip_pvt *p); -static int __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist); +static void *dialog_unlink_all(struct sip_pvt *dialog, int lockowner, int lockdialoglist); +static void *registry_unref(struct sip_registry *reg, char *tag); +static void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist); static void __sip_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod); static void __sip_pretend_ack(struct sip_pvt *p); static int __sip_semi_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod); @@ -1799,12 +1923,14 @@ static int clear_realm_authentication(struct sip_auth *authlist); /* Clear realm static struct sip_auth *find_realm_authentication(struct sip_auth *authlist, const char *realm); /*--- Misc functions */ +static void check_rtp_timeout(struct sip_pvt *dialog, time_t t); static int sip_do_reload(enum channelreloadreason reason); static int reload_config(enum channelreloadreason reason); static int expire_register(const void *data); static void *do_monitor(void *data); static int restart_monitor(void); -static int sip_addrcmp(char *name, struct sockaddr_in *sin); /* Support for peer matching */ +static void peer_mailboxes_to_str(struct ast_str **mailbox_str, struct sip_peer *peer); +/* static int sip_addrcmp(char *name, struct sockaddr_in *sin); Support for peer matching */ static int sip_refer_allocate(struct sip_pvt *p); static void ast_quiet_chan(struct ast_channel *chan); static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target); @@ -1825,8 +1951,11 @@ static char *transfermode2str(enum transfermodes mode) attribute_const; static const char *nat2str(int nat) attribute_const; static int peer_status(struct sip_peer *peer, char *status, int statuslen); static char *sip_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); +static char *sip_show_sched(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char * _sip_show_peers(int fd, int *total, struct mansession *s, const struct message *m, int argc, const char *argv[]); static char *sip_show_peers(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); +static char *_sip_dbdump(int fd, int *total, struct mansession *s, const struct message *m, int argc, const char *argv[]); +static char *sip_dbdump(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *sip_show_objects(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static void print_group(int fd, ast_group_t group, int crlf); static const char *dtmfmode2str(int mode) attribute_const; @@ -1887,7 +2016,9 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str static struct sip_user *build_user(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime); static int update_call_counter(struct sip_pvt *fup, int event); static void sip_destroy_peer(struct sip_peer *peer); +static void sip_destroy_peer_fn(void *peer); static void sip_destroy_user(struct sip_user *user); +static void sip_destroy_user_fn(void *user); static int sip_poke_peer(struct sip_peer *peer); static void set_peer_defaults(struct sip_peer *peer); static struct sip_peer *temp_peer(const char *name); @@ -1919,6 +2050,7 @@ static int sip_reregister(const void *data); static int __sip_do_register(struct sip_registry *r); static int sip_reg_timeout(const void *data); static void sip_send_all_registers(void); +static int sip_reinvite_retry(const void *data); /*--- Parsing SIP requests and responses */ static void append_date(struct sip_request *req); /* Append date to SIP packet */ @@ -2267,26 +2399,93 @@ cleanup2: return NULL; } -#define sip_pvt_lock(x) ast_mutex_lock(&x->pvt_lock) -#define sip_pvt_trylock(x) ast_mutex_trylock(&x->pvt_lock) -#define sip_pvt_unlock(x) ast_mutex_unlock(&x->pvt_lock) +#define sip_pvt_lock(x) ao2_lock(x) +#define sip_pvt_trylock(x) ao2_trylock(x) +#define sip_pvt_unlock(x) ao2_unlock(x) /*! * helper functions to unreference various types of objects. * By handling them this way, we don't have to declare the * destructor on each call, which removes the chance of errors. */ -static void unref_peer(struct sip_peer *peer) +static void *unref_peer(struct sip_peer *peer, char *tag) { - ASTOBJ_UNREF(peer, sip_destroy_peer); + ao2_t_ref(peer, -1, tag); + return NULL; } -static void unref_user(struct sip_user *user) +static void *unref_user(struct sip_user *user, char *tag) { - ASTOBJ_UNREF(user, sip_destroy_user); + ao2_t_ref(user, -1, tag); + return NULL; } -static void *registry_unref(struct sip_registry *reg) +static struct sip_peer *ref_peer(struct sip_peer *peer, char *tag) +{ + ao2_t_ref(peer, 1,tag); + return peer; +} + +/*! + * \brief Unlink a dialog from the dialogs container, as well as any other places + * that it may be currently stored. + * + * \note A reference to the dialog must be held before calling this function, and this + * function does not release that reference. + */ +static void *dialog_unlink_all(struct sip_pvt *dialog, int lockowner, int lockdialoglist) +{ + struct sip_pkt *cp; + + dialog_ref(dialog, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done"); + + ao2_t_unlink(dialogs, dialog, "unlinking dialog via ao2_unlink"); + + /* Unlink us from the owner (channel) if we have one */ + if (dialog->owner) { + if (lockowner) + ast_channel_lock(dialog->owner); + ast_debug(1, "Detaching from channel %s\n", dialog->owner->name); + dialog->owner->tech_pvt = dialog_unref(dialog->owner->tech_pvt, "resetting channel dialog ptr in unlink_all"); + if (lockowner) + ast_channel_unlock(dialog->owner); + } + if (dialog->registry) { + if (dialog->registry->call == dialog) + dialog->registry->call = dialog_unref(dialog->registry->call, "nulling out the registry's call dialog field in unlink_all"); + dialog->registry = registry_unref(dialog->registry, "delete dialog->registry"); + } + if (dialog->stateid > -1) { + ast_extension_state_del(dialog->stateid, NULL); + dialog_unref(dialog, "removing extension_state, should unref the associated dialog ptr that was stored there."); + dialog->stateid = -1; /* shouldn't we 'zero' this out? */ + } + /* Remove link from peer to subscription of MWI */ + if (dialog->relatedpeer && dialog->relatedpeer->mwipvt) + dialog->relatedpeer->mwipvt = dialog_unref(dialog->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt"); + if (dialog->relatedpeer && dialog->relatedpeer->call == dialog) + dialog->relatedpeer->call = dialog_unref(dialog->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself"); + + /* remove all current packets in this dialog */ + while((cp = dialog->packets)) { + dialog->packets = dialog->packets->next; + AST_SCHED_DEL(sched, cp->retransid); + dialog_unref(cp->owner, "remove all current packets in this dialog, and the pointer to the dialog too as part of __sip_destroy"); + ast_free(cp); + } + + AST_SCHED_DEL_UNREF(sched, dialog->waitid, dialog_unref(dialog, "when you delete the waitid sched, you should dec the refcount for the stored dialog ptr")); + + AST_SCHED_DEL_UNREF(sched, dialog->initid, dialog_unref(dialog, "when you delete the initid sched, you should dec the refcount for the stored dialog ptr")); + + if (dialog->autokillid > -1) + AST_SCHED_DEL_UNREF(sched, dialog->autokillid, dialog_unref(dialog, "when you delete the autokillid sched, you should dec the refcount for the stored dialog ptr")); + + dialog_unref(dialog, "Let's unbump the count in the unlink so the poor pvt can disappear if it is time"); + return NULL; +} + +static void *registry_unref(struct sip_registry *reg, char *tag) { ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount - 1); ASTOBJ_UNREF(reg, sip_registry_destroy); @@ -2294,7 +2493,7 @@ static void *registry_unref(struct sip_registry *reg) } /*! \brief Add object reference to SIP registry */ -static struct sip_registry *registry_addref(struct sip_registry *reg) +static struct sip_registry *registry_addref(struct sip_registry *reg, char *tag) { ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount + 1); return ASTOBJ_REF(reg); /* Add pointer to registry in packet */ @@ -2689,7 +2888,7 @@ static int retrans_pkt(const void *data) struct sip_pkt *pkt = (struct sip_pkt *)data, *prev, *cur = NULL; int reschedule = DEFAULT_RETRANS; int xmitres = 0; - + /* Lock channel PVT */ sip_pvt_lock(pkt->owner); @@ -2792,8 +2991,11 @@ static int retrans_pkt(const void *data) if (cur == pkt) { UNLINK(cur, pkt->owner->packets, prev); sip_pvt_unlock(pkt->owner); + if (pkt->owner) + pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now"); if (pkt->data) ast_free(pkt->data); + pkt->data = NULL; ast_free(pkt); return 0; } @@ -2822,7 +3024,7 @@ static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, int seqno, int res /* I removed the code from retrans_pkt that does the same thing so it doesn't get loaded into the scheduler */ /* According to the RFC some packets need to be retransmitted even if its TCP, so this needs to get revisited */ if (!(p->socket.type & SIP_TRANSPORT_UDP)) { - xmitres = __sip_xmit(dialog_ref(p), data, len); /* Send packet */ + xmitres = __sip_xmit(dialog_ref(p, "pasing dialog ptr into callback..."), data, len); /* Send packet */ if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */ append_history(p, "XmitErr", "%s", fatal ? "(Critical)" : "(Non-critical)"); return AST_FAILURE; @@ -2844,7 +3046,7 @@ static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, int seqno, int res pkt->seqno = seqno; pkt->is_resp = resp; pkt->is_fatal = fatal; - pkt->owner = dialog_ref(p); + pkt->owner = dialog_ref(p, "__sip_reliable_xmit: setting pkt->owner"); pkt->next = p->packets; p->packets = pkt; /* Add it to the queue */ pkt->timer_t1 = p->timer_t1; /* Set SIP timer T1 */ @@ -2861,6 +3063,7 @@ static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, int seqno, int res if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */ append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)"); + ast_log(LOG_ERROR, "Serious Network Trouble; __sip_xmit returns error for pkt data\n"); if (pkt->data) ast_free(pkt->data); return AST_FAILURE; @@ -2896,7 +3099,7 @@ static int __sip_autodestruct(const void *data) if (p->subscribed == MWI_NOTIFICATION) if (p->relatedpeer) - unref_peer(p->relatedpeer); /* Remove link to peer. If it's realtime, make sure it's gone from memory) */ + p->relatedpeer = unref_peer(p->relatedpeer, "__sip_autodestruct: unref peer p->relatedpeer"); /* Remove link to peer. If it's realtime, make sure it's gone from memory) */ /* Reset schedule ID */ p->autokillid = -1; @@ -2904,19 +3107,20 @@ static int __sip_autodestruct(const void *data) if (p->owner) { ast_log(LOG_WARNING, "Autodestruct on dialog '%s' with owner in place (Method: %s)\n", p->callid, sip_methods[p->method].text); ast_queue_hangup(p->owner); - dialog_unref(p); } else if (p->refer) { ast_debug(3, "Finally hanging up channel after transfer: %s\n", p->callid); transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1); append_history(p, "ReferBYE", "Sending BYE on transferer call leg %s", p->callid); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); - dialog_unref(p); } else { append_history(p, "AutoDestroy", "%s", p->callid); ast_debug(3, "Auto destroying SIP dialog '%s'\n", p->callid); - sip_destroy(p); /* Go ahead and destroy dialog. All attempts to recover is done */ + dialog_unlink_all(p, TRUE, TRUE); /* once it's unlinked and unrefd everywhere, it'll be freed automagically */ + /* dialog_unref(p, "unref dialog-- no other matching conditions"); -- unlink all now should finish off the dialog's references and free it. */ + /* sip_destroy(p); */ /* Go ahead and destroy dialog. All attempts to recover is done */ /* sip_destroy also absorbs the reference */ } + dialog_unref(p, "The ref to a dialog passed to this sched callback is going out of scope; unref it."); return 0; } @@ -2937,7 +3141,7 @@ static void sip_scheddestroy(struct sip_pvt *p, int ms) if (p->do_history) append_history(p, "SchedDestroy", "%d ms", ms); - p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p)); + p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p, "setting ref as passing into ast_sched_add for __sip_autodestruct")); if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_schedid > 0) stop_session_timer(p); @@ -2951,11 +3155,13 @@ static int sip_cancel_destroy(struct sip_pvt *p) { int res = 0; if (p->autokillid > -1) { - if (!(res = ast_sched_del(sched, p->autokillid))) { + int res3; + + if (!(res3 = ast_sched_del(sched, p->autokillid))) { append_history(p, "CancelDestroy", ""); p->autokillid = -1; + dialog_unref(p, "dialog unrefd because autokillid is de-sched'd"); } - dialog_unref(p); } return res; } @@ -3010,7 +3216,7 @@ static void __sip_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod) sip_pvt_lock(p); } UNLINK(cur, p->packets, prev); - dialog_unref(cur->owner); + dialog_unref(cur->owner, "unref pkt cur->owner dialog from sip ack before freeing pkt"); if (cur->data) ast_free(cur->data); ast_free(cur); @@ -3491,27 +3697,36 @@ static void clear_peer_mailboxes(struct sip_peer *peer) destroy_mailbox(mailbox); } +static void sip_destroy_peer_fn(void *peer) +{ + sip_destroy_peer(peer); +} + /*! \brief Destroy peer object from memory */ static void sip_destroy_peer(struct sip_peer *peer) { ast_debug(3, "Destroying SIP peer %s\n", peer->name); - if (peer->outboundproxy) ast_free(peer->outboundproxy); peer->outboundproxy = NULL; /* Delete it, it needs to disappear */ - if (peer->call) - peer->call = sip_destroy(peer->call); - - if (peer->mwipvt) /* We have an active subscription, delete it */ - peer->mwipvt = sip_destroy(peer->mwipvt); + if (peer->call) { + dialog_unlink_all(peer->call, TRUE, TRUE); + peer->call = dialog_unref(peer->call, "peer->call is being unset"); + } + + if (peer->mwipvt) { /* We have an active subscription, delete it */ + dialog_unlink_all(peer->mwipvt, TRUE, TRUE); + peer->mwipvt = dialog_unref(peer->mwipvt, "unreffing peer->mwipvt"); + } + if (peer->chanvars) { ast_variables_destroy(peer->chanvars); peer->chanvars = NULL; } - + /* If the schedule delete fails, that means the schedule is currently * running, which means we should wait for that thread to complete. * Otherwise, there's a crashable race condition. @@ -3524,18 +3739,17 @@ static void sip_destroy_peer(struct sip_peer *peer) register_peer_exten(peer, FALSE); ast_free_ha(peer->ha); if (peer->selfdestruct) - apeerobjs--; + ast_atomic_fetchadd_int(&apeerobjs, -1); else if (peer->is_realtime) { - rpeerobjs--; + ast_atomic_fetchadd_int(&rpeerobjs, -1); ast_debug(3, "-REALTIME- peer Destroyed. Name: %s. Realtime Peer objects: %d\n", peer->name, rpeerobjs); } else - speerobjs--; + ast_atomic_fetchadd_int(&speerobjs, -1); clear_realm_authentication(peer->auth); peer->auth = NULL; if (peer->dnsmgr) ast_dnsmgr_release(peer->dnsmgr); clear_peer_mailboxes(peer); - ast_free(peer); } /*! \brief Update peer data in database (if used) */ @@ -3578,6 +3792,8 @@ static const char *get_name_from_variable(struct ast_variable *var, const char * /*! \brief realtime_peer: Get peer from realtime storage * Checks the "sippeers" realtime family from extconfig.conf * Checks the "sipregs" realtime family from extconfig.conf if it's configured. + * This returns a pointer to a peer and because we use build_peer, we can rest + * assured that the refcount is bumped. */ static struct sip_peer *realtime_peer(const char *newpeername, struct sockaddr_in *sin) { @@ -3743,8 +3959,14 @@ static struct sip_peer *realtime_peer(const char *newpeername, struct sockaddr_i ast_copy_flags(&peer->flags[1], &global_flags[1], SIP_PAGE2_RTAUTOCLEAR|SIP_PAGE2_RTCACHEFRIENDS); if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTAUTOCLEAR)) { AST_SCHED_REPLACE(peer->expire, sched, global_rtautoclear * 1000, expire_register, (void *) peer); + /* we could be incr. its refcount right here, but I guess, since + peers hang around until module unload time anyway, it's not worth the trouble */ } - ASTOBJ_CONTAINER_LINK(&peerl, peer); + ao2_t_link(peers, peer, "link peer into peers table"); + if (peer->addr.sin_addr.s_addr) { + ao2_t_link(peers_by_ip, peer, "link peer into peers_by_ip table"); + } + } else { peer->is_realtime = 1; } @@ -3758,30 +3980,33 @@ static struct sip_peer *realtime_peer(const char *newpeername, struct sockaddr_i return peer; } -/*! \brief Support routine for find_peer */ -static int sip_addrcmp(char *name, struct sockaddr_in *sin) -{ - /* We know name is the first field, so we can cast */ - struct sip_peer *p = (struct sip_peer *) name; - return !(!inaddrcmp(&p->addr, sin) || - (ast_test_flag(&p->flags[0], SIP_INSECURE_PORT) && - (p->addr.sin_addr.s_addr == sin->sin_addr.s_addr))); -} - /*! \brief Locate peer by name or ip address * This is used on incoming SIP message to find matching peer on ip or outgoing message to find matching peer on name - \note Avoid using this function in new functions if there's a way to avoid it, i - since it causes a database lookup or a traversal of the in-memory peer list. + \note Avoid using this function in new functions if there is a way to avoid it, i + since it might cause a database lookup. */ static struct sip_peer *find_peer(const char *peer, struct sockaddr_in *sin, int realtime) { struct sip_peer *p = NULL; - - if (peer) - p = ASTOBJ_CONTAINER_FIND(&peerl, peer); - else - p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, name, sip_addr_hashfunc, 1, sip_addrcmp); + struct sip_peer tmp_peer; + + if (peer) { + ast_copy_string(tmp_peer.name, peer, sizeof(tmp_peer.name)); + p = ao2_t_find(peers, &tmp_peer, OBJ_POINTER, "ao2_find in peers table"); + } else if (sin) { /* search by addr? */ + tmp_peer.addr.sin_addr.s_addr = sin->sin_addr.s_addr; + tmp_peer.addr.sin_port = sin->sin_port; + tmp_peer.flags[0].flags = 0; + p = ao2_t_find(peers_by_ip, &tmp_peer, OBJ_POINTER, "ao2_find in peers_by_ip table"); /* WAS: p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, name, sip_addr_hashfunc, 1, sip_addrcmp); */ + if (!p) { + ast_set_flag(&tmp_peer.flags[0], SIP_INSECURE_PORT); + p = ao2_t_find(peers_by_ip, &tmp_peer, OBJ_POINTER, "ao2_find in peers_by_ip table 2"); /* WAS: p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, name, sip_addr_hashfunc, 1, sip_addrcmp); */ + if (p) { + return p; + } + } + } if (!p && realtime) p = realtime_peer(peer, sin); @@ -3789,6 +4014,12 @@ static struct sip_peer *find_peer(const char *peer, struct sockaddr_in *sin, int return p; } +static void sip_destroy_user_fn(void *user) +{ + sip_destroy_user(user); +} + + /*! \brief Remove user object from in-memory storage */ static void sip_destroy_user(struct sip_user *user) { @@ -3800,14 +4031,14 @@ static void sip_destroy_user(struct sip_user *user) user->chanvars = NULL; } if (user->is_realtime) - ruserobjs--; + ast_atomic_fetchadd_int(&ruserobjs, -1); else - suserobjs--; - ast_free(user); + ast_atomic_fetchadd_int(&suserobjs, -1); } /*! \brief Load user from realtime storage * Loads user from "sipusers" category in realtime (extconfig.conf) + * returns a refcounted pointer to a sip_user structure. * Users are matched on From: user name (the domain in skipped) */ static struct sip_user *realtime_user(const char *username) { @@ -3837,12 +4068,12 @@ static struct sip_user *realtime_user(const char *username) if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { ast_set_flag(&user->flags[1], SIP_PAGE2_RTCACHEFRIENDS); - suserobjs++; - ASTOBJ_CONTAINER_LINK(&userl, user); + ast_atomic_fetchadd_int(&suserobjs, 1); + ao2_t_link(users, user, "link user into users table"); } else { /* Move counter from s to r... */ - suserobjs--; - ruserobjs++; + ast_atomic_fetchadd_int(&suserobjs, -1); + ast_atomic_fetchadd_int(&ruserobjs, 1); user->is_realtime = 1; } ast_variables_destroy(var); @@ -3852,10 +4083,20 @@ static struct sip_user *realtime_user(const char *username) /*! \brief Locate user by name * Locates user by name (From: sip uri user name part) first * from in-memory list (static configuration) then from - * realtime storage (defined in extconfig.conf) */ + * realtime storage (defined in extconfig.conf) + * \return a refcounted pointer to a user. Make sure the + * the ref count is decremented when this pointer is nulled, changed, + * or goes out of scope! + */ + static struct sip_user *find_user(const char *name, int realtime) { - struct sip_user *u = ASTOBJ_CONTAINER_FIND(&userl, name); + struct sip_user tmp; + struct sip_user *u; + + ast_copy_string(tmp.name, name, sizeof(tmp.name)); + u = ao2_t_find(users, &tmp, OBJ_POINTER, "ao2_find in users table"); + if (!u && realtime) u = realtime_user(name); return u; @@ -3935,6 +4176,7 @@ static void set_t38_capabilities(struct sip_pvt *p) * again from memory or database during the life time of the dialog. * * \return -1 on error, 0 on success. + * */ static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer) { @@ -4018,11 +4260,13 @@ static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer) if (!dialog->initreq.headers) { char *c; char *tmpcall = ast_strdupa(dialog->callid); - + /* this sure looks to me like we are going to change the callid on this dialog!! */ c = strchr(tmpcall, '@'); if (c) { *c = '\0'; + ao2_t_unlink(dialogs, dialog, "About to change the callid -- remove the old name"); ast_string_field_build(dialog, callid, "%s@%s", tmpcall, peer->fromdomain); + ao2_t_link(dialogs, dialog, "New dialog callid -- inserted back into table"); } } } @@ -4082,7 +4326,7 @@ static int create_addr(struct sip_pvt *dialog, const char *opeer, struct sockadd if (peer) { int res = create_addr_from_peer(dialog, peer); - unref_peer(peer); + unref_peer(peer, "create_addr: unref peer from find_peer hashtab lookup"); return res; } else { /* Setup default parameters for this dialog's socket. Currently we only support regular UDP SIP as the default */ @@ -4150,14 +4394,13 @@ static int auto_congest(const void *arg) if (p->owner) { /* XXX fails on possible deadlock */ if (!ast_channel_trylock(p->owner)) { - ast_log(LOG_NOTICE, "Auto-congesting %s\n", p->owner->name); append_history(p, "Cong", "Auto-congesting (timer)"); ast_queue_control(p->owner, AST_CONTROL_CONGESTION); ast_channel_unlock(p->owner); } } sip_pvt_unlock(p); - dialog_unref(p); + dialog_unref(p, "unreffing arg passed into auto_congest callback (p->initid)"); return 0; } @@ -4245,9 +4488,11 @@ static int sip_call(struct ast_channel *ast, char *dest, int timeout) p->invitestate = INV_CALLING; /* Initialize auto-congest time */ - AST_SCHED_REPLACE(p->initid, sched, p->timer_b, auto_congest, dialog_ref(p)); + AST_SCHED_REPLACE_UNREF(p->initid, sched, p->timer_b, auto_congest, p, + dialog_unref(_data, "dialog ptr dec when SCHED_REPLACE del op succeeded"), + dialog_unref(p, "dialog ptr dec when SCHED_REPLACE add failed"), + dialog_ref(p, "dialog ptr inc when SCHED_REPLACE add succeeded") ); } - return res; } @@ -4261,35 +4506,25 @@ static void sip_registry_destroy(struct sip_registry *reg) if (reg->call) { /* Clear registry before destroying to ensure we don't get reentered trying to grab the registry lock */ - reg->call->registry = NULL; + reg->call->registry = registry_unref(reg->call->registry, "destroy reg->call->registry"); ast_debug(3, "Destroying active SIP dialog for registry %s@%s\n", reg->username, reg->hostname); - reg->call = sip_destroy(reg->call); + dialog_unlink_all(reg->call, TRUE, TRUE); + reg->call = dialog_unref(reg->call, "unref reg->call"); + /* reg->call = sip_destroy(reg->call); */ } - AST_SCHED_DEL(sched, reg->expire); + AST_SCHED_DEL(sched, reg->expire); AST_SCHED_DEL(sched, reg->timeout); + ast_string_field_free_memory(reg); - regobjs--; + ast_atomic_fetchadd_int(®objs, -1); ast_dnsmgr_release(reg->dnsmgr); ast_free(reg); } /*! \brief Execute destruction of SIP dialog structure, release memory */ -static int __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist) +static void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist) { - struct sip_pvt *cur, *prev = NULL; - struct sip_pkt *cp; - - /* We absolutely cannot destroy the rtp struct while a bridge is active or we WILL crash */ - if (p->rtp && ast_rtp_get_bridged(p->rtp)) { - ast_verbose("Bridge still active. Delaying destroy of SIP dialog '%s' Method: %s\n", p->callid, sip_methods[p->method].text); - return -1; - } - - if (p->vrtp && ast_rtp_get_bridged(p->vrtp)) { - ast_verbose("Bridge still active. Delaying destroy of SIP dialog '%s' Method: %s\n", p->callid, sip_methods[p->method].text); - return -1; - } if (sip_debug_test_pvt(p)) ast_verbose("Really destroying SIP dialog '%s' Method: %s\n", p->callid, sip_methods[p->method].text); @@ -4315,21 +4550,26 @@ static int __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist) } /* Remove link from peer to subscription of MWI */ - if (p->relatedpeer && p->relatedpeer->mwipvt) - p->relatedpeer->mwipvt = dialog_unref(p->relatedpeer->mwipvt); - + if (p->relatedpeer && p->relatedpeer->mwipvt) + p->relatedpeer->mwipvt = dialog_unref(p->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt"); + if (p->relatedpeer && p->relatedpeer->call == p) + p->relatedpeer->call = dialog_unref(p->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself"); + + if (p->relatedpeer) + p->relatedpeer = unref_peer(p->relatedpeer,"unsetting a dialog relatedpeer field in sip_destroy"); + + if (p->registry) { + if (p->registry->call == p) + p->registry->call = dialog_unref(p->registry->call, "nulling out the registry's call dialog field in unlink_all"); + p->registry = registry_unref(p->registry, "delete p->registry"); + } + if (dumphistory) sip_dump_history(p); if (p->options) ast_free(p->options); - if (p->stateid > -1) - ast_extension_state_del(p->stateid, NULL); - AST_SCHED_DEL(sched, p->initid); - AST_SCHED_DEL(sched, p->waitid); - AST_SCHED_DEL(sched, p->autokillid); - if (p->rtp) { ast_rtp_destroy(p->rtp); } @@ -4349,11 +4589,6 @@ static int __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist) free_old_route(p->route); p->route = NULL; } - if (p->registry) { - if (p->registry->call == p) - p->registry->call = NULL; - p->registry = registry_unref(p->registry); - } if (p->initreq.data) ast_free(p->initreq.data); @@ -4362,6 +4597,7 @@ static int __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist) if (p->stimer->st_active == TRUE && p->stimer->st_schedid > -1) ast_sched_del(sched, p->stimer->st_schedid); ast_free(p->stimer); + p->stimer = NULL; } /* Clear history */ @@ -4375,41 +4611,13 @@ static int __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist) p->history = NULL; } - /* Lock dialog list before removing ourselves from the list */ - if (lockdialoglist) - dialoglist_lock(); - for (prev = NULL, cur = dialoglist; cur; prev = cur, cur = cur->next) { - if (cur == p) { - UNLINK(cur, dialoglist, prev); - break; - } - } - if (lockdialoglist) - dialoglist_unlock(); - if (!cur) { - ast_log(LOG_WARNING, "Trying to destroy \"%s\", not found in dialog list?!?! \n", p->callid); - return 0; - } - - /* remove all current packets in this dialog */ - while((cp = p->packets)) { - p->packets = p->packets->next; - AST_SCHED_DEL(sched, cp->retransid); - dialog_unref(cp->owner); - if (cp->data) - ast_free(cp->data); - ast_free(cp); - } if (p->chanvars) { ast_variables_destroy(p->chanvars); p->chanvars = NULL; } - ast_mutex_destroy(&p->pvt_lock); ast_string_field_free_memory(p); - - ast_free(p); - return 0; + return; } /*! \brief update_call_counter: Handle call_limit for SIP users @@ -4490,9 +4698,9 @@ static int update_call_counter(struct sip_pvt *fup, int event) if (*inuse >= *call_limit) { ast_log(LOG_WARNING, "Call %s %s '%s' rejected due to usage limit of %d\n", outgoing ? "to" : "from", u ? "user":"peer", name, *call_limit); if (u) - unref_user(u); + unref_user(u, "update_call_counter: unref user u call limit exceeded"); else - unref_peer(p); + unref_peer(p, "update_call_counter: unref peer p, call limit exceeded"); return -1; } } @@ -4522,12 +4730,18 @@ static int update_call_counter(struct sip_pvt *fup, int event) } if (p) { ast_device_state_changed("SIP/%s", p->name); - unref_peer(p); + unref_peer(p, "update_call_counter: unref_peer from call counter"); } else /* u must be set */ - unref_user(u); + unref_user(u, "update_call_counter: unref_user from call counter"); return 0; } + +static void sip_destroy_fn(void *p) +{ + sip_destroy(p); +} + /*! \brief Destroy SIP call structure. * Make it return NULL so the caller can do things like * foo = sip_destroy(foo); @@ -4731,8 +4945,10 @@ static int sip_hangup(struct ast_channel *ast) sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); ast_clear_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Really hang up next time */ p->needdestroy = 0; - p->owner->tech_pvt = dialog_unref(p->owner->tech_pvt); + p->owner->tech_pvt = dialog_unref(p->owner->tech_pvt, "unref p->owner->tech_pvt"); + sip_pvt_lock(p); p->owner = NULL; /* Owner will be gone after we return, so take it away */ + sip_pvt_unlock(p); return 0; } @@ -4773,7 +4989,7 @@ static int sip_hangup(struct ast_channel *ast) ast_dsp_free(p->vad); p->owner = NULL; - ast->tech_pvt = dialog_unref(ast->tech_pvt); + ast->tech_pvt = dialog_unref(ast->tech_pvt, "unref ast->tech_pvt"); ast_module_unref(ast_module_info->self); /* Do not destroy this pvt until we have timeout or @@ -4857,7 +5073,7 @@ static int sip_hangup(struct ast_channel *ast) but we can't send one while we have "INVITE" outstanding. */ ast_set_flag(&p->flags[0], SIP_PENDINGBYE); ast_clear_flag(&p->flags[0], SIP_NEEDREINVITE); - AST_SCHED_DEL(sched, p->waitid); + AST_SCHED_DEL_UNREF(sched, p->waitid, dialog_unref(p, "when you delete the waitid sched, you should dec the refcount for the stored dialog ptr")); if (sip_cancel_destroy(p)) ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); } @@ -5357,7 +5573,7 @@ static struct ast_channel *sip_new(struct sip_pvt *i, int state, const char *tit tmp->rawwriteformat = fmt; tmp->readformat = fmt; tmp->rawreadformat = fmt; - tmp->tech_pvt = dialog_ref(i); + tmp->tech_pvt = dialog_ref(i, "sip_new: set chan->tech_pvt to i"); tmp->callgroup = i->callgroup; tmp->pickupgroup = i->pickupgroup; @@ -5743,16 +5959,15 @@ static struct sip_pvt *sip_alloc(ast_string_field callid, struct sockaddr_in *si { struct sip_pvt *p; - if (!(p = ast_calloc(1, sizeof(*p)))) + if (!(p = ao2_t_alloc(sizeof(*p), sip_destroy_fn, "allocate a dialog(pvt) struct"))) return NULL; if (ast_string_field_init(p, 512)) { + ao2_t_ref(p, -1, "failed to string_field_init, drop p"); ast_free(p); return NULL; } - ast_mutex_init(&p->pvt_lock); - p->socket.fd = -1; p->socket.type = SIP_TRANSPORT_UDP; p->method = intended_method; @@ -5802,12 +6017,11 @@ static struct sip_pvt *sip_alloc(ast_string_field callid, struct sockaddr_in *si ast_log(LOG_WARNING, "Unable to create RTP audio %s%ssession: %s\n", ast_test_flag(&p->flags[1], SIP_PAGE2_VIDEOSUPPORT) ? "and video " : "", ast_test_flag(&p->flags[1], SIP_PAGE2_TEXTSUPPORT) ? "and text " : "", strerror(errno)); - ast_mutex_destroy(&p->pvt_lock); if (p->chanvars) { ast_variables_destroy(p->chanvars); p->chanvars = NULL; } - ast_free(p); + ao2_t_ref(p, -1, "failed to create RTP audio session, drop p"); return NULL; } ast_rtp_setqos(p->rtp, global_tos_audio, global_cos_audio, "SIP RTP"); @@ -5865,14 +6079,60 @@ static struct sip_pvt *sip_alloc(ast_string_field callid, struct sockaddr_in *si /* Add to active dialog list */ - dialoglist_lock(); - p->next = dialoglist; - dialoglist = dialog_ref(p); - dialoglist_unlock(); - ast_debug(1, "Allocating new SIP dialog for %s - %s (%s)\n", callid ? callid : "(No Call-ID)", sip_methods[intended_method].text, p->rtp ? "With RTP" : "No RTP"); + + ao2_t_link(dialogs, p, "link pvt into dialogs table"); + + ast_debug(1, "Allocating new SIP dialog for %s - %s (%s)\n", callid ? callid : p->callid, sip_methods[intended_method].text, p->rtp ? "With RTP" : "No RTP"); return p; } +/*! \brief argument to the helper function to identify a call */ +struct find_call_cb_arg { + enum sipmethod method; + const char *callid; + const char *fromtag; + const char *totag; + const char *tag; +}; + +/*! + * code to determine whether this is the pvt that we are looking for. + * Return FALSE if not found, true otherwise. p is unlocked. + */ +static int find_call_cb(void *__p, void *__arg, int flags) +{ + struct sip_pvt *p = __p; + struct find_call_cb_arg *arg = __arg; + /* In pedantic, we do not want packets with bad syntax to be connected to a PVT */ + int found = FALSE; + + if (!ast_strlen_zero(p->callid)) { /* XXX double check, do we allow match on empty p->callid ? */ + if (arg->method == SIP_REGISTER) + found = (!strcmp(p->callid, arg->callid)); + else + found = (!strcmp(p->callid, arg->callid) && + (!pedanticsipchecking || ast_strlen_zero(arg->tag) || ast_strlen_zero(p->theirtag) || !strcmp(p->theirtag, arg->tag))) ; + + + ast_debug(5, "= %s Their Call ID: %s Their Tag %s Our tag: %s\n", found ? "Found" : "No match", p->callid, p->theirtag, p->tag); + + /* If we get a new request within an existing to-tag - check the to tag as well */ + if (pedanticsipchecking && found && arg->method != SIP_RESPONSE) { /* SIP Request */ + if (p->tag[0] == '\0' && arg->totag[0]) { + /* We have no to tag, but they have. Wrong dialog */ + found = FALSE; + } else if (arg->totag[0]) { /* Both have tags, compare them */ + if (strcmp(arg->totag, p->tag)) { + found = FALSE; /* This is not our packet */ + } + } + if (!found) + ast_debug(5, "= Being pedantic: This is not our match on request: Call ID: %s Ourtag <null> Totag %s Method %s\n", p->callid, arg->totag, sip_methods[arg->method].text); + } + } + return found; +} + /*! \brief find or create a dialog structure for an incoming SIP message. * Connect incoming SIP message to current dialog or create new dialog structure * Returns a reference to the sip_pvt object, remember to give it back once done. @@ -5884,10 +6144,17 @@ static struct sip_pvt *find_call(struct sip_request *req, struct sockaddr_in *si char *tag = ""; /* note, tag is never NULL */ char totag[128]; char fromtag[128]; + struct find_call_cb_arg arg; const char *callid = get_header(req, "Call-ID"); const char *from = get_header(req, "From"); const char *to = get_header(req, "To"); const char *cseq = get_header(req, "Cseq"); + struct sip_pvt *sip_pvt_ptr; + + callid = get_header(req, "Call-ID"); + from = get_header(req, "From"); + to = get_header(req, "To"); + cseq = get_header(req, "Cseq"); /* Call-ID, to, from and Cseq are required by RFC 3261. (Max-forwards and via too - ignored now) */ /* get_header always returns non-NULL so we must use ast_strlen_zero() */ @@ -5895,6 +6162,12 @@ static struct sip_pvt *find_call(struct sip_request *req, struct sockaddr_in *si ast_strlen_zero(from) || ast_strlen_zero(cseq)) return NULL; /* Invalid packet */ + arg.method = req->method; + arg.callid = callid; + arg.fromtag = fromtag; + arg.totag = totag; + arg.tag = ""; /* make sure tag is never NULL */ + if (pedanticsipchecking) { /* In principle Call-ID's uniquely identify a call, but with a forking SIP proxy we need more to identify a branch - so we have to check branch, from @@ -5922,50 +6195,32 @@ static struct sip_pvt *find_call(struct sip_request *req, struct sockaddr_in *si } } - dialoglist_lock(); restartsearch: - for (p = dialoglist; p; p = p->next) { - /* In pedantic, we do not want packets with bad syntax to be connected to a PVT */ - int found = FALSE; - if (ast_strlen_zero(p->callid)) - continue; - if (req->method == SIP_REGISTER) - found = (!strcmp(p->callid, callid)); - else - found = (!strcmp(p->callid, callid) && - (!pedanticsipchecking || ast_strlen_zero(tag) || ast_strlen_zero(p->theirtag) || !strcmp(p->theirtag, tag))) ; - - ast_debug(5, "= %s Their Call ID: %s Their Tag %s Our tag: %s\n", found ? "Found" : "No match", p->callid, p->theirtag, p->tag); - - /* If we get a new request within an existing to-tag - check the to tag as well */ - if (pedanticsipchecking && found && req->method != SIP_RESPONSE) { /* SIP Request */ - if (p->tag[0] == '\0' && totag[0]) { - /* We have no to tag, but they have. Wrong dialog */ - found = FALSE; - } else if (totag[0]) { /* Both have tags, compare them */ - if (strcmp(totag, p->tag)) { - found = FALSE; /* This is not our packet */ - } - } - if (!found) - ast_debug(5, "= Being pedantic: This is not our match on request: Call ID: %s Ourtag <null> Totag %s Method %s\n", p->callid, totag, sip_methods[req->method].text); - } - - - if (found) { + if (!pedanticsipchecking) { + struct sip_pvt tmp_dialog = { + .callid = callid, + }; + sip_pvt_ptr = ao2_t_find(dialogs, &tmp_dialog, OBJ_POINTER, "ao2_find in dialogs"); + if (sip_pvt_ptr) { /* well, if we don't find it-- what IS in there? */ /* Found the call */ + sip_pvt_lock(sip_pvt_ptr); + return sip_pvt_ptr; + } + } else { /* in pedantic mode! -- do the fancy linear search */ + ao2_lock(dialogs); + p = ao2_t_callback(dialogs, 0 /* single, data */, find_call_cb, &arg, "pedantic linear search for dialog"); + if (p) { if (sip_pvt_trylock(p)) { - dialoglist_unlock(); + ao2_unlock(dialogs); usleep(1); - dialoglist_lock(); + ao2_lock(dialogs); goto restartsearch; } - dialoglist_unlock(); return p; } + ao2_unlock(dialogs); } - dialoglist_unlock(); - + /* See if the method is capable of creating a dialog */ if (sip_methods[intended_method].can_create == CAN_CREATE_DIALOG) { if (intended_method == SIP_REFER) { @@ -6008,7 +6263,7 @@ restartsearch: if (intended_method == SIP_RESPONSE) ast_debug(2, "That's odd... Got a response on a call we dont know about. Callid %s\n", callid ? callid : "<unknown>"); - return p; + return NULL; } /*! \brief Parse register=> line in sip.conf and add to registry */ @@ -6092,7 +6347,7 @@ static int sip_register(const char *value, int lineno) return -1; } - regobjs++; + ast_atomic_fetchadd_int(®objs, 1); ASTOBJ_INIT(reg); ast_string_field_set(reg, callback, callback); if (!ast_strlen_zero(username)) @@ -6111,8 +6366,8 @@ static int sip_register(const char *value, int lineno) reg->portno = portnum; reg->callid_valid = FALSE; reg->ocseq = INITIAL_CSEQ; - ASTOBJ_CONTAINER_LINK(®l, reg); /* Add the new registry entry to the list */ - registry_unref(reg); /* release the reference given by ASTOBJ_INIT. The container has another reference */ + ASTOBJ_CONTAINER_LINK(®l, reg); /* Add the new registry entry to the list */ + registry_unref(reg, "unref the reg pointer"); /* release the reference given by ASTOBJ_INIT. The container has another reference */ return 0; } @@ -7531,7 +7786,7 @@ static int transmit_response_using_temp(ast_string_field callid, struct sockaddr struct sip_pvt *p = NULL; if (!(p = ast_threadstorage_get(&ts_temp_pvt, sizeof(*p)))) { - ast_log(LOG_NOTICE, "Failed to get temporary pvt\n"); + ast_log(LOG_ERROR, "Failed to get temporary pvt\n"); return -1; } @@ -8436,7 +8691,7 @@ static int determine_firstline_parts(struct sip_request *req) static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int oldsdp) { struct sip_request req; - + reqprep(&req, p, ast_test_flag(&p->flags[0], SIP_REINVITE_UPDATE) ? SIP_UPDATE : SIP_INVITE, 0, 1); add_header(&req, "Allow", ALLOWED_METHODS); @@ -8859,7 +9114,7 @@ static int transmit_state_notify(struct sip_pvt *p, int state, int full, int tim enum state { NOTIFY_OPEN, NOTIFY_INUSE, NOTIFY_CLOSED } local_state = NOTIFY_OPEN; char *pidfstate = "--"; char *pidfnote= "Ready"; - + memset(from, 0, sizeof(from)); memset(to, 0, sizeof(to)); @@ -9039,7 +9294,7 @@ static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs, { struct sip_request req; struct ast_str *out = ast_str_alloca(500); - + initreqprep(&req, p, SIP_NOTIFY); add_header(&req, "Event", "message-summary"); add_header(&req, "Content-Type", default_notifymime); @@ -9081,7 +9336,7 @@ static int transmit_notify_with_sipfrag(struct sip_pvt *p, int cseq, char *messa { struct sip_request req; char tmp[SIPBUFSIZE/2]; - + reqprep(&req, p, SIP_NOTIFY, 0, 1); snprintf(tmp, sizeof(tmp), "refer;id=%d", cseq); add_header(&req, "Event", tmp); @@ -9129,7 +9384,7 @@ static const char *regstate2str(enum sipregistrystate regstate) static int sip_reregister(const void *data) { /* if we are here, we know that we need to reregister. */ - struct sip_registry *r= registry_addref((struct sip_registry *) data); + struct sip_registry *r= (struct sip_registry *) data; /* if we couldn't get a reference to the registry object, punt */ if (!r) @@ -9144,7 +9399,7 @@ static int sip_reregister(const void *data) r->expire = -1; __sip_do_register(r); - registry_unref(r); + registry_unref(r, "unreg the re-registered"); return 0; } @@ -9167,7 +9422,7 @@ static int sip_reg_timeout(const void *data) { /* if we are here, our registration timed out, so we'll just do it over */ - struct sip_registry *r = registry_addref((struct sip_registry *) data); + struct sip_registry *r = (struct sip_registry *)data; /* the ref count should have been bumped when the sched item was added */ struct sip_pvt *p; int res; @@ -9198,8 +9453,8 @@ static int sip_reg_timeout(const void *data) /* decouple the two objects */ /* p->registry == r, so r has 2 refs, and the unref won't take the object away */ if (p->registry) - p->registry = registry_unref(p->registry); - r->call = dialog_unref(r->call); + p->registry = registry_unref(p->registry, "p->registry unreffed"); + r->call = dialog_unref(r->call, "unrefing r->call"); } /* If we have a limit, stop registration and give up */ if (global_regattempts_max && r->regattempts > global_regattempts_max) { @@ -9214,7 +9469,7 @@ static int sip_reg_timeout(const void *data) res=transmit_register(r, SIP_REGISTER, NULL, NULL); } manager_event(EVENT_FLAG_SYSTEM, "Registry", "ChannelType: SIP\r\nUsername: %s\r\nDomain: %s\r\nStatus: %s\r\n", r->username, r->hostname, regstate2str(r->regstate)); - registry_unref(r); + registry_unref(r, "unreffing registry_unref r"); return 0; } @@ -9229,6 +9484,7 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char * char tmp[80]; char addr[80]; struct sip_pvt *p; + int res; /* exit if we are already in process with this registrar ?*/ if (r == NULL || ((auth == NULL) && (r->regstate == REG_STATE_REGSENT || r->regstate == REG_STATE_AUTHSENT))) { @@ -9247,7 +9503,7 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char * ast_log(LOG_WARNING, "Already have a REGISTER going on to %s@%s?? \n", r->username, r->hostname); return 0; } else { - p = r->call; + p = dialog_ref(r->call, "getting a copy of the r->call dialog in transmit_register"); make_our_tag(p->tag, sizeof(p->tag)); /* create a new local tag for every register attempt */ ast_string_field_set(p, theirtag, NULL); /* forget their old tag, so we don't match tags when getting response */ } @@ -9276,12 +9532,16 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char * if (create_addr(p, r->hostname, &r->us)) { /* we have what we hope is a temporary network error, * probably DNS. We need to reschedule a registration try */ - sip_destroy(p); + dialog_unlink_all(p, TRUE, TRUE); + p = dialog_unref(p, "unref dialog after unlink_all"); if (r->timeout > -1) { - AST_SCHED_REPLACE(r->timeout, sched, global_reg_timeout * 1000, sip_reg_timeout, r); + AST_SCHED_REPLACE_UNREF(r->timeout, sched, global_reg_timeout * 1000, sip_reg_timeout, r, + registry_unref(_data, "del for REPLACE of registry ptr"), + registry_unref(r, "object ptr dec when SCHED_REPLACE add failed"), + registry_addref(r,"add for REPLACE registry ptr")); ast_log(LOG_WARNING, "Still have a registration timeout for %s@%s (create_addr() error), %d\n", r->username, r->hostname, r->timeout); } else { - r->timeout = ast_sched_add(sched, global_reg_timeout * 1000, sip_reg_timeout, r); + r->timeout = ast_sched_add(sched, global_reg_timeout * 1000, sip_reg_timeout, registry_addref(r, "add for REPLACE registry ptr")); ast_log(LOG_WARNING, "Probably a DNS error for registration to %s@%s, trying REGISTER again (after %d seconds)\n", r->username, r->hostname, global_reg_timeout); } r->regattempts++; @@ -9292,13 +9552,13 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char * ast_string_field_set(r, callid, p->callid); if (!r->dnsmgr && r->portno) { p->sa.sin_port = htons(r->portno); - p->recv.sin_port = htons(r->portno); + p->recv.sin_port = htons(r->portno); } else { /* Set registry port to the port set from the peer definition/srv or default */ r->portno = ntohs(p->sa.sin_port); } ast_set_flag(&p->flags[0], SIP_OUTGOING); /* Registration is outgoing call */ - r->call = dialog_ref(p); /* Save pointer to SIP dialog */ - p->registry = registry_addref(r); /* Add pointer to registry in packet */ + r->call = dialog_ref(p, "copying dialog into registry r->call"); /* Save pointer to SIP dialog */ + p->registry = registry_addref(r, "transmit_register: addref to p->registry in transmit_register"); /* Add pointer to registry in packet */ if (!ast_strlen_zero(r->secret)) /* Secret (password) */ ast_string_field_set(p, peersecret, r->secret); if (!ast_strlen_zero(r->md5secret)) @@ -9336,7 +9596,10 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char * if (auth == NULL) { if (r->timeout > -1) ast_log(LOG_WARNING, "Still have a registration timeout, #%d - deleting it\n", r->timeout); - AST_SCHED_REPLACE(r->timeout, sched, global_reg_timeout * 1000, sip_reg_timeout, r); + AST_SCHED_REPLACE_UNREF(r->timeout, sched, global_reg_timeout * 1000, sip_reg_timeout, r, + registry_unref(_data,"reg ptr unrefed from del in SCHED_REPLACE"), + registry_unref(r,"reg ptr unrefed from add failure in SCHED_REPLACE"), + registry_addref(r,"reg ptr reffed from add in SCHED_REPLACE")); ast_debug(1, "Scheduled a registration timeout for %s id #%d \n", r->hostname, r->timeout); } @@ -9427,15 +9690,16 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char * r->regstate = auth ? REG_STATE_AUTHSENT : REG_STATE_REGSENT; r->regattempts++; /* Another attempt */ ast_debug(4, "REGISTER attempt %d to %s@%s\n", r->regattempts, r->username, r->hostname); - - return send_request(p, &req, XMIT_CRITICAL, p->ocseq); + res = send_request(p, &req, XMIT_CRITICAL, p->ocseq); + dialog_unref(p, "p is finished here at the end of transmit_register"); + return res; } /*! \brief Transmit text with SIP MESSAGE method */ static int transmit_message_with_text(struct sip_pvt *p, const char *text) { struct sip_request req; - + reqprep(&req, p, SIP_MESSAGE, 0, 1); add_text(&req, text); return send_request(p, &req, XMIT_RELIABLE, p->ocseq); @@ -9514,6 +9778,7 @@ static int transmit_refer(struct sip_pvt *p, const char *dest) add_header(&req, "Referred-By", p->our_contact); return send_request(p, &req, XMIT_RELIABLE, p->ocseq); + /* We should propably wait for a NOTIFY here until we ack the transfer */ /* Maybe fork a new thread and wait for a STATUS of REFER_200OK on the refer status before returning to app_transfer */ @@ -9529,7 +9794,7 @@ static int transmit_refer(struct sip_pvt *p, const char *dest) static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration) { struct sip_request req; - + reqprep(&req, p, SIP_INFO, 0, 1); add_digit(&req, digit, duration, (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_SHORTINFO)); return send_request(p, &req, XMIT_RELIABLE, p->ocseq); @@ -9539,7 +9804,7 @@ static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigne static int transmit_info_with_vidupdate(struct sip_pvt *p) { struct sip_request req; - + reqprep(&req, p, SIP_INFO, 0, 1); add_vidupdate(&req); return send_request(p, &req, XMIT_RELIABLE, p->ocseq); @@ -9551,7 +9816,7 @@ static int transmit_info_with_vidupdate(struct sip_pvt *p) static int transmit_request(struct sip_pvt *p, int sipmethod, int seqno, enum xmittype reliable, int newbranch) { struct sip_request resp; - + if (sipmethod == SIP_ACK) p->invitestate = INV_CONFIRMED; @@ -9582,7 +9847,7 @@ static void auth_headers(enum sip_auth_type code, char **header, char **resphead static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, int seqno, enum xmittype reliable, int newbranch) { struct sip_request resp; - + reqprep(&resp, p, sipmethod, seqno, newbranch); if (!ast_strlen_zero(p->realm)) { char digest[1024]; @@ -9632,13 +9897,13 @@ static int expire_register(const void *data) if (!peer) /* Hmmm. We have no peer. Weird. */ return 0; + peer->expire = -1; memset(&peer->addr, 0, sizeof(peer->addr)); destroy_association(peer); /* remove registration data from storage */ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", peer->name); register_peer_exten(peer, FALSE); /* Remove regexten */ - peer->expire = -1; ast_device_state_changed("SIP/%s", peer->name); /* Do we need to release this peer from memory? @@ -9649,8 +9914,11 @@ static int expire_register(const void *data) if (peer->selfdestruct || ast_test_flag(&peer->flags[1], SIP_PAGE2_RTAUTOCLEAR)) { - peer = ASTOBJ_CONTAINER_UNLINK(&peerl, peer); /* Remove from peer list */ - unref_peer(peer); /* Remove from memory */ + ao2_t_unlink(peers, peer, "ao2_unlink of peer from peers table"); + if (peer->addr.sin_addr.s_addr) { + ao2_t_unlink(peers_by_ip, peer, "ao2_unlink of peer from peers_by_ip table"); + } + } return 0; @@ -10256,7 +10524,8 @@ static void sip_peer_hold(struct sip_pvt *p, int hold) /* Request device state update */ ast_device_state_changed("SIP/%s", peer->name); - + unref_peer(peer, "sip_peer_hold: from find_peer operation"); + return; } @@ -10265,9 +10534,9 @@ static void mwi_event_cb(const struct ast_event *event, void *userdata) { struct sip_peer *peer = userdata; - ASTOBJ_RDLOCK(peer); + ao2_lock(peer); sip_send_mwi_to_peer(peer, event, 0); - ASTOBJ_UNLOCK(peer); + ao2_unlock(peer); } /*! \brief Callback for the devicestate notification (SUBSCRIBE) support subsystem @@ -10398,7 +10667,7 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct sockaddr if (!(peer && ast_apply_ha(peer->ha, sin))) { /* Peer fails ACL check */ if (peer) { - unref_peer(peer); + unref_peer(peer, "register_verify: unref_peer: from find_peer operation"); peer = NULL; res = AUTH_ACL_FAILED; } else @@ -10452,7 +10721,11 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct sockaddr /* Create peer if we have autocreate mode enabled */ peer = temp_peer(name); if (peer) { - ASTOBJ_CONTAINER_LINK(&peerl, peer); + ao2_t_link(peers, peer, "link peer into peer table"); + if (peer->addr.sin_addr.s_addr) { + ao2_t_link(peers_by_ip, peer, "link peer into peers-by-ip table"); + } + if (sip_cancel_destroy(p)) ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); switch (parse_register_contact(p, peer, req)) { @@ -10524,7 +10797,7 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct sockaddr } } if (peer) - unref_peer(peer); + unref_peer(peer, "register_verify: unref_peer: tossing stack peer pointer at end of func"); return res; } @@ -10757,55 +11030,41 @@ static int get_destination(struct sip_pvt *p, struct sip_request *oreq) - Their tag is fromtag, our tag is to-tag - This means that in some transactions, totag needs to be their tag :-) depending upon the direction - Returns a reference, remember to release it when done XXX + \return a reference, remember to release it when done */ static struct sip_pvt *get_sip_pvt_byid_locked(const char *callid, const char *totag, const char *fromtag) { struct sip_pvt *sip_pvt_ptr; - + struct sip_pvt tmp_dialog = { + .callid = callid, + }; if (totag) ast_debug(4, "Looking for callid %s (fromtag %s totag %s)\n", callid, fromtag ? fromtag : "<no fromtag>", totag ? totag : "<no totag>"); /* Search dialogs and find the match */ - dialoglist_lock(); - for (sip_pvt_ptr = dialoglist; sip_pvt_ptr; sip_pvt_ptr = sip_pvt_ptr->next) { - if (!strcmp(sip_pvt_ptr->callid, callid)) { - int match = 1; - char *ourtag = sip_pvt_ptr->tag; - - /* Go ahead and lock it (and its owner) before returning */ - sip_pvt_lock(sip_pvt_ptr); - - /* Check if tags match. If not, this is not the call we want - (With a forking SIP proxy, several call legs share the - call id, but have different tags) - */ - if (pedanticsipchecking && (strcmp(fromtag, sip_pvt_ptr->theirtag) || (!ast_strlen_zero(totag) && strcmp(totag, ourtag)))) - match = 0; - - if (!match) { - sip_pvt_unlock(sip_pvt_ptr); - continue; - } - - if (totag) - ast_debug(4, "Matched %s call - their tag is %s Our tag is %s\n", - ast_test_flag(&sip_pvt_ptr->flags[0], SIP_OUTGOING) ? "OUTGOING": "INCOMING", - sip_pvt_ptr->theirtag, sip_pvt_ptr->tag); - - /* deadlock avoidance... */ - while (sip_pvt_ptr->owner && ast_channel_trylock(sip_pvt_ptr->owner)) { - sip_pvt_unlock(sip_pvt_ptr); - usleep(1); - sip_pvt_lock(sip_pvt_ptr); - } - break; + + sip_pvt_ptr = ao2_t_find(dialogs, &tmp_dialog, OBJ_POINTER, "ao2_find of dialog in dialogs table"); + if (sip_pvt_ptr) { + char *ourtag = sip_pvt_ptr->tag; + /* Go ahead and lock it (and its owner) before returning */ + sip_pvt_lock(sip_pvt_ptr); + + if (pedanticsipchecking && (strcmp(fromtag, sip_pvt_ptr->theirtag) || (!ast_strlen_zero(totag) && strcmp(totag, ourtag)))) { + sip_pvt_unlock(sip_pvt_ptr); + ast_debug(4, "Matched %s call for callid=%s - But the pedantic check rejected the match; their tag is %s Our tag is %s\n", + ast_test_flag(&sip_pvt_ptr->flags[0], SIP_OUTGOING) ? "OUTGOING": "INCOMING", sip_pvt_ptr->callid, + sip_pvt_ptr->theirtag, sip_pvt_ptr->tag); + return 0; } + sip_pvt_unlock(sip_pvt_ptr); + + if (totag) + ast_debug(4, "Matched %s call - their tag is %s Our tag is %s\n", + ast_test_flag(&sip_pvt_ptr->flags[0], SIP_OUTGOING) ? "OUTGOING": "INCOMING", + sip_pvt_ptr->theirtag, sip_pvt_ptr->tag); } - dialoglist_unlock(); - if (!sip_pvt_ptr) - ast_debug(4, "Found no match for callid %s to-tag %s from-tag %s\n", callid, totag, fromtag); + return sip_pvt_ptr; } @@ -11042,7 +11301,7 @@ static int get_also_info(struct sip_pvt *p, struct sip_request *oreq) ast_copy_string(referdata->refer_to, c, sizeof(referdata->refer_to)); ast_copy_string(referdata->referred_by, "", sizeof(referdata->referred_by)); ast_copy_string(referdata->refer_contact, "", sizeof(referdata->refer_contact)); - referdata->refer_call = dialog_unref(referdata->refer_call); + referdata->refer_call = dialog_unref(referdata->refer_call, "unreffing referdata->refer_call"); /* Set new context */ ast_string_field_set(p, context, transfer_context); return 0; @@ -11269,7 +11528,7 @@ static enum check_auth_result check_user_ok(struct sip_pvt *p, char *of, if (debug) ast_verbose("Found user '%s' for '%s', but fails host access\n", user->name, of); - unref_user(user); + unref_user(user, "unref_user: from check_user_ok from find_user call, early return of AUTH_DONT_KNOW."); return AUTH_DONT_KNOW; } if (debug) @@ -11351,7 +11610,7 @@ static enum check_auth_result check_user_ok(struct sip_pvt *p, char *of, p->trtp = NULL; } } - unref_user(user); + unref_user(user, "unref_user from check_auth_result, at end"); return res; } @@ -11436,7 +11695,8 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of, /* copy channel vars */ p->chanvars = copy_vars(peer->chanvars); if (authpeer) { - (*authpeer) = ASTOBJ_REF(peer); /* Add a ref to the object here, to keep it in memory a bit longer if it is realtime */ + ao2_t_ref(peer, 1, "copy pointer into (*authpeer)"); + (*authpeer) = peer; /* Add a ref to the object here, to keep it in memory a bit longer if it is realtime */ } if (!ast_strlen_zero(peer->username)) { @@ -11486,7 +11746,7 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of, if (p->t38.peercapability) p->t38.jointcapability &= p->t38.peercapability; } - unref_peer(peer); + unref_peer(peer, "check_peer_ok: unref_peer: tossing temp ptr to peer from find_peer"); return res; } @@ -11703,7 +11963,10 @@ static char *sip_show_inuse(struct ast_cli_entry *e, int cmd, struct ast_cli_arg char ilimits[40]; char iused[40]; int showall = FALSE; - + struct ao2_iterator i; + struct sip_user *user; + struct sip_peer *peer; + switch (cmd) { case CLI_INIT: e->command = "sip show inuse"; @@ -11723,31 +11986,39 @@ static char *sip_show_inuse(struct ast_cli_entry *e, int cmd, struct ast_cli_arg showall = TRUE; ast_cli(a->fd, FORMAT, "* User name", "In use", "Limit"); - ASTOBJ_CONTAINER_TRAVERSE(&userl, 1, do { - ASTOBJ_RDLOCK(iterator); - if (iterator->call_limit) - snprintf(ilimits, sizeof(ilimits), "%d", iterator->call_limit); + + i = ao2_iterator_init(users, 0); + + while ((user = ao2_t_iterator_next(&i, "iterate thru user table"))) { + ao2_lock(user); + + if (user->call_limit) + snprintf(ilimits, sizeof(ilimits), "%d", user->call_limit); else ast_copy_string(ilimits, "N/A", sizeof(ilimits)); - snprintf(iused, sizeof(iused), "%d", iterator->inUse); - if (showall || iterator->call_limit) - ast_cli(a->fd, FORMAT2, iterator->name, iused, ilimits); - ASTOBJ_UNLOCK(iterator); - } while (0) ); + snprintf(iused, sizeof(iused), "%d", user->inUse); + if (showall || user->call_limit) + ast_cli(a->fd, FORMAT2, user->name, iused, ilimits); + ao2_unlock(user); + unref_user(user, "toss iterator pointer"); + } ast_cli(a->fd, FORMAT, "* Peer name", "In use", "Limit"); - ASTOBJ_CONTAINER_TRAVERSE(&peerl, 1, do { - ASTOBJ_RDLOCK(iterator); - if (iterator->call_limit) - snprintf(ilimits, sizeof(ilimits), "%d", iterator->call_limit); + i = ao2_iterator_init(peers, 0); + + while ((peer = ao2_t_iterator_next(&i, "iterate thru peer table"))) { + ao2_lock(peer); + if (peer->call_limit) + snprintf(ilimits, sizeof(ilimits), "%d", peer->call_limit); else ast_copy_string(ilimits, "N/A", sizeof(ilimits)); - snprintf(iused, sizeof(iused), "%d/%d/%d", iterator->inUse, iterator->inRinging, iterator->onHold); - if (showall || iterator->call_limit) - ast_cli(a->fd, FORMAT2, iterator->name, iused, ilimits); - ASTOBJ_UNLOCK(iterator); - } while (0) ); + snprintf(iused, sizeof(iused), "%d/%d/%d", peer->inUse, peer->inRinging, peer->onHold); + if (showall || peer->call_limit) + ast_cli(a->fd, FORMAT2, peer->name, iused, ilimits); + ao2_unlock(peer); + unref_peer(peer, "toss iterator pointer"); + } return CLI_SUCCESS; #undef FORMAT @@ -11773,12 +12044,26 @@ static struct _map_x_s natmodes[] = { { -1, NULL}, /* terminator */ }; +static struct _map_x_s natcfgmodes[] = { + { SIP_NAT_NEVER, "never"}, + { SIP_NAT_ROUTE, "route"}, + { SIP_NAT_ALWAYS, "yes"}, + { SIP_NAT_RFC3581, "no"}, + { -1, NULL}, /* terminator */ +}; + /*! \brief Convert NAT setting to text string */ static const char *nat2str(int nat) { return map_x_s(natmodes, nat, "Unknown"); } +/*! \brief Convert NAT setting to text string appropriate for config files */ +static const char *nat2strconfig(int nat) +{ + return map_x_s(natcfgmodes, nat, "Unknown"); +} + /*! \brief Report Peer status in character string * \return 0 if peer is unreachable, 1 if peer is online, -1 if unmonitored */ @@ -11896,7 +12181,10 @@ static char *sip_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_arg { regex_t regexbuf; int havepattern = FALSE; - + int count = 0; + struct sip_user *user; + struct ao2_iterator i; + #define FORMAT "%-25.25s %-15.15s %-15.15s %-15.15s %-5.5s%-10.10s\n" switch (cmd) { @@ -11926,27 +12214,32 @@ static char *sip_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_arg } ast_cli(a->fd, FORMAT, "Username", "Secret", "Accountcode", "Def.Context", "ACL", "NAT"); - ASTOBJ_CONTAINER_TRAVERSE(&userl, 1, do { - ASTOBJ_RDLOCK(iterator); - if (havepattern && regexec(®exbuf, iterator->name, 0, NULL, 0)) { - ASTOBJ_UNLOCK(iterator); + i = ao2_iterator_init(users, 0); + + while ((user = ao2_t_iterator_next(&i, "iterate thru user table"))) { + ao2_lock(user); + count++; + if (havepattern && regexec(®exbuf, user->name, 0, NULL, 0)) { + ao2_unlock(user); + unref_user(user, "toss iterator pointer via a continue in iterator loop"); continue; } - ast_cli(a->fd, FORMAT, iterator->name, - iterator->secret, - iterator->accountcode, - iterator->context, - cli_yesno(iterator->ha != NULL), - nat2str(ast_test_flag(&iterator->flags[0], SIP_NAT))); - ASTOBJ_UNLOCK(iterator); - } while (0) - ); + ast_cli(a->fd, FORMAT, user->name, + user->secret, + user->accountcode, + user->context, + cli_yesno(user->ha != NULL), + nat2str(ast_test_flag(&user->flags[0], SIP_NAT))); + ao2_unlock(user); + unref_user(user, "toss iterator pointer"); + } if (havepattern) regfree(®exbuf); - + ast_cli(a->fd, "Total of %d user entries\n", count); + return CLI_SUCCESS; #undef FORMAT } @@ -12050,8 +12343,293 @@ static char *sip_show_peers(struct ast_cli_entry *e, int cmd, struct ast_cli_arg case CLI_GENERATE: return NULL; } - - return _sip_show_peers(a->fd, NULL, NULL, NULL, a->argc, (const char **) a->argv); + + return _sip_show_peers(a->fd, NULL, NULL, NULL, a->argc, (const char **) a->argv); +} + +static char *sip_dbdump(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "sip dbdump"; + e->usage = + "Usage: sip dbdump [<file>]\n" + " dumps user/peer info to the screen in SQL INSERT command form\n" + " Optional file path will be output instead of to console if present.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + return _sip_dbdump(a->fd, NULL, NULL, NULL, a->argc, (const char **) a->argv); +} + +/*! \brief Execute sip show peers command */ +static char *_sip_dbdump(int fd, int *total, struct mansession *s, const struct message *m, int argc, const char *argv[]) +{ + struct sip_peer *peer; + struct sip_user *user; + struct ao2_iterator i; + char fname[1024]; + const char *id; + FILE *f1; + char idtext[256] = ""; + int realtimepeers; + + realtimepeers = ast_check_realtime("sippeers"); + + if (s) { /* Manager - get ActionID */ + id = astman_get_header(m, "ActionID"); + if (!ast_strlen_zero(id)) + snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id); + } + + switch (argc) { + case 3: + strncpy(fname, argv[2], sizeof(fname)); + f1 = fopen(fname, "w"); + if (!f1) { + ast_cli(fd, "Sorry, could not open %s for writing!", fname); + return CLI_SHOWUSAGE; + } + break; + case 2: + ast_cli(fd, "Sorry, will only generate a file at the moment. Please run again with a file name to write to.\n"); + default: + return CLI_SHOWUSAGE; + } + + i = ao2_iterator_init(peers, 0); + while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table"))) { + + user = find_user(peer->name,realtimepeers); + + ao2_lock(peer); + + fprintf(f1, "INSERT INTO sipbuddies ("); + + /* print out the populated field names in order */ + fprintf(f1,"name"); + + if (peer->host_dynamic) + fprintf(f1,",host"); + if (ast_test_flag(&peer->flags[0], SIP_NAT)) + fprintf(f1,",nat"); + fprintf(f1,",type"); + if (!ast_strlen_zero(peer->accountcode)) + fprintf(f1,",accountcode"); + if (peer->amaflags) + fprintf(f1,",amaflags"); + fprintf(f1,",`call-limit`"); + if (peer->callgroup) + fprintf(f1,",callgroup"); + if (user && !ast_strlen_zero(user->cid_num)) + fprintf(f1,",callerid"); + if (ast_test_flag(&peer->flags[0], SIP_REINVITE)) + fprintf(f1,",canreinvite"); + if (!ast_strlen_zero(peer->context)) + fprintf(f1,",context"); + if (peer->defaddr.sin_addr.s_addr) + fprintf(f1,",defaultip"); + if (ast_test_flag(&peer->flags[0], SIP_DTMF)) + fprintf(f1,",dtmfmode"); + if (!ast_strlen_zero(peer->fromuser)) + fprintf(f1,",fromuser"); + if (!ast_strlen_zero(peer->fromdomain)) + fprintf(f1,",fromdomain"); + if (ast_test_flag(&peer->flags[0], SIP_INSECURE)) + fprintf(f1,",insecure"); + if (!ast_strlen_zero(peer->language)) + fprintf(f1,",language"); + if (!AST_LIST_EMPTY(&peer->mailboxes)) { + fprintf(f1,",mailbox"); + } + if (!ast_strlen_zero(peer->md5secret)) + fprintf(f1,",md5secret"); + if (peer->ha) { + if (peer->ha->sense == AST_SENSE_DENY) { + fprintf(f1,",deny"); + } + if (peer->ha->next && peer->ha->next->sense == AST_SENSE_ALLOW) { + fprintf(f1,",permit"); + } + } + if (!ast_strlen_zero(peer->mohinterpret)) + fprintf(f1,",mohinterpret"); + if (!ast_strlen_zero(peer->mohsuggest)) + fprintf(f1,",mohsuggest"); + if (peer->pickupgroup) + fprintf(f1,",pickupgroup"); + if (peer->maxms) + fprintf(f1,",qualify"); + if (!ast_strlen_zero(peer->regexten)) + fprintf(f1,",regexten"); + if (peer->rtptimeout) + fprintf(f1,",rtptimeout"); + if (peer->rtpholdtimeout) + fprintf(f1,",rtpholdtimeout"); + if (!ast_strlen_zero(peer->secret)) + fprintf(f1,",secret"); + if (peer->chanvars) + fprintf(f1,",setvar"); + if (ast_codec_pref_index(&peer->prefs, 0)) { /* print the codecs wanted in order */ + fprintf(f1,",allow"); + } + if (!ast_strlen_zero(peer->fullcontact)) + fprintf(f1,",fullcontact"); + if (peer->addr.sin_addr.s_addr) + fprintf(f1,",ipaddr"); + if (peer->addr.sin_port) + fprintf(f1,",port"); + if (!ast_strlen_zero(peer->username)) + fprintf(f1,",username"); + + /* print out the values in order */ + fprintf(f1, ") VALUES ("); + + fprintf(f1,"'%s'", peer->name); + + if (peer->host_dynamic) + fprintf(f1,",'dynamic'"); + if (ast_test_flag(&peer->flags[0], SIP_NAT)) { + fprintf(f1,",'%s'",nat2strconfig(ast_test_flag(&peer->flags[0], SIP_NAT))); + } + if (user) + fprintf(f1,",'friend'"); + else + fprintf(f1,",'peer'"); + if (!ast_strlen_zero(peer->accountcode)) + fprintf(f1,",'%s'", peer->accountcode); + if (peer->amaflags) + fprintf(f1,",'%s'", ast_cdr_flags2str(peer->amaflags)); + fprintf(f1,",%d", peer->call_limit); + if (peer->callgroup) { + char buf[256]; + + fprintf(f1,",'%s'", ast_print_group(buf, sizeof(buf), peer->callgroup)); + } + if (user && !ast_strlen_zero(user->cid_num)) + fprintf(f1,",\"%s<%s>\"", user->cid_name, user->cid_num); + if (ast_test_flag(&peer->flags[0], SIP_REINVITE)) { + switch (ast_test_flag(&peer->flags[0], SIP_REINVITE)) { + case SIP_REINVITE_NONE: + fprintf(f1,",'no'"); + break; + case SIP_CAN_REINVITE: + fprintf(f1,",'yes'"); + break; + case SIP_CAN_REINVITE_NAT: + fprintf(f1,",'nonat'"); + break; + case SIP_REINVITE_UPDATE: + fprintf(f1,",'update'"); + break; + default: + fprintf(f1,",'no'"); + break; + } + } + if (!ast_strlen_zero(peer->context)) + fprintf(f1,",'%s'",peer->context); + if (peer->defaddr.sin_addr.s_addr) + fprintf(f1,",'%s'", ast_inet_ntoa(peer->defaddr.sin_addr)); + if (ast_test_flag(&peer->flags[0], SIP_DTMF)) { + fprintf(f1,",'%s'", dtmfmode2str(ast_test_flag(&peer->flags[0], SIP_DTMF))); + } + if (!ast_strlen_zero(peer->fromuser)) + fprintf(f1,",'%s'", peer->fromuser); + if (!ast_strlen_zero(peer->fromdomain)) + fprintf(f1,",'%s'", peer->fromdomain); + if (ast_test_flag(&peer->flags[0], SIP_INSECURE)) { + + fprintf(f1,",'%s'", insecure2str(ast_test_flag(&peer->flags[0], SIP_INSECURE))); + } + if (!ast_strlen_zero(peer->language)) + fprintf(f1,",'%s'", peer->language); + + if (!AST_LIST_EMPTY(&peer->mailboxes)) { + struct ast_str *mailbox_str = ast_str_alloca(512); + peer_mailboxes_to_str(&mailbox_str, peer); + fprintf(f1,",'%s'", mailbox_str->str); + } + + if (!ast_strlen_zero(peer->md5secret)) + fprintf(f1,",'%s'", peer->md5secret); + if (peer->ha) { + if (peer->ha->sense == AST_SENSE_DENY) { + fprintf(f1,",'%s/%s'", ast_inet_ntoa(peer->ha->netaddr), ast_inet_ntoa(peer->ha->netmask)); + } + if (peer->ha->next && peer->ha->next->sense == AST_SENSE_ALLOW) { + fprintf(f1,",'%s/%s'", ast_inet_ntoa(peer->ha->next->netaddr), ast_inet_ntoa(peer->ha->next->netmask)); + } + } + + if (!ast_strlen_zero(peer->mohinterpret)) + fprintf(f1,",'%s'", peer->mohinterpret); + if (!ast_strlen_zero(peer->mohsuggest)) + fprintf(f1,",'%s'", peer->mohsuggest); + if (peer->pickupgroup) { + char buf[256]; + + fprintf(f1,",'%s'", ast_print_group(buf, sizeof(buf), peer->pickupgroup)); + } + if (peer->maxms) + fprintf(f1,",'%d'", peer->maxms); + if (!ast_strlen_zero(peer->regexten)) + fprintf(f1,",'%s'", peer->regexten); + if (peer->rtptimeout) + fprintf(f1,",'%d'", peer->rtptimeout); + if (peer->rtpholdtimeout) + fprintf(f1,",'%d'", peer->rtpholdtimeout); + if (!ast_strlen_zero(peer->secret)) + fprintf(f1,",'%s'", peer->secret); + if (peer->chanvars) { + int first=1; + struct ast_variable *p1 = peer->chanvars; + fprintf(f1,",'"); + while (p1) + { + if (!first) + fprintf(f1,";"); + else + first = 0; + + fprintf(f1,"%s=%s", p1->name, p1->value); + p1 = p1->next; + } + fprintf(f1,"'"); + } + + if (ast_codec_pref_index(&peer->prefs, 0)) { /* print the codecs wanted in order */ + /* this code isn't general, it assumes deny=all; but that's pretty common. + people who use this differently will have to modify the results by hand. sorry. */ + int x, codec; + fprintf(f1,",'"); + for(x = 0; x < 32 ; x++) { + codec = ast_codec_pref_index(&peer->prefs, x); + if (!codec) + break; + fprintf(f1, "%s", ast_getformatname(codec)); + fprintf(f1, ":%d", peer->prefs.framing[x]); + if (x < 31 && ast_codec_pref_index(&peer->prefs, x + 1)) + fprintf(f1, ","); + } + fprintf(f1,"'"); + } + + if (!ast_strlen_zero(peer->fullcontact)) + fprintf(f1,",'%s'", peer->fullcontact); + if (peer->addr.sin_addr.s_addr) + fprintf(f1,",'%s'", ast_inet_ntoa(peer->addr.sin_addr)); + if (peer->addr.sin_port) + fprintf(f1,",%d", peer->addr.sin_port); + if (!ast_strlen_zero(peer->username)) + fprintf(f1,",'%s'", peer->username); + + fprintf(f1, ");\n"); + } + fclose(f1); + return CLI_SUCCESS; } /*! \brief Execute sip show peers command */ @@ -12059,7 +12637,9 @@ static char *_sip_show_peers(int fd, int *total, struct mansession *s, const str { regex_t regexbuf; int havepattern = FALSE; - + struct sip_peer *peer; + struct ao2_iterator i; + /* the last argument is left-aligned, so we don't need a size anyways */ #define FORMAT2 "%-25.25s %-15.15s %-3.3s %-3.3s %-3.3s %-8s %-10s %s\n" #define FORMAT "%-25.25s %-15.15s %-3.3s %-3.3s %-3.3s %-8d %-10s %s\n" @@ -12099,52 +12679,53 @@ static char *_sip_show_peers(int fd, int *total, struct mansession *s, const str if (!s) /* Normal list */ ast_cli(fd, FORMAT2, "Name/username", "Host", "Dyn", "Nat", "ACL", "Port", "Status", (realtimepeers ? "Realtime" : "")); - ASTOBJ_CONTAINER_TRAVERSE(&peerl, 1, do { + i = ao2_iterator_init(peers, 0); + while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table"))) { char status[20] = ""; char srch[2000]; char pstatus; - ASTOBJ_RDLOCK(iterator); - - if (havepattern && regexec(®exbuf, iterator->name, 0, NULL, 0)) { - ASTOBJ_UNLOCK(iterator); + ao2_lock(peer); + if (havepattern && regexec(®exbuf, peer->name, 0, NULL, 0)) { + ao2_unlock(peer); + unref_peer(peer, "toss iterator peer ptr before continue"); continue; } - if (!ast_strlen_zero(iterator->username) && !s) - snprintf(name, sizeof(name), "%s/%s", iterator->name, iterator->username); + if (!ast_strlen_zero(peer->username) && !s) + snprintf(name, sizeof(name), "%s/%s", peer->name, peer->username); else - ast_copy_string(name, iterator->name, sizeof(name)); + ast_copy_string(name, peer->name, sizeof(name)); - pstatus = peer_status(iterator, status, sizeof(status)); + pstatus = peer_status(peer, status, sizeof(status)); if (pstatus == 1) peers_mon_online++; else if (pstatus == 0) peers_mon_offline++; else { - if (iterator->addr.sin_port == 0) + if (peer->addr.sin_port == 0) peers_unmon_offline++; else peers_unmon_online++; } snprintf(srch, sizeof(srch), FORMAT, name, - iterator->addr.sin_addr.s_addr ? ast_inet_ntoa(iterator->addr.sin_addr) : "(Unspecified)", - iterator->host_dynamic ? " D " : " ", /* Dynamic or not? */ - ast_test_flag(&iterator->flags[0], SIP_NAT_ROUTE) ? " N " : " ", /* NAT=yes? */ - iterator->ha ? " A " : " ", /* permit/deny */ - ntohs(iterator->addr.sin_port), status, - realtimepeers ? (iterator->is_realtime ? "Cached RT":"") : ""); + peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "(Unspecified)", + peer->host_dynamic ? " D " : " ", /* Dynamic or not? */ + ast_test_flag(&peer->flags[0], SIP_NAT_ROUTE) ? " N " : " ", /* NAT=yes? */ + peer->ha ? " A " : " ", /* permit/deny */ + ntohs(peer->addr.sin_port), status, + realtimepeers ? (peer->is_realtime ? "Cached RT":"") : ""); if (!s) {/* Normal CLI list */ ast_cli(fd, FORMAT, name, - iterator->addr.sin_addr.s_addr ? ast_inet_ntoa(iterator->addr.sin_addr) : "(Unspecified)", - iterator->host_dynamic ? " D " : " ", /* Dynamic or not? */ - ast_test_flag(&iterator->flags[0], SIP_NAT_ROUTE) ? " N " : " ", /* NAT=yes? */ - iterator->ha ? " A " : " ", /* permit/deny */ + peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "(Unspecified)", + peer->host_dynamic ? " D " : " ", /* Dynamic or not? */ + ast_test_flag(&peer->flags[0], SIP_NAT_ROUTE) ? " N " : " ", /* NAT=yes? */ + peer->ha ? " A " : " ", /* permit/deny */ - ntohs(iterator->addr.sin_port), status, - realtimepeers ? (iterator->is_realtime ? "Cached RT":"") : ""); + ntohs(peer->addr.sin_port), status, + realtimepeers ? (peer->is_realtime ? "Cached RT":"") : ""); } else { /* Manager format */ /* The names here need to be the same as other channels */ astman_append(s, @@ -12162,22 +12743,22 @@ static char *_sip_show_peers(int fd, int *total, struct mansession *s, const str "Status: %s\r\n" "RealtimeDevice: %s\r\n\r\n", idtext, - iterator->name, - iterator->addr.sin_addr.s_addr ? ast_inet_ntoa(iterator->addr.sin_addr) : "-none-", - ntohs(iterator->addr.sin_port), - iterator->host_dynamic ? "yes" : "no", /* Dynamic or not? */ - ast_test_flag(&iterator->flags[0], SIP_NAT_ROUTE) ? "yes" : "no", /* NAT=yes? */ - ast_test_flag(&iterator->flags[1], SIP_PAGE2_VIDEOSUPPORT) ? "yes" : "no", /* VIDEOSUPPORT=yes? */ - ast_test_flag(&iterator->flags[1], SIP_PAGE2_TEXTSUPPORT) ? "yes" : "no", /* TEXTSUPPORT=yes? */ - iterator->ha ? "yes" : "no", /* permit/deny */ + peer->name, + peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "-none-", + ntohs(peer->addr.sin_port), + peer->host_dynamic ? "yes" : "no", /* Dynamic or not? */ + ast_test_flag(&peer->flags[0], SIP_NAT_ROUTE) ? "yes" : "no", /* NAT=yes? */ + ast_test_flag(&peer->flags[1], SIP_PAGE2_VIDEOSUPPORT) ? "yes" : "no", /* VIDEOSUPPORT=yes? */ + ast_test_flag(&peer->flags[1], SIP_PAGE2_TEXTSUPPORT) ? "yes" : "no", /* TEXTSUPPORT=yes? */ + peer->ha ? "yes" : "no", /* permit/deny */ status, - realtimepeers ? (iterator->is_realtime ? "yes":"no") : "no"); + realtimepeers ? (peer->is_realtime ? "yes":"no") : "no"); } - ASTOBJ_UNLOCK(iterator); - total_peers++; - } while(0) ); + ao2_unlock(peer); + unref_peer(peer, "toss iterator peer ptr"); + } if (!s) ast_cli(fd, "%d sip peers [Monitored: %d online, %d offline Unmonitored: %d online, %d offline]\n", @@ -12195,6 +12776,39 @@ static char *_sip_show_peers(int fd, int *total, struct mansession *s, const str #undef FORMAT2 } +static int user_dump_func(void *userobj, void *arg, int flags) +{ + struct sip_user *user = userobj; + int refc = ao2_t_ref(userobj, 0, ""); + int *fd = arg; + + ast_cli(*fd, "name: %s\ntype: user\nobjflags: %d\nrefcount: %d\n\n", user->name, 0, refc); + return 0; +} + +static int peer_dump_func(void *userobj, void *arg, int flags) +{ + struct sip_peer *peer = userobj; + int refc = ao2_t_ref(userobj, 0, ""); + int *fd = arg; + + ast_cli(*fd, "name: %s\ntype: peer\nobjflags: %d\nrefcount: %d\n\n", + peer->name, 0, refc); + return 0; +} + +static int dialog_dump_func(void *userobj, void *arg, int flags) +{ + struct sip_pvt *pvt = userobj; + int refc = ao2_t_ref(userobj, 0, ""); + int *fd = arg; + + ast_cli(*fd, "name: %s\ntype: dialog\nobjflags: %d\nrefcount: %d\n\n", + pvt->callid, 0, refc); + return 0; +} + + /*! \brief List all allocated SIP Objects (realtime or static) */ static char *sip_show_objects(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { @@ -12214,11 +12828,13 @@ static char *sip_show_objects(struct ast_cli_entry *e, int cmd, struct ast_cli_a if (a->argc != 3) return CLI_SHOWUSAGE; ast_cli(a->fd, "-= User objects: %d static, %d realtime =-\n\n", suserobjs, ruserobjs); - ASTOBJ_CONTAINER_DUMP(a->fd, tmp, sizeof(tmp), &userl); + ao2_t_callback(users, OBJ_NODATA, user_dump_func, &a->fd, "initiate ao2_callback to dump users"); ast_cli(a->fd, "-= Peer objects: %d static, %d realtime, %d autocreate =-\n\n", speerobjs, rpeerobjs, apeerobjs); - ASTOBJ_CONTAINER_DUMP(a->fd, tmp, sizeof(tmp), &peerl); + ao2_t_callback(peers, OBJ_NODATA, peer_dump_func, &a->fd, "initiate ao2_callback to dump peers"); ast_cli(a->fd, "-= Registry objects: %d =-\n\n", regobjs); ASTOBJ_CONTAINER_DUMP(a->fd, tmp, sizeof(tmp), ®l); + ast_cli(a->fd, "-= Dialog objects:\n\n"); + ao2_t_callback(dialogs, OBJ_NODATA, dialog_dump_func, &a->fd, "initiate ao2_callback to dump dialogs"); return CLI_SUCCESS; } /*! \brief Print call group and pickup group */ @@ -12290,17 +12906,95 @@ static void cleanup_stale_contexts(char *new, char *old) } } +/* this func is used with ao2_callback to unlink/delete all dialogs that + are marked needdestroy. It will return CMP_MATCH for candidates, and they + will be unlinked */ + +/* TODO: Implement a queue and instead of marking a dialog + to be destroyed, toss it into the queue. Have a separate + thread do the locking and destruction */ + +static int dialog_needdestroy(void *dialogobj, void *arg, int flags) +{ + struct sip_pvt *dialog = dialogobj; + time_t *t = arg; + + if (sip_pvt_trylock(dialog)) { + /* this path gets executed fairly frequently (3% or so) even in low load + situations; the routines we most commonly fight for a lock with: + sip_answer (7 out of 9) + sip_hangup (2 out of 9) + */ + ao2_unlock(dialogs); + usleep(1); + ao2_lock(dialogs); + /* I had previously returned CMP_STOP here; but changed it to return + a zero instead, because there is no need to stop at the first sign + of trouble. The old algorithm would restart in such circumstances, + but there is no need for this now in astobj2-land. We'll + just skip over this element this time, and catch it on the + next traversal, which is about once a second or so. See the + ao2_callback call in do_monitor. We don't want the number of + dialogs to back up too much between runs. + */ + return 0; + } + + /* We absolutely cannot destroy the rtp struct while a bridge is active or we WILL crash */ + if (dialog->rtp && ast_rtp_get_bridged(dialog->rtp)) { + ast_verbose("Bridge still active. Delaying destroy of SIP dialog '%s' Method: %s\n", dialog->callid, sip_methods[dialog->method].text); + return 0; + } + + if (dialog->vrtp && ast_rtp_get_bridged(dialog->vrtp)) { + ast_verbose("Bridge still active. Delaying destroy of SIP dialog '%s' Method: %s\n", dialog->callid, sip_methods[dialog->method].text); + return 0; + } + /* Check RTP timeouts and kill calls if we have a timeout set and do not get RTP */ + check_rtp_timeout(dialog, *t); + + /* If we have sessions that needs to be destroyed, do it now */ + /* Check if we have outstanding requests not responsed to or an active call + - if that's the case, wait with destruction */ + if (dialog->needdestroy && !dialog->packets && !dialog->owner) { + sip_pvt_unlock(dialog); + /* no, the unlink should handle this: dialog_unref(dialog, "needdestroy: one more refcount decrement to allow dialog to be destroyed"); */ + /* the CMP_MATCH will unlink this dialog from the dialog hash table */ + dialog_unlink_all(dialog, TRUE, FALSE); + return 0; /* the unlink_all should unlink this from the table, so.... no need to return a match */ + } + sip_pvt_unlock(dialog); + return 0; +} + +/* this func is used with ao2_callback to unlink/delete all marked + peers */ +static int peer_is_marked(void *peerobj, void *arg, int flags) +{ + struct sip_peer *peer = peerobj; + return peer->the_mark ? CMP_MATCH : 0; +} + +/* this func is used with ao2_callback to unlink/delete all marked + users */ +static int user_is_marked(void *userobj, void *arg, int flags) +{ + struct sip_user *user = userobj; + return user->the_mark ? CMP_MATCH : 0; +} + /*! \brief Remove temporary realtime objects from memory (CLI) */ static char *sip_prune_realtime(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - struct sip_peer *peer; - struct sip_user *user; + struct sip_peer *peer, *pi; + struct sip_user *user, *ui; int pruneuser = FALSE; int prunepeer = FALSE; int multi = FALSE; char *name = NULL; regex_t regexbuf; - + struct ao2_iterator i; + if (cmd == CLI_INIT) { e->command = "sip prune realtime [peer|user|all] [all|like]"; e->usage = @@ -12375,70 +13069,83 @@ static char *sip_prune_realtime(struct ast_cli_entry *e, int cmd, struct ast_cli if (multi) { if (prunepeer) { int pruned = 0; - - ASTOBJ_CONTAINER_WRLOCK(&peerl); - ASTOBJ_CONTAINER_TRAVERSE(&peerl, 1, do { - ASTOBJ_RDLOCK(iterator); - if (name && regexec(®exbuf, iterator->name, 0, NULL, 0)) { - ASTOBJ_UNLOCK(iterator); + + i = ao2_iterator_init(peers, 0); + while ((pi = ao2_t_iterator_next(&i, "iterate thru peers table"))) { + ao2_lock(pi); + if (name && regexec(®exbuf, pi->name, 0, NULL, 0)) { + unref_peer(pi, "toss iterator peer ptr before continue"); continue; }; - if (ast_test_flag(&iterator->flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { - ASTOBJ_MARK(iterator); + if (ast_test_flag(&pi->flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { + pi->the_mark = 1; pruned++; } - ASTOBJ_UNLOCK(iterator); - } while (0) ); + ao2_unlock(pi); + unref_peer(pi, "toss iterator peer ptr"); + } if (pruned) { - ASTOBJ_CONTAINER_PRUNE_MARKED(&peerl, sip_destroy_peer); + ao2_t_callback(peers, OBJ_NODATA|OBJ_UNLINK, peer_is_marked, 0, "initiating callback to remove marked peers"); ast_cli(a->fd, "%d peers pruned.\n", pruned); } else ast_cli(a->fd, "No peers found to prune.\n"); - ASTOBJ_CONTAINER_UNLOCK(&peerl); } if (pruneuser) { int pruned = 0; - ASTOBJ_CONTAINER_WRLOCK(&userl); - ASTOBJ_CONTAINER_TRAVERSE(&userl, 1, do { - ASTOBJ_RDLOCK(iterator); - if (name && regexec(®exbuf, iterator->name, 0, NULL, 0)) { - ASTOBJ_UNLOCK(iterator); + i = ao2_iterator_init(users, 0); + while ((ui = ao2_t_iterator_next(&i, "iterate thru users table"))) { + ao2_lock(ui); + if (name && regexec(®exbuf, ui->name, 0, NULL, 0)) { + unref_user(ui, "toss iterator user ptr before continue"); continue; }; - if (ast_test_flag(&iterator->flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { - ASTOBJ_MARK(iterator); + if (ast_test_flag(&ui->flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { + ui->the_mark = 1; pruned++; } - ASTOBJ_UNLOCK(iterator); - } while (0) ); + ao2_unlock(ui); + unref_user(ui, "toss iterator user ptr"); + } if (pruned) { - ASTOBJ_CONTAINER_PRUNE_MARKED(&userl, sip_destroy_user); + ao2_t_callback(users, OBJ_NODATA|OBJ_UNLINK, user_is_marked, 0, "callback to remove marked users"); ast_cli(a->fd, "%d users pruned.\n", pruned); } else ast_cli(a->fd, "No users found to prune.\n"); - ASTOBJ_CONTAINER_UNLOCK(&userl); } } else { if (prunepeer) { - if ((peer = ASTOBJ_CONTAINER_FIND_UNLINK(&peerl, name))) { + struct sip_peer tmp; + ast_copy_string(tmp.name, name, sizeof(tmp.name)); + if ((peer = ao2_t_find(peers, &tmp, OBJ_POINTER|OBJ_UNLINK, "finding to unlink from peers"))) { + if (peer->addr.sin_addr.s_addr) { + ao2_t_unlink(peers_by_ip, peer, "unlinking peer from peers_by_ip also"); + } if (!ast_test_flag(&peer->flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { ast_cli(a->fd, "Peer '%s' is not a Realtime peer, cannot be pruned.\n", name); - ASTOBJ_CONTAINER_LINK(&peerl, peer); + /* put it back! */ + ao2_t_link(peers, peer, "link peer into peer table"); + if (peer->addr.sin_addr.s_addr) { + ao2_t_link(peers_by_ip, peer, "link peer into peers_by_ip table"); + } + } else ast_cli(a->fd, "Peer '%s' pruned.\n", name); - unref_peer(peer); + unref_peer(peer, "sip_prune_realtime: unref_peer: tossing temp peer ptr"); } else ast_cli(a->fd, "Peer '%s' not found.\n", name); } if (pruneuser) { - if ((user = ASTOBJ_CONTAINER_FIND_UNLINK(&userl, name))) { + struct sip_user tmp; + ast_copy_string(tmp.name, name, sizeof(tmp.name)); + if ((user = ao2_t_find(users, &tmp, OBJ_POINTER|OBJ_UNLINK, "finding to unlink from users table"))) { if (!ast_test_flag(&user->flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { ast_cli(a->fd, "User '%s' is not a Realtime user, cannot be pruned.\n", name); - ASTOBJ_CONTAINER_LINK(&userl, user); + /* put it back! */ + ao2_t_link(users, user, "link unlinked user back into users table"); } else ast_cli(a->fd, "User '%s' pruned.\n", name); - unref_user(user); + unref_user(user, "unref_user: Tossing temp user ptr"); } else ast_cli(a->fd, "User '%s' not found.\n", name); } @@ -12715,7 +13422,7 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct ast_cli(fd, " Sess-Expires : %d secs\n", peer->stimer.st_max_se); ast_cli(fd, " Min-Sess : %d secs\n", peer->stimer.st_min_se); ast_cli(fd, "\n"); - unref_peer(peer); + peer = unref_peer(peer, "sip_show_peer: unref_peer: done with peer ptr"); } else if (peer && type == 1) { /* manager listing */ char buf[256]; struct ast_str *mailbox_str = ast_str_alloca(512); @@ -12797,7 +13504,7 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct } } - unref_peer(peer); + peer = unref_peer(peer, "sip_show_peer: unref_peer: done with peer"); } else { ast_cli(fd, "Peer %s not found.\n", argv[3]); @@ -12807,6 +13514,45 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct return CLI_SUCCESS; } +static char *sip_show_sched(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + char cbuf[2256]; + struct ast_cb_names cbnames = {9, { "retrans_pkt", + "__sip_autodestruct", + "expire_register", + "auto_congest", + "sip_reg_timeout", + "sip_poke_peer_s", + "sip_poke_noanswer", + "sip_reregister", + "sip_reinvite_retry"}, + { retrans_pkt, + __sip_autodestruct, + expire_register, + auto_congest, + sip_reg_timeout, + sip_poke_peer_s, + sip_poke_noanswer, + sip_reregister, + sip_reinvite_retry}}; + + switch (cmd) { + case CLI_INIT: + e->command = "sip show sched"; + e->usage = + "Usage: sip show sched\n" + " Shows stats on what's in the sched queue at the moment\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + ast_cli(a->fd, "\n"); + ast_sched_report(sched, cbuf, sizeof(cbuf), &cbnames); + ast_cli(a->fd, "%s", cbuf); + return CLI_SUCCESS; +} + + /*! \brief Show one user in detail */ static char *sip_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { @@ -12871,8 +13617,7 @@ static char *sip_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args } ast_cli(a->fd, "\n"); - - unref_user(user); + unref_user(user, "unref_user from sip_show_user, near end"); } else { ast_cli(a->fd, "User %s not found.\n", a->argv[3]); ast_cli(a->fd, "\n"); @@ -12905,6 +13650,7 @@ static char *sip_show_registry(struct ast_cli_entry *e, int cmd, struct ast_cli_ if (a->argc != 3) return CLI_SHOWUSAGE; ast_cli(a->fd, FORMAT2, "Host", "dnsmgr", "Username", "Refresh", "State", "Reg.Time"); + ASTOBJ_CONTAINER_TRAVERSE(®l, 1, do { ASTOBJ_RDLOCK(iterator); snprintf(host, sizeof(host), "%s:%d", iterator->hostname, iterator->portno ? iterator->portno : STANDARD_SIP_PORT); @@ -12953,6 +13699,7 @@ static char *sip_unregister(struct ast_cli_entry *e, int cmd, struct ast_cli_arg } else { ast_cli(a->fd, "Peer %s not registered\n", a->argv[2]); } + unref_peer(peer, "sip_unregister: unref_peer via sip_unregister: done with peer from find_peer call"); } else { ast_cli(a->fd, "Peer unknown: \'%s\'. Not unregistered.\n", a->argv[2]); } @@ -13195,14 +13942,14 @@ static int show_channels_cb(void *__cur, void *__arg, int flags) struct sip_pvt *cur = __cur; struct __show_chan_arg *arg = __arg; const struct sockaddr_in *dst = sip_real_dst(cur); - - /* XXX indentation preserved to reduce diff. Will be fixed later */ - if (cur->subscribed == NONE && !arg->subscriptions) { - /* set if SIP transfer in progress */ - const char *referstatus = cur->refer ? referstatus2str(cur->refer->status) : ""; - char formatbuf[SIPBUFSIZE/2]; - - ast_cli(arg->fd, FORMAT, ast_inet_ntoa(dst->sin_addr), + + /* XXX indentation preserved to reduce diff. Will be fixed later */ + if (cur->subscribed == NONE && !arg->subscriptions) { + /* set if SIP transfer in progress */ + const char *referstatus = cur->refer ? referstatus2str(cur->refer->status) : ""; + char formatbuf[SIPBUFSIZE/2]; + + ast_cli(arg->fd, FORMAT, ast_inet_ntoa(dst->sin_addr), S_OR(cur->username, S_OR(cur->cid_num, "(None)")), cur->callid, ast_getformatname_multiple(formatbuf, sizeof(formatbuf), cur->owner ? cur->owner->nativeformats : 0), @@ -13211,13 +13958,13 @@ static int show_channels_cb(void *__cur, void *__arg, int flags) cur->lastmsg , referstatus ); - arg->numchans++; - } - if (cur->subscribed != NONE && arg->subscriptions) { - struct ast_str *mailbox_str = ast_str_alloca(512); - if (cur->subscribed == MWI_NOTIFICATION && cur->relatedpeer) - peer_mailboxes_to_str(&mailbox_str, cur->relatedpeer); - ast_cli(arg->fd, FORMAT4, ast_inet_ntoa(dst->sin_addr), + arg->numchans++; + } + if (cur->subscribed != NONE && arg->subscriptions) { + struct ast_str *mailbox_str = ast_str_alloca(512); + if (cur->subscribed == MWI_NOTIFICATION && cur->relatedpeer) + peer_mailboxes_to_str(&mailbox_str, cur->relatedpeer); + ast_cli(arg->fd, FORMAT4, ast_inet_ntoa(dst->sin_addr), S_OR(cur->username, S_OR(cur->cid_num, "(None)")), cur->callid, /* the 'complete' exten/context is hidden in the refer_to field for subscriptions */ @@ -13226,10 +13973,9 @@ static int show_channels_cb(void *__cur, void *__arg, int flags) subscription_type2str(cur->subscribed), cur->subscribed == MWI_NOTIFICATION ? S_OR(mailbox_str->str, "<none>") : "<none>", cur->expiry -); - arg->numchans++; - } - + ); + arg->numchans++; + } return 0; /* don't care, we scan all channels */ } @@ -13240,9 +13986,9 @@ static int show_channels_cb(void *__cur, void *__arg, int flags) */ static char *sip_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - struct sip_pvt *cur; struct __show_chan_arg arg = { .fd = a->fd, .numchans = 0 }; + if (cmd == CLI_INIT) { e->command = "sip show {channels|subscriptions}"; e->usage = @@ -13263,12 +14009,8 @@ static char *sip_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_ ast_cli(arg.fd, FORMAT3, "Peer", "User", "Call ID", "Extension", "Last state", "Type", "Mailbox", "Expiry"); /* iterate on the container and invoke the callback on each item */ - dialoglist_lock(); - for (cur = dialoglist; cur; cur = cur->next) { - show_channels_cb(cur, &arg, 0); - } - dialoglist_unlock(); - + ao2_t_callback(dialogs, OBJ_NODATA, show_channels_cb, &arg, "callback to show channels"); + /* print summary information */ ast_cli(arg.fd, "%d active SIP %s%s\n", arg.numchans, (arg.subscriptions ? "subscription" : "dialog"), @@ -13284,38 +14026,53 @@ static char *sip_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_ * given position. As many functions of this kind, each invokation has * O(state) time complexity so be careful in using it. */ + + static char *complete_sipch(const char *line, const char *word, int pos, int state) { int which=0; struct sip_pvt *cur; char *c = NULL; int wordlen = strlen(word); + struct ao2_iterator i; - dialoglist_lock(); - for (cur = dialoglist; cur; cur = cur->next) { + i = ao2_iterator_init(dialogs, 0); + + while ((cur = ao2_t_iterator_next(&i, "iterate thru dialogs"))) { + sip_pvt_lock(cur); if (!strncasecmp(word, cur->callid, wordlen) && ++which > state) { c = ast_strdup(cur->callid); + sip_pvt_unlock(cur); + dialog_unref(cur, "drop ref in iterator loop break"); break; } + sip_pvt_unlock(cur); + dialog_unref(cur, "drop ref in iterator loop"); } - dialoglist_unlock(); return c; } + /*! \brief Do completion on peer name */ static char *complete_sip_peer(const char *word, int state, int flags2) { char *result = NULL; int wordlen = strlen(word); int which = 0; + struct ao2_iterator i = ao2_iterator_init(peers, 0); + struct sip_peer *peer; - ASTOBJ_CONTAINER_TRAVERSE(&peerl, !result, do { + while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table"))) { /* locking of the object is not required because only the name and flags are being compared */ - if (!strncasecmp(word, iterator->name, wordlen) && - (!flags2 || ast_test_flag(&iterator->flags[1], flags2)) && + if (!strncasecmp(word, peer->name, wordlen) && + (!flags2 || ast_test_flag(&peer->flags[1], flags2)) && ++which > state) - result = ast_strdup(iterator->name); - } while(0) ); + result = ast_strdup(peer->name); + unref_peer(peer, "toss iterator peer ptr before break"); + if (result) { + break; + } + } return result; } @@ -13325,15 +14082,21 @@ static char *complete_sip_registered_peer(const char *word, int state, int flags char *result = NULL; int wordlen = strlen(word); int which = 0; - - ASTOBJ_CONTAINER_TRAVERSE(&peerl, !result, do { - ASTOBJ_WRLOCK(iterator); - if (!strncasecmp(word, iterator->name, wordlen) && - (!flags2 || ast_test_flag(&iterator->flags[1], flags2)) && - ++which > state && iterator->expire > 0) - result = ast_strdup(iterator->name); - ASTOBJ_UNLOCK(iterator); - } while(0) ); + struct ao2_iterator i; + struct sip_peer *peer; + + i = ao2_iterator_init(peers, 0); + while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table"))) { + if (!strncasecmp(word, peer->name, wordlen) && + (!flags2 || ast_test_flag(&peer->flags[1], flags2)) && + ++which > state && peer->expire > 0) + result = ast_strdup(peer->name); + if (result) { + unref_peer(peer, "toss iterator peer ptr before break"); + break; + } + unref_peer(peer, "toss iterator peer ptr"); + } return result; } @@ -13370,17 +14133,28 @@ static char *complete_sip_user(const char *word, int state, int flags2) char *result = NULL; int wordlen = strlen(word); int which = 0; + struct ao2_iterator i; + struct sip_user *user; - ASTOBJ_CONTAINER_TRAVERSE(&userl, !result, do { + i = ao2_iterator_init(users, 0); + + while ((user = ao2_t_iterator_next(&i, "iterate thru users table"))) { /* locking of the object is not required because only the name and flags are being compared */ - if (!strncasecmp(word, iterator->name, wordlen)) { - if (flags2 && !ast_test_flag(&iterator->flags[1], flags2)) + if (!strncasecmp(word, user->name, wordlen)) { + if (flags2 && !ast_test_flag(&user->flags[1], flags2)) { + unref_user(user, "toss iterator user ptr before continue"); continue; + } if (++which > state) { - result = ast_strdup(iterator->name); + result = ast_strdup(user->name); } } - } while(0) ); + if (result) { + unref_user(user, "toss iterator user ptr before break"); + break; + } + unref_user(user, "toss iterator user ptr"); + } return result; } @@ -13429,6 +14203,7 @@ static char *sip_show_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_a struct sip_pvt *cur; size_t len; int found = 0; + struct ao2_iterator i; switch (cmd) { case CLI_INIT: @@ -13444,8 +14219,12 @@ static char *sip_show_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_a if (a->argc != 4) return CLI_SHOWUSAGE; len = strlen(a->argv[3]); - dialoglist_lock(); - for (cur = dialoglist; cur; cur = cur->next) { + + i = ao2_iterator_init(dialogs, 0); + + while ((cur = ao2_t_iterator_next(&i, "iterate thru dialogs"))) { + sip_pvt_lock(cur); + if (!strncasecmp(cur->callid, a->argv[3], len)) { char formatbuf[SIPBUFSIZE/2]; ast_cli(a->fd, "\n"); @@ -13517,10 +14296,15 @@ static char *sip_show_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_a found++; } + + sip_pvt_unlock(cur); + + ao2_t_ref(cur, -1, "toss dialog ptr set by iterator_next"); } - dialoglist_unlock(); + if (!found) ast_cli(a->fd, "No such SIP Call ID starting with '%s'\n", a->argv[3]); + return CLI_SUCCESS; } @@ -13530,6 +14314,7 @@ static char *sip_show_history(struct ast_cli_entry *e, int cmd, struct ast_cli_a struct sip_pvt *cur; size_t len; int found = 0; + struct ao2_iterator i; switch (cmd) { case CLI_INIT: @@ -13544,11 +14329,15 @@ static char *sip_show_history(struct ast_cli_entry *e, int cmd, struct ast_cli_a if (a->argc != 4) return CLI_SHOWUSAGE; + if (!recordhistory) ast_cli(a->fd, "\n***Note: History recording is currently DISABLED. Use 'sip set history on' to ENABLE.\n"); + len = strlen(a->argv[3]); - dialoglist_lock(); - for (cur = dialoglist; cur; cur = cur->next) { + + i = ao2_iterator_init(dialogs, 0); + while ((cur = ao2_t_iterator_next(&i, "iterate thru dialogs"))) { + sip_pvt_lock(cur); if (!strncasecmp(cur->callid, a->argv[3], len)) { struct sip_history *hist; int x = 0; @@ -13565,10 +14354,13 @@ static char *sip_show_history(struct ast_cli_entry *e, int cmd, struct ast_cli_a ast_cli(a->fd, "Call '%s' has no history\n", cur->callid); found++; } + sip_pvt_unlock(cur); + ao2_t_ref(cur, -1, "toss dialog ptr from iterator_next"); } - dialoglist_unlock(); + if (!found) ast_cli(a->fd, "No such SIP Call ID starting with '%s'\n", a->argv[3]); + return CLI_SUCCESS; } @@ -13833,7 +14625,7 @@ static char *sip_do_debug_peer(int fd, char *arg) sipdebug |= sip_debug_console; } if (peer) - unref_peer(peer); + unref_peer(peer, "sip_do_debug_peer: unref_peer, from find_peer call"); return CLI_SUCCESS; } @@ -13926,7 +14718,9 @@ static char *sip_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a if (create_addr(p, a->argv[i], NULL)) { /* Maybe they're not registered, etc. */ - sip_destroy(p); + dialog_unlink_all(p, TRUE, TRUE); + dialog_unref(p, "unref dialog inside for loop" ); + /* sip_destroy(p); */ ast_cli(a->fd, "Could not create address for '%s'\n", a->argv[i]); continue; } @@ -13942,11 +14736,14 @@ static char *sip_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a /* Recalculate our side, and recalculate Call ID */ ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip); build_via(p); + ao2_t_unlink(dialogs, p, "About to change the callid -- remove the old name"); build_callid_pvt(p); + ao2_t_link(dialogs, p, "Linking in new name"); ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n", a->argv[2], a->argv[i]); + dialog_ref(p, "bump the count of p, which transmit_sip_request will decrement."); transmit_sip_request(p, &req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); - dialog_unref(p); + dialog_unref(p, "unref pvt at end of for loop in sip_notify"); } return CLI_SUCCESS; @@ -14373,7 +15170,7 @@ static int function_sippeer(struct ast_channel *chan, const char *cmd, char *dat } } - unref_peer(peer); + unref_peer(peer, "unref_peer from function_sippeer, just before return"); return 0; } @@ -14572,6 +15369,7 @@ static int sip_reinvite_retry(const void *data) ast_set_flag(&p->flags[0], SIP_NEEDREINVITE); p->waitid = -1; + dialog_unref(p, "unref the dialog ptr from sip_reinvite_retry, because it held a dialog ptr"); return 0; } @@ -14599,7 +15397,7 @@ static void handle_response_invite(struct sip_pvt *p, int resp, char *rest, stru /* Acknowledge sequence number - This only happens on INVITE from SIP-call */ /* Don't auto congest anymore since we've gotten something useful back */ - AST_SCHED_DEL(sched, p->initid); + AST_SCHED_DEL_UNREF(sched, p->initid, dialog_unref(p, "when you delete the initid sched, you should dec the refcount for the stored dialog ptr")); /* RFC3261 says we must treat every 1xx response (but not 100) that we don't recognize as if it was 183. @@ -14902,7 +15700,8 @@ static void handle_response_invite(struct sip_pvt *p, int resp, char *rest, stru /* Reset the flag after a while */ int wait = 3 + ast_random() % 5; - p->waitid = ast_sched_add(sched, wait, sip_reinvite_retry, p); + p->waitid = ast_sched_add(sched, wait, sip_reinvite_retry, dialog_ref(p, "passing dialog ptr into sched structure based on waitid for sip_reinvite_retry.")); + ast_log(LOG_WARNING, "just did sched_add waitid(%d) for sip_reinvite_retry for dialog %s in handle_response_invite\n", p->waitid, p->callid); ast_debug(2, "Reinvite race. Waiting %d secs before retry\n", wait); } } @@ -14983,7 +15782,7 @@ static int handle_response_register(struct sip_pvt *p, int resp, char *rest, str int expires, expires_ms; struct sip_registry *r; r=p->registry; - + switch (resp) { case 401: /* Unauthorized */ if (p->authtries == MAX_AUTHTRIES || do_register_auth(p, req, resp)) { @@ -15000,7 +15799,8 @@ static int handle_response_register(struct sip_pvt *p, int resp, char *rest, str case 404: /* Not found */ ast_log(LOG_WARNING, "Got 404 Not found on SIP register to service %s@%s, giving up\n", p->registry->username, p->registry->hostname); p->needdestroy = 1; - r->call = NULL; + if (r->call) + r->call = dialog_unref(r->call, "unsetting registry->call pointer-- case 404"); r->regstate = REG_STATE_REJECTED; AST_SCHED_DEL(sched, r->timeout); break; @@ -15014,7 +15814,8 @@ static int handle_response_register(struct sip_pvt *p, int resp, char *rest, str if (global_regattempts_max) p->registry->regattempts = global_regattempts_max+1; p->needdestroy = 1; - r->call = NULL; + if (r->call) + r->call = dialog_unref(r->call, "unsetting registry->call pointer-- case 408"); AST_SCHED_DEL(sched, r->timeout); break; case 423: /* Interval too brief */ @@ -15023,7 +15824,7 @@ static int handle_response_register(struct sip_pvt *p, int resp, char *rest, str ast_sched_del(sched, r->timeout); r->timeout = -1; if (r->call) { - r->call = NULL; + r->call = dialog_unref(r->call, "unsetting registry->call pointer-- case 423"); p->needdestroy = 1; } if (r->expiry > max_expiry) { @@ -15039,7 +15840,8 @@ static int handle_response_register(struct sip_pvt *p, int resp, char *rest, str case 479: /* SER: Not able to process the URI - address is wrong in register*/ ast_log(LOG_WARNING, "Got error 479 on register to %s@%s, giving up (check config)\n", p->registry->username, p->registry->hostname); p->needdestroy = 1; - r->call = NULL; + if (r->call) + r->call = dialog_unref(r->call, "unsetting registry->call pointer-- case 479"); r->regstate = REG_STATE_REJECTED; AST_SCHED_DEL(sched, r->timeout); break; @@ -15049,7 +15851,7 @@ static int handle_response_register(struct sip_pvt *p, int resp, char *rest, str p->needdestroy = 1; return 0; } - + r->regstate = REG_STATE_REGISTERED; r->regtime = ast_tvnow(); /* Reset time of last succesful registration */ manager_event(EVENT_FLAG_SYSTEM, "Registry", "ChannelType: SIP\r\nDomain: %s\r\nStatus: %s\r\n", r->hostname, regstate2str(r->regstate)); @@ -15059,19 +15861,21 @@ static int handle_response_register(struct sip_pvt *p, int resp, char *rest, str ast_debug(1, "Cancelling timeout %d\n", r->timeout); } AST_SCHED_DEL(sched, r->timeout); - r->call = NULL; - p->registry = NULL; + if (r->call) + r->call = dialog_unref(r->call, "unsetting registry->call pointer-- case 200"); + p->registry = registry_unref(p->registry, "unref registry entry p->registry"); /* Let this one hang around until we have all the responses */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); /* p->needdestroy = 1; */ - + /* set us up for re-registering */ /* figure out how long we got registered for */ AST_SCHED_DEL(sched, r->expire); + /* according to section 6.13 of RFC, contact headers override expires headers, so check those first */ expires = 0; - + /* XXX todo: try to save the extra call */ if (!ast_strlen_zero(get_header(req, "Contact"))) { const char *contact = NULL; @@ -15093,13 +15897,13 @@ static int handle_response_register(struct sip_pvt *p, int resp, char *rest, str if (sscanf(tmptmp + 8, "%d;", &expires) != 1) expires = 0; } - + } if (!expires) expires=atoi(get_header(req, "expires")); if (!expires) expires=default_expiry; - + expires_ms = expires * 1000; if (expires <= EXPIRY_GUARD_LIMIT) expires_ms -= MAX((expires_ms * EXPIRY_GUARD_PCT), EXPIRY_GUARD_MIN); @@ -15107,12 +15911,17 @@ static int handle_response_register(struct sip_pvt *p, int resp, char *rest, str expires_ms -= EXPIRY_GUARD_SECS * 1000; if (sipdebug) ast_log(LOG_NOTICE, "Outbound Registration: Expiry for %s is %d sec (Scheduling reregistration in %d s)\n", r->hostname, expires, expires_ms/1000); - + r->refresh= (int) expires_ms / 1000; - + /* Schedule re-registration before we expire */ - AST_SCHED_REPLACE(r->expire, sched, expires_ms, sip_reregister, r); - registry_unref(r); + AST_SCHED_REPLACE_UNREF(r->expire, sched, expires_ms, sip_reregister, r, + registry_unref(_data,"unref in REPLACE del fail"), + registry_unref(r,"unref in REPLACE add fail"), + registry_addref(r,"The Addition side of REPLACE")); + /* it is clear that we would not want to destroy the registry entry if we just + scheduled a callback and recorded it in there! */ + /* since we never bumped the count, we shouldn't decrement it! registry_unref(r, "unref registry ptr r"); if this gets deleted, p->registry will be a bad pointer! */ } return 1; } @@ -15120,7 +15929,7 @@ static int handle_response_register(struct sip_pvt *p, int resp, char *rest, str /*! \brief Handle qualification responses (OPTIONS) */ static void handle_response_peerpoke(struct sip_pvt *p, int resp, struct sip_request *req) { - struct sip_peer *peer = p->relatedpeer; + struct sip_peer *peer = /* ref_peer( */ p->relatedpeer /* , "bump refcount on p, as it is being used in this function(handle_response_peerpoke)")*/ ; /* hope this is already refcounted! */ int statechanged, is_reachable, was_reachable; int pingtime = ast_tvdiff_ms(ast_tvnow(), peer->ps); @@ -15142,7 +15951,7 @@ static void handle_response_peerpoke(struct sip_pvt *p, int resp, struct sip_req || was_reachable != is_reachable; peer->lastms = pingtime; - peer->call = dialog_unref(peer->call); + peer->call = dialog_unref(peer->call, "unref dialog peer->call"); if (statechanged) { const char *s = is_reachable ? "Reachable" : "Lagged"; @@ -15162,6 +15971,7 @@ static void handle_response_peerpoke(struct sip_pvt *p, int resp, struct sip_req AST_SCHED_REPLACE(peer->pokeexpire, sched, is_reachable ? peer->qualifyfreq : DEFAULT_FREQ_NOTOK, sip_poke_peer_s, peer); + /* unref_peer(peer, "unref relatedpeer ptr var at end of handle_response_peerpoke"); */ } /*! \brief Immediately stop RTP, VRTP and UDPTL as applicable */ @@ -16192,7 +17002,7 @@ static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req, in sip_pvt_unlock(p); /* Unlock SIP structure */ /* The call should be down with no ast_channel, so hang it up */ - c->tech_pvt = dialog_unref(c->tech_pvt); + c->tech_pvt = dialog_unref(c->tech_pvt, "unref dialog c->tech_pvt"); ast_hangup(c); return 0; } @@ -16347,7 +17157,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int if (p->refer->refer_call == p) { ast_log(LOG_NOTICE, "INVITE with replaces into it's own call id (%s == %s)!\n", replace_id, p->callid); - p->refer->refer_call = dialog_unref(p->refer->refer_call); + p->refer->refer_call = dialog_unref(p->refer->refer_call, "unref dialog p->refer->refer_call"); transmit_response(p, "400 Bad request", req); /* The best way to not not accept the transfer */ error = 1; } @@ -16899,6 +17709,8 @@ static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual * transferer->refer->status = REFER_FAILED; sip_pvt_unlock(targetcall_pvt); ast_channel_unlock(current->chan1); + if (targetcall_pvt) + ao2_t_ref(targetcall_pvt, -1, "Drop targetcall_pvt pointer"); return -1; } @@ -16969,6 +17781,8 @@ static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual * ast_channel_unlock(targetcall_pvt->owner); } } + if (targetcall_pvt) + ao2_t_ref(targetcall_pvt, -1, "drop targetcall_pvt"); return 1; } @@ -17449,8 +18263,9 @@ static int handle_request_bye(struct sip_pvt *p, struct sip_request *req) struct ast_channel *bridged_to; /* If we have an INCOMING invite that we haven't answered, terminate that transaction */ - if (p->pendinginvite && !ast_test_flag(&p->flags[0], SIP_OUTGOING) && !req->ignore && !p->owner) + if (p->pendinginvite && !ast_test_flag(&p->flags[0], SIP_OUTGOING) && !req->ignore && !p->owner) { transmit_response_reliable(p, "487 Request Terminated", &p->initreq); + } p->invitestate = INV_TERMINATED; @@ -17516,7 +18331,7 @@ static int handle_request_bye(struct sip_pvt *p, struct sip_request *req) ast_debug(3, "Received bye, issuing owner hangup\n"); } else { sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); - ast_debug(3, "Received bye, no owner, selfdestruct soon.\n"); + ast_debug(3, "Received bye, no owner, selfdestruct soon.\n"); } transmit_response(p, "200 OK", req); @@ -17558,6 +18373,7 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, const char *accept = get_header(req, "Accept"); int resubscribe = (p->subscribed != NONE); char *temp, *event; + struct ao2_iterator i; if (p->initreq.headers) { /* We already have a dialog */ @@ -17652,7 +18468,7 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, transmit_response(p, "403 Forbidden (policy)", req); p->needdestroy = 1; if (authpeer) - unref_peer(authpeer); + unref_peer(authpeer, "unref_peer, from handle_request_subscribe (authpeer 1)"); return 0; } @@ -17667,7 +18483,7 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, transmit_response(p, "404 Not Found", req); p->needdestroy = 1; if (authpeer) - unref_peer(authpeer); + unref_peer(authpeer, "unref_peer, from handle_request_subscribe (authpeer 2)"); return 0; } @@ -17677,7 +18493,7 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, if (!strcmp(event, "presence") || !strcmp(event, "dialog")) { /* Presence, RFC 3842 */ if (authpeer) /* We do not need the authpeer any more */ - unref_peer(authpeer); + unref_peer(authpeer, "unref_peer, from handle_request_subscribe (authpeer 2)"); /* Header from Xten Eye-beam Accept: multipart/related, application/rlmi+xml, application/pidf+xml, application/xpidf+xml */ /* Polycom phones only handle xpidf+xml, even if they say they can @@ -17723,7 +18539,7 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, ast_debug(2, "Received SIP mailbox subscription for unknown format: %s\n", accept); p->needdestroy = 1; if (authpeer) - unref_peer(authpeer); + unref_peer(authpeer, "unref_peer, from handle_request_subscribe (authpeer 3)"); return 0; } /* Looks like they actually want a mailbox status @@ -17736,7 +18552,7 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, p->needdestroy = 1; ast_log(LOG_NOTICE, "Received SIP subscribe for peer without mailbox: %s\n", authpeer->name); if (authpeer) - unref_peer(authpeer); + unref_peer(authpeer, "unref_peer, from handle_request_subscribe (authpeer 4)"); return 0; } @@ -17744,26 +18560,36 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, if (ast_test_flag(&authpeer->flags[1], SIP_PAGE2_SUBSCRIBEMWIONLY)) { add_peer_mwi_subs(authpeer); } - if (authpeer->mwipvt && authpeer->mwipvt != p) /* Destroy old PVT if this is a new one */ + if (authpeer->mwipvt && authpeer->mwipvt != p) { /* Destroy old PVT if this is a new one */ /* We only allow one subscription per peer */ - sip_destroy(authpeer->mwipvt); - authpeer->mwipvt = p; /* Link from peer to pvt */ - p->relatedpeer = authpeer; /* Link from pvt to peer */ + dialog_unlink_all(authpeer->mwipvt, TRUE, TRUE); + authpeer->mwipvt = dialog_unref(authpeer->mwipvt, "unref dialog authpeer->mwipvt"); + /* sip_destroy(authpeer->mwipvt); */ + } + if (authpeer->mwipvt) + dialog_unref(authpeer->mwipvt, "Unref previously stored mwipvt dialog pointer"); + authpeer->mwipvt = dialog_ref(p, "setting peers' mwipvt to p"); /* Link from peer to pvt UH- should this be dialog_ref()? */ + if (p->relatedpeer) + unref_peer(p->relatedpeer, "Unref previously stored relatedpeer ptr"); + p->relatedpeer = ref_peer(authpeer, "setting dialog's relatedpeer pointer"); /* already refcounted...Link from pvt to peer UH- should this be dialog_ref()? */ /* Do not release authpeer here */ } else { /* At this point, Asterisk does not understand the specified event */ transmit_response(p, "489 Bad Event", req); ast_debug(2, "Received SIP subscribe for unknown event package: %s\n", event); p->needdestroy = 1; if (authpeer) - unref_peer(authpeer); + unref_peer(authpeer, "unref_peer, from handle_request_subscribe (authpeer 5)"); return 0; } /* Add subscription for extension state from the PBX core */ if (p->subscribed != MWI_NOTIFICATION && !resubscribe) { - if (p->stateid > -1) + if (p->stateid > -1) { ast_extension_state_del(p->stateid, cb_extensionstate); - p->stateid = ast_extension_state_add(p->context, p->exten, cb_extensionstate, p); + /* we need to dec the refcount, now that the extensionstate is removed */ + dialog_unref(p, "the extensionstate containing this dialog ptr was deleted"); + } + p->stateid = ast_extension_state_add(p->context, p->exten, cb_extensionstate, dialog_ref(p,"copying dialog ptr into extension state struct")); } if (!req->ignore && p) @@ -17791,9 +18617,9 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, if (p->subscribed == MWI_NOTIFICATION) { transmit_response(p, "200 OK", req); if (p->relatedpeer) { /* Send first notification */ - ASTOBJ_WRLOCK(p->relatedpeer); + ao2_lock(p->relatedpeer); /* was WRLOCK */ sip_send_mwi_to_peer(p->relatedpeer, NULL, 0); - ASTOBJ_UNLOCK(p->relatedpeer); + ao2_unlock(p->relatedpeer); } } else { struct sip_pvt *p_old; @@ -17817,26 +18643,34 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, for it to expire and send NOTIFY messages to the peer only to have them ignored (or generate errors) */ - dialoglist_lock(); - for (p_old = dialoglist; p_old; p_old = p_old->next) { - if (p_old == p) + i = ao2_iterator_init(dialogs, 0); + + while ((p_old = ao2_t_iterator_next(&i, "iterate thru dialogs"))) { + if (p_old == p) { + ao2_t_ref(p_old, -1, "toss dialog ptr from iterator_next before continue"); continue; - if (p_old->initreq.method != SIP_SUBSCRIBE) + } + if (p_old->initreq.method != SIP_SUBSCRIBE) { + ao2_t_ref(p_old, -1, "toss dialog ptr from iterator_next before continue"); continue; - if (p_old->subscribed == NONE) + } + if (p_old->subscribed == NONE) { + ao2_t_ref(p_old, -1, "toss dialog ptr from iterator_next before continue"); continue; + } sip_pvt_lock(p_old); if (!strcmp(p_old->username, p->username)) { if (!strcmp(p_old->exten, p->exten) && !strcmp(p_old->context, p->context)) { p_old->needdestroy = 1; sip_pvt_unlock(p_old); + ao2_t_ref(p_old, -1, "toss dialog ptr from iterator_next before break"); break; } } sip_pvt_unlock(p_old); + ao2_t_ref(p_old, -1, "toss dialog ptr from iterator_next"); } - dialoglist_unlock(); } if (!p->expiry) p->needdestroy = 1; @@ -18213,6 +19047,8 @@ static int handle_request_do(struct sip_request *req, struct sockaddr_in *sin) break; /* locking succeeded */ ast_debug(1, "Failed to grab owner channel lock, trying again. (SIP call %s)\n", p->callid); sip_pvt_unlock(p); + if (lockretry != 1) + ao2_t_ref(p, -1, "release p (from find_call) inside lockretry loop"); /* we'll look for it again, but p is dead now */ ast_mutex_unlock(&netlock); /* Sleep for a very short amount of time */ usleep(1); @@ -18230,6 +19066,7 @@ static int handle_request_do(struct sip_request *req, struct sockaddr_in *sin) transmit_response(p, "503 Server error", req); /* We must respond according to RFC 3261 sec 12.2 */ /* XXX We could add retry-after to make sure they come back */ append_history(p, "LockFail", "Owner lock failed, transaction failed."); + ao2_t_ref(p, -1, "release p (from find_call) at end of lockretry"); /* p is gone after the return */ return 1; } @@ -18246,7 +19083,7 @@ static int handle_request_do(struct sip_request *req, struct sockaddr_in *sin) ast_channel_unlock(p->owner); sip_pvt_unlock(p); ast_mutex_unlock(&netlock); - + ao2_t_ref(p, -1, "throw away dialog ptr from find_call at end of routine"); /* p is gone after the return */ return 1; } @@ -18391,38 +19228,44 @@ static int sip_send_mwi_to_peer(struct sip_peer *peer, const struct ast_event *e if (peer->mwipvt) { /* Base message on subscription */ - p = dialog_ref(peer->mwipvt); + p = dialog_ref(peer->mwipvt, "sip_send_mwi_to_peer: Setting dialog ptr p from peer->mwipvt-- should this be done?"); } else { /* Build temporary dialog for this message */ if (!(p = sip_alloc(NULL, NULL, 0, SIP_NOTIFY))) return -1; if (create_addr_from_peer(p, peer)) { /* Maybe they're not registered, etc. */ - sip_destroy(p); + dialog_unlink_all(p, TRUE, TRUE); + dialog_unref(p, "unref dialog p just created via sip_alloc"); + /* sip_destroy(p); */ return 0; } /* Recalculate our side, and recalculate Call ID */ ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip); build_via(p); + ao2_t_unlink(dialogs, p, "About to change the callid -- remove the old name"); build_callid_pvt(p); + ao2_t_link(dialogs, p, "Linking in under new name"); /* Destroy this session after 32 secs */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } /* Send MWI */ ast_set_flag(&p->flags[0], SIP_OUTGOING); + /* the following will decrement the refcount on p as it finishes */ transmit_notify_with_mwi(p, newmsgs, oldmsgs, peer->vmexten); - + dialog_unref(p, "unref dialog ptr p just before it goes out of scope at the end of sip_send_mwi_to_peer."); return 0; } -/*! \brief helper function for the monitoring thread */ +/*! \brief helper function for the monitoring thread -- seems to be called with the assumption that the dialog is locked */ static void check_rtp_timeout(struct sip_pvt *dialog, time_t t) { /* If we have no RTP or no active owner, no need to check timers */ if (!dialog->rtp || !dialog->owner) return; /* If the call is not in UP state or redirected outside Asterisk, no need to check timers */ + if (dialog->owner->_state != AST_STATE_UP || dialog->redirip.sin_addr.s_addr) return; @@ -18491,14 +19334,13 @@ static void check_rtp_timeout(struct sip_pvt *dialog, time_t t) static void *do_monitor(void *data) { int res; - struct sip_pvt *dialog; time_t t; int reloading; /* Add an I/O event to our SIP UDP socket */ if (sipsock > -1) sipsock_read_id = ast_io_add(io, sipsock, sipsock_read, AST_IO_IN, NULL); - + /* From here on out, we die whenever asked */ for(;;) { /* Check for a reload request */ @@ -18522,36 +19364,17 @@ static void *do_monitor(void *data) } } -restartsearch: /* Check for dialogs needing to be killed */ - dialoglist_lock(); t = time(NULL); /* don't scan the dialogs list if it hasn't been a reasonable period of time since the last time we did it (when MWI is being sent, we can get back to this point every millisecond or less) */ - for (dialog = dialoglist; dialog; dialog = dialog->next) { - if (sip_pvt_trylock(dialog)) { - dialoglist_unlock(); - usleep(1); - goto restartsearch; - } + ao2_t_callback(dialogs, OBJ_UNLINK|OBJ_NODATA, dialog_needdestroy, &t, "callback to remove dialogs w/needdestroy"); - /* Check RTP timeouts and kill calls if we have a timeout set and do not get RTP */ - check_rtp_timeout(dialog, t); - /* If we have sessions that needs to be destroyed, do it now */ - /* Check if we have outstanding requests not responsed to or an active call - - if that's the case, wait with destruction */ - if (dialog->needdestroy && !dialog->packets && !dialog->owner) { - sip_pvt_unlock(dialog); - __sip_destroy(dialog, TRUE, FALSE); - dialoglist_unlock(); - usleep(1); - goto restartsearch; - } - sip_pvt_unlock(dialog); - } - dialoglist_unlock(); + /* the old methodology would be to restart the search for dialogs to delete with every + dialog that was found and destroyed, probably because the list contents would change, + so we'd need to restart. This isn't the best thing to do with callbacks. */ pthread_testcancel(); /* Wait for sched or io */ @@ -18611,10 +19434,7 @@ static void restart_session_timer(struct sip_pvt *p) } if (p->stimer->st_active == TRUE) { - if (ast_sched_del(sched, p->stimer->st_schedid) != 0) { - ast_log(LOG_WARNING, "ast_sched_del failed: %d - %s\n", p->stimer->st_schedid, p->callid); - } - + AST_SCHED_DEL(sched, p->stimer->st_schedid); ast_debug(2, "Session timer stopped: %d - %s\n", p->stimer->st_schedid, p->callid); start_session_timer(p); } @@ -18631,7 +19451,7 @@ static void stop_session_timer(struct sip_pvt *p) if (p->stimer->st_active == TRUE) { p->stimer->st_active = FALSE; - ast_sched_del(sched, p->stimer->st_schedid); + AST_SCHED_DEL(sched, p->stimer->st_schedid); ast_debug(2, "Session timer stopped: %d - %s\n", p->stimer->st_schedid, p->callid); } } @@ -18829,6 +19649,7 @@ int st_get_se(struct sip_pvt *p, int max) struct sip_user *up = find_user(p->username, 1); if (up) { p->stimer->st_cached_max_se = up->stimer.st_max_se; + unref_user(up, "unref user pointer from find_user call in st_get_se"); return (p->stimer->st_cached_max_se); } } @@ -18836,6 +19657,7 @@ int st_get_se(struct sip_pvt *p, int max) struct sip_peer *pp = find_peer(p->peername, NULL, 1); if (pp) { p->stimer->st_cached_max_se = pp->stimer.st_max_se; + unref_peer(pp, "unref peer pointer from find_peer call in st_get_se"); return (p->stimer->st_cached_max_se); } } @@ -18850,6 +19672,7 @@ int st_get_se(struct sip_pvt *p, int max) struct sip_user *up = find_user(p->username, 1); if (up) { p->stimer->st_cached_min_se = up->stimer.st_min_se; + unref_user(up, "unref user pointer from find_user call in st_get_se (2)"); return (p->stimer->st_cached_min_se); } } @@ -18857,6 +19680,7 @@ int st_get_se(struct sip_pvt *p, int max) struct sip_peer *pp = find_peer(p->peername, NULL, 1); if (pp) { p->stimer->st_cached_min_se = pp->stimer.st_min_se; + unref_peer(pp, "unref peer pointer from find_peer call in st_get_se (2)"); return (p->stimer->st_cached_min_se); } } @@ -18879,6 +19703,7 @@ enum st_refresher st_get_refresher(struct sip_pvt *p) struct sip_user *up = find_user(p->username, 1); if (up) { p->stimer->st_cached_ref = up->stimer.st_ref; + unref_user(up, "unref user pointer from find_user call in st_get_refresher"); return up->stimer.st_ref; } } @@ -18887,6 +19712,7 @@ enum st_refresher st_get_refresher(struct sip_pvt *p) struct sip_peer *pp = find_peer(p->peername, NULL, 1); if (pp) { p->stimer->st_cached_ref = pp->stimer.st_ref; + unref_peer(pp, "unref peer pointer from find_peer call in st_get_refresher"); return pp->stimer.st_ref; } } @@ -18911,6 +19737,7 @@ enum st_mode st_get_mode(struct sip_pvt *p) struct sip_user *up = find_user(p->username, 1); if (up) { p->stimer->st_cached_mode = up->stimer.st_mode_oper; + unref_user(up, "unref user pointer from find_user call in st_get_mode"); return up->stimer.st_mode_oper; } } @@ -18918,6 +19745,7 @@ enum st_mode st_get_mode(struct sip_pvt *p) struct sip_peer *pp = find_peer(p->peername, NULL, 1); if (pp) { p->stimer->st_cached_mode = pp->stimer.st_mode_oper; + unref_peer(pp, "unref peer pointer from find_peer call in st_get_mode"); return pp->stimer.st_mode_oper; } } @@ -18939,8 +19767,12 @@ static int sip_poke_noanswer(const void *data) if (global_regextenonqualify) register_peer_exten(peer, FALSE); } - if (peer->call) - peer->call = sip_destroy(peer->call); + if (peer->call) { + dialog_unlink_all(peer->call, TRUE, TRUE); + peer->call = dialog_unref(peer->call, "unref dialog peer->call"); + /* peer->call = sip_destroy(peer->call);*/ + } + peer->lastms = -1; ast_device_state_changed("SIP/%s", peer->name); /* Try again quickly */ @@ -18956,22 +19788,27 @@ static int sip_poke_peer(struct sip_peer *peer) { struct sip_pvt *p; int xmitres = 0; - + if (!peer->maxms || !peer->addr.sin_addr.s_addr) { /* IF we have no IP, or this isn't to be monitored, return immediately after clearing things out */ AST_SCHED_DEL(sched, peer->pokeexpire); + peer->lastms = 0; - peer->call = NULL; + if (peer->call) + peer->call = dialog_unref(peer->call, "unref dialog peer->call"); return 0; } if (peer->call) { if (sipdebug) ast_log(LOG_NOTICE, "Still have a QUALIFY dialog active, deleting\n"); - peer->call = sip_destroy(peer->call); + dialog_unlink_all(peer->call, TRUE, TRUE); + peer->call = dialog_unref(peer->call, "unref dialog peer->call"); + /* peer->call = sip_destroy(peer->call); */ } - if (!(p = peer->call = sip_alloc(NULL, NULL, 0, SIP_OPTIONS))) + if (!(p = sip_alloc(NULL, NULL, 0, SIP_OPTIONS))) return -1; + peer->call = dialog_ref(p, "copy sip alloc from p to peer->call"); p->sa = peer->addr; p->recv = peer->addr; @@ -18991,16 +19828,21 @@ static int sip_poke_peer(struct sip_peer *peer) /* Recalculate our side, and recalculate Call ID */ ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip); build_via(p); + ao2_t_unlink(dialogs, p, "About to change the callid -- remove the old name"); build_callid_pvt(p); + ao2_t_link(dialogs, p, "Linking in under new name"); AST_SCHED_DEL(sched, peer->pokeexpire); - p->relatedpeer = peer; + + if (p->relatedpeer) + p->relatedpeer = unref_peer(p->relatedpeer,"unsetting the relatedpeer field in the dialog, before it is set to something else."); + p->relatedpeer = ref_peer(peer, "setting the relatedpeer field in the dialog"); ast_set_flag(&p->flags[0], SIP_OUTGOING); #ifdef VOCAL_DATA_HACK ast_copy_string(p->username, "__VOCAL_DATA_SHOULD_READ_THE_SIP_SPEC__", sizeof(p->username)); - xmitres = transmit_invite(p, SIP_INVITE, 0, 2); + xmitres = transmit_invite(p, SIP_INVITE, 0, 2); /* sinks the p refcount */ #else - xmitres = transmit_invite(p, SIP_OPTIONS, 0, 2); + xmitres = transmit_invite(p, SIP_OPTIONS, 0, 2); /* sinks the p refcount */ #endif peer->ps = ast_tvnow(); if (xmitres == XMIT_ERROR) @@ -19009,7 +19851,7 @@ static int sip_poke_peer(struct sip_peer *peer) AST_SCHED_REPLACE(peer->pokeexpire, sched, peer->maxms * 2, sip_poke_noanswer, peer); } - + dialog_unref(p, "unref dialog at end of sip_poke_peer, obtained from sip_alloc, just before it goes out of scope"); return 0; } @@ -19100,7 +19942,7 @@ static int sip_devicestate(void *data) /* there is no address, it's unavailable */ res = AST_DEVICE_UNAVAILABLE; } - unref_peer(p); + unref_peer(p, "unref_peer, from sip_devicestate, release ref from find_peer"); } else { res = AST_DEVICE_UNKNOWN; } @@ -19155,7 +19997,9 @@ static struct ast_channel *sip_request_call(const char *type, int format, void * p->outgoing_call = TRUE; if (!(p->options = ast_calloc(1, sizeof(*p->options)))) { - sip_destroy(p); + dialog_unlink_all(p, TRUE, TRUE); + dialog_unref(p, "unref dialog p from mem fail"); + /* sip_destroy(p); */ ast_log(LOG_ERROR, "Unable to build option SIP data structure - Out of memory\n"); *cause = AST_CAUSE_SWITCH_CONGESTION; return NULL; @@ -19203,7 +20047,9 @@ static struct ast_channel *sip_request_call(const char *type, int format, void * if (create_addr(p, host, NULL)) { *cause = AST_CAUSE_UNREGISTERED; ast_debug(3, "Cant create SIP call - target device not registred\n"); - sip_destroy(p); + dialog_unlink_all(p, TRUE, TRUE); + dialog_unref(p, "unref dialog p UNREGISTERED"); + /* sip_destroy(p); */ return NULL; } if (ast_strlen_zero(p->peername) && ext) @@ -19211,7 +20057,9 @@ static struct ast_channel *sip_request_call(const char *type, int format, void * /* Recalculate our side, and recalculate Call ID */ ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip); build_via(p); + ao2_t_unlink(dialogs, p, "About to change the callid -- remove the old name"); build_callid_pvt(p); + ao2_t_link(dialogs, p, "Linking in under new name"); /* We have an extension to call, don't use the full contact here */ /* This to enable dialing registered peers with extension dialling, @@ -19242,8 +20090,11 @@ static struct ast_channel *sip_request_call(const char *type, int format, void * "Channel: %s\r\nChanneltype: %s\r\nSIPcallid: %s\r\nSIPfullcontact: %s\r\nPeername: %s\r\n", p->owner? p->owner->name : "", "SIP", p->callid, p->fullcontact, p->peername); sip_pvt_unlock(p); - if (!tmpc) - sip_destroy(p); + if (!tmpc) { + dialog_unlink_all(p, TRUE, TRUE); + /* sip_destroy(p); */ + } + dialog_unref(p, "toss pvt ptr at end of sip_request_call"); ast_update_use_count(); restart_monitor(); return tmpc; @@ -19566,18 +20417,14 @@ static struct sip_user *build_user(const char *name, struct ast_variable *v, str { struct sip_user *user; int format; - struct ast_ha *oldha = NULL; struct ast_flags userflags[2] = {{(0)}}; struct ast_flags mask[2] = {{(0)}}; - - if (!(user = ast_calloc(1, sizeof(*user)))) + if (!(user = ao2_t_alloc(sizeof(*user), sip_destroy_user_fn, "allocate a user struct"))) return NULL; - suserobjs++; - ASTOBJ_INIT(user); + ast_atomic_fetchadd_int(&suserobjs, 1); ast_copy_string(user->name, name, sizeof(user->name)); - oldha = user->ha; user->ha = NULL; ast_copy_flags(&user->flags[0], &global_flags[0], SIP_FLAGS_TO_COPY); ast_copy_flags(&user->flags[1], &global_flags[1], SIP_PAGE2_FLAGS_TO_COPY); @@ -19710,7 +20557,6 @@ static struct sip_user *build_user(const char *name, struct ast_variable *v, str ast_copy_flags(&user->flags[1], &userflags[1], mask[1].flags); if (ast_test_flag(&user->flags[1], SIP_PAGE2_ALLOWSUBSCRIBE)) global_allowsubscribe = TRUE; /* No global ban any more */ - ast_free_ha(oldha); return user; } @@ -19772,11 +20618,10 @@ static struct sip_peer *temp_peer(const char *name) { struct sip_peer *peer; - if (!(peer = ast_calloc(1, sizeof(*peer)))) + if (!(peer = ao2_t_alloc(sizeof(*peer), sip_destroy_peer_fn, "allocate a peer struct"))) return NULL; - apeerobjs++; - ASTOBJ_INIT(peer); + ast_atomic_fetchadd_int(&apeerobjs, 1); set_peer_defaults(peer); ast_copy_string(peer->name, name, sizeof(peer->name)); @@ -19826,34 +20671,36 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str struct ast_flags peerflags[2] = {{(0)}}; struct ast_flags mask[2] = {{(0)}}; char callback[256] = ""; + struct sip_peer tmp_peer; const char *srvlookup = NULL; static int deprecation_warning = 1; - - if (!realtime) + + if (!realtime) { /* Note we do NOT use find_peer here, to avoid realtime recursion */ /* We also use a case-sensitive comparison (unlike find_peer) so that case changes made to the peer name will be properly handled during reload */ - peer = ASTOBJ_CONTAINER_FIND_UNLINK_FULL(&peerl, name, name, 0, 0, strcmp); - + ast_copy_string(tmp_peer.name, name, sizeof(tmp_peer.name)); + peer = ao2_t_find(peers, &tmp_peer, OBJ_POINTER|OBJ_UNLINK, "find and unlink peer from peers table"); + } + if (peer) { /* Already in the list, remove it and it will be added back (or FREE'd) */ found++; - if (!(peer->objflags & ASTOBJ_FLAG_MARKED)) + if (!(peer->the_mark)) firstpass = 0; } else { - if (!(peer = ast_calloc(1, sizeof(*peer)))) + if (!(peer = ao2_t_alloc(sizeof(*peer), sip_destroy_peer_fn, "allocate a peer struct"))) return NULL; if (realtime) { - rpeerobjs++; + ast_atomic_fetchadd_int(&rpeerobjs, 1); ast_debug(3, "-REALTIME- peer built. Name: %s. Peer objects: %d\n", name, rpeerobjs); } else - speerobjs++; - ASTOBJ_INIT(peer); + ast_atomic_fetchadd_int(&speerobjs, 1); } - /* Note that our peer HAS had its reference count incrased */ + /* Note that our peer HAS had its reference count increased */ if (firstpass) { peer->lastmsgssent = -1; oldha = peer->ha; @@ -19952,7 +20799,7 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str } } else if (!strcasecmp(v->name, "defaultip")) { if (ast_get_ip(&peer->defaddr, v->value)) { - unref_peer(peer); + unref_peer(peer, "unref_peer: from build_peer defaultip"); return NULL; } } else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny")) { @@ -20118,7 +20965,7 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str snprintf(transport, sizeof(transport), "_sip._%s", get_transport(peer->socket.type)); if (ast_dnsmgr_lookup(srvlookup, &peer->addr, &peer->dnsmgr, global_srvlookup ? transport : NULL)) { - unref_peer(peer); + unref_peer(peer, "getting rid of a peer pointer"); return NULL; } @@ -20157,8 +21004,7 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str * way, then we will get events when app_voicemail gets loaded. */ sip_send_mwi_to_peer(peer, NULL, 1); } - - ASTOBJ_UNMARK(peer); + peer->the_mark = 0; ast_free_ha(oldha); if (!ast_strlen_zero(callback)) { /* build string from peer info */ @@ -20173,6 +21019,13 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str return peer; } +static int peer_markall_func(void *userobj, void *arg, int flags) +{ + struct sip_peer *peer = userobj; + peer->the_mark = 1; + return 0; +} + /*! \brief Re-read SIP.conf config file \note This function reloads all config data, except for active peers (with registrations). They will only @@ -20192,7 +21045,10 @@ static int reload_config(enum channelreloadreason reason) int auto_sip_domains = FALSE; struct sockaddr_in old_bindaddr = bindaddr; int registry_count = 0, peer_count = 0, user_count = 0; - + time_t run_start, run_end; + + run_start = time(0); + cfg = ast_config_load(config, config_flags); /* We *must* have a config file otherwise stop immediately */ @@ -20229,25 +21085,34 @@ static int reload_config(enum channelreloadreason reason) /* First, destroy all outstanding registry calls */ /* This is needed, since otherwise active registry entries will not be destroyed */ - ASTOBJ_CONTAINER_TRAVERSE(®l, 1, do { - ASTOBJ_RDLOCK(iterator); - if (iterator->call) { - ast_debug(3, "Destroying active SIP dialog for registry %s@%s\n", iterator->username, iterator->hostname); - /* This will also remove references to the registry */ - iterator->call = sip_destroy(iterator->call); - } - ASTOBJ_UNLOCK(iterator); - + ASTOBJ_CONTAINER_TRAVERSE(®l, 1, do { /* regl is locked */ + + /* avoid a deadlock in the unlink_all call, if iterator->call's (a dialog) registry entry + is this registry entry. In other words, if the dialog we are pointing to points back to + us, then if we get a lock on this object, and try to UNREF it, we will deadlock, because + we already ... NO. This is not the problem. */ + ASTOBJ_RDLOCK(iterator); /* now regl is locked, and the object is also locked */ + if (iterator->call) { + ast_debug(3, "Destroying active SIP dialog for registry %s@%s\n", iterator->username, iterator->hostname); + /* This will also remove references to the registry */ + dialog_unlink_all(iterator->call, TRUE, TRUE); + iterator->call = dialog_unref(iterator->call, "remove iterator->call from registry traversal"); + /* iterator->call = sip_destroy(iterator->call); */ + } + ASTOBJ_UNLOCK(iterator); + } while(0)); /* Then, actually destroy users and registry */ - ASTOBJ_CONTAINER_DESTROYALL(&userl, sip_destroy_user); + ao2_t_ref(users, -1, "destroy users table"); ast_debug(4, "--------------- Done destroying user list\n"); ASTOBJ_CONTAINER_DESTROYALL(®l, sip_registry_destroy); ast_debug(4, "--------------- Done destroying registry list\n"); - ASTOBJ_CONTAINER_MARKALL(&peerl); + ao2_t_callback(peers, OBJ_NODATA, peer_markall_func, 0, "callback to mark all peers"); + /* reinstate the user table */ + users = ao2_t_container_alloc(hash_user_size, user_hash_cb, user_cmp_cb, "allocate users"); } - + /* Reset certificate handling for TLS sessions */ default_tls_cfg.certfile = ast_strdup(AST_CERTFILE); /*XXX Not sure if this is useful */ default_tls_cfg.cipher = ast_strdup(""); @@ -20371,6 +21236,7 @@ static int reload_config(enum channelreloadreason reason) ast_clear_flag(&global_flags[1], SIP_PAGE2_VIDEOSUPPORT); ast_clear_flag(&global_flags[1], SIP_PAGE2_TEXTSUPPORT); + /* Read the [general] config section of sip.conf (or from realtime config) */ for (v = ast_variable_browse(cfg, "general"); v; v = v->next) { if (handle_common_options(&global_flags[0], &dummy[0], v)) @@ -20650,6 +21516,27 @@ static int reload_config(enum channelreloadreason reason) } else { ast_log(LOG_WARNING, "Invalid port number '%s' at line %d of %s\n", v->value, v->lineno, config); } + } else if (!strcasecmp(v->name, "hash_user")) { + int i; + if (sscanf(v->value, "%d", &i) == 1 && i > 2) { + hash_user_size = i; + } else { + ast_log(LOG_WARNING, "Invalid hash_user size '%s' at line %d of %s -- should be much larger than 2\n", v->value, v->lineno, config); + } + } else if (!strcasecmp(v->name, "hash_peer")) { + int i; + if (sscanf(v->value, "%d", &i) == 1 && i > 2) { + hash_peer_size = i; + } else { + ast_log(LOG_WARNING, "Invalid hash_peer size '%s' at line %d of %s -- should be much larger than 2\n", v->value, v->lineno, config); + } + } else if (!strcasecmp(v->name, "hash_dialog")) { + int i; + if (sscanf(v->value, "%d", &i) == 1 && i > 2) { + hash_dialog_size = i; + } else { + ast_log(LOG_WARNING, "Invalid hash_dialog size '%s' at line %d of %s -- should be much larger than 2\n", v->value, v->lineno, config); + } } else if (!strcasecmp(v->name, "qualify")) { if (!strcasecmp(v->value, "no")) { default_qualify = 0; @@ -20738,14 +21625,18 @@ static int reload_config(enum channelreloadreason reason) if (ast_true(hassip) || (!hassip && genhassip)) { user = build_user(cat, gen, ast_variable_browse(ucfg, cat), 0); if (user) { - ASTOBJ_CONTAINER_LINK(&userl,user); - ASTOBJ_UNREF(user, sip_destroy_user); + ao2_t_link(users, user, "link user into users table"); + unref_user(user, "Unref the result of build_user. Now, the table link is the only one left."); user_count++; } peer = build_peer(cat, gen, ast_variable_browse(ucfg, cat), 0); if (peer) { - ASTOBJ_CONTAINER_LINK(&peerl, peer); - unref_peer(peer); + ao2_t_link(peers, peer, "link peer into peer table"); + if (peer->addr.sin_addr.s_addr) { + ao2_t_link(peers_by_ip, peer, "link peer into peers_by_ip table"); + } + + unref_peer(peer, "unref_peer: from reload_config"); peer_count++; } } @@ -20804,21 +21695,25 @@ static int reload_config(enum channelreloadreason reason) if (is_user) { user = build_user(cat, ast_variable_browse(cfg, cat), NULL, 0); if (user) { - ASTOBJ_CONTAINER_LINK(&userl, user); - unref_user(user); + ao2_t_link(users, user, "link user into users table"); + unref_user(user, "Unref the result of build_user. Now, the table link is the only one left."); user_count++; } } if (is_peer) { peer = build_peer(cat, ast_variable_browse(cfg, cat), NULL, 0); if (peer) { - ASTOBJ_CONTAINER_LINK(&peerl, peer); - unref_peer(peer); + ao2_t_link(peers, peer, "link peer into peers table"); + if (peer->addr.sin_addr.s_addr) { + ao2_t_link(peers_by_ip, peer, "link peer into peers_by_ip table"); + } + unref_peer(peer, "unref the result of the build_peer call. Now, the links from the tables are the only ones left."); peer_count++; } } } } + bindaddr.sin_family = AF_INET; internip = bindaddr; if (ast_find_ourip(&internip.sin_addr, bindaddr)) { @@ -20917,6 +21812,8 @@ static int reload_config(enum channelreloadreason reason) /* Done, tell the manager */ manager_event(EVENT_FLAG_SYSTEM, "ChannelReload", "ChannelType: SIP\r\nReloadReason: %s\r\nRegistry_Count: %d\r\nPeer_Count: %d\r\nUser_Count: %d\r\n", channelreloadreason2txt(reason), registry_count, peer_count, user_count); + run_end = time(0); + ast_log(LOG_NOTICE, "reload_config done...Runtime= %d sec\n", (int)(run_end-run_start)); return 0; } @@ -21374,17 +22271,21 @@ static int sip_get_codec(struct ast_channel *chan) static void sip_poke_all_peers(void) { int ms = 0; + struct ao2_iterator i; + struct sip_peer *peer; + + i = ao2_iterator_init(peers, 0); if (!speerobjs) /* No peers, just give up */ return; - ASTOBJ_CONTAINER_TRAVERSE(&peerl, 1, do { - ASTOBJ_WRLOCK(iterator); + while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table"))) { + ao2_lock(peer); ms += 100; - AST_SCHED_REPLACE(iterator->pokeexpire, sched, ms, sip_poke_peer_s, iterator); - ASTOBJ_UNLOCK(iterator); - } while (0) - ); + AST_SCHED_REPLACE(peer->pokeexpire, sched, ms, sip_poke_peer_s, peer); + ao2_unlock(peer); + unref_peer(peer, "toss iterator peer ptr"); + } } /*! \brief Send all known registrations */ @@ -21401,8 +22302,10 @@ static void sip_send_all_registers(void) ASTOBJ_CONTAINER_TRAVERSE(®l, 1, do { ASTOBJ_WRLOCK(iterator); ms += regspacing; - AST_SCHED_REPLACE(iterator->expire, - sched, ms, sip_reregister, iterator); + AST_SCHED_REPLACE_UNREF(iterator->expire, sched, ms, sip_reregister, iterator, + registry_unref(_data, "REPLACE sched del decs the refcount"), + registry_unref(iterator, "REPLACE sched add failure decs the refcount"), + registry_addref(iterator, "REPLACE sched add incs the refcount")); ASTOBJ_UNLOCK(iterator); } while (0) ); @@ -21411,10 +22314,15 @@ static void sip_send_all_registers(void) /*! \brief Reload module */ static int sip_do_reload(enum channelreloadreason reason) { + time_t start_poke, end_poke; + reload_config(reason); + ast_sched_dump(sched); + start_poke = time(0); /* Prune peers who still are supposed to be deleted */ - ASTOBJ_CONTAINER_PRUNE_MARKED(&peerl, sip_destroy_peer); + ao2_t_callback(peers, OBJ_NODATA|OBJ_UNLINK, peer_is_marked, 0, "callback to remove marked peers"); + ast_debug(4, "--------------- Done destroying pruned peers\n"); /* Send qualify (OPTIONS) to all peers */ @@ -21422,6 +22330,9 @@ static int sip_do_reload(enum channelreloadreason reason) /* Register with all services */ sip_send_all_registers(); + end_poke = time(0); + + ast_log(LOG_NOTICE, "do_reload finished. peer poke/prune reg contact time = %d sec.\n", (int)(end_poke-start_poke)); ast_debug(4, "--------------- SIP reload done\n"); @@ -21472,6 +22383,7 @@ static struct ast_cli_entry cli_sip[] = { AST_CLI_DEFINE(sip_show_inuse, "List all inuse/limits"), AST_CLI_DEFINE(sip_show_objects, "List all SIP object allocations"), AST_CLI_DEFINE(sip_show_peers, "List defined SIP peers"), + AST_CLI_DEFINE(sip_dbdump, "dump peer info into realtime db sql format"), AST_CLI_DEFINE(sip_show_registry, "List SIP registration status"), AST_CLI_DEFINE(sip_unregister, "Unregister (force expiration) a SIP peer from the registery\n"), AST_CLI_DEFINE(sip_show_settings, "Show SIP global settings"), @@ -21481,6 +22393,7 @@ static struct ast_cli_entry cli_sip[] = { AST_CLI_DEFINE(sip_show_peer, "Show details on specific SIP peer"), AST_CLI_DEFINE(sip_show_users, "List defined SIP users"), AST_CLI_DEFINE(sip_show_user, "Show details on specific SIP user"), + AST_CLI_DEFINE(sip_show_sched, "Present a report on the status of the sched queue"), AST_CLI_DEFINE(sip_prune_realtime, "Prune cached Realtime users/peers"), AST_CLI_DEFINE(sip_do_debug, "Enable/Disable SIP debugging"), AST_CLI_DEFINE(sip_set_history, "Enable/Disable SIP history", .deprecate_cmd = &cli_sip_do_history_deprecated), @@ -21492,9 +22405,14 @@ static struct ast_cli_entry cli_sip[] = { static int load_module(void) { ast_verbose("SIP channel loading...\n"); - ASTOBJ_CONTAINER_INIT(&userl); /* User object list */ - ASTOBJ_CONTAINER_INIT(&peerl); /* Peer object list */ - ASTOBJ_CONTAINER_INIT(®l); /* Registry object list */ + /* the fact that ao2_containers can't resize automatically is a major worry! */ + /* if the number of objects gets above MAX_XXX_BUCKETS, things will slow down */ + users = ao2_t_container_alloc(hash_user_size, user_hash_cb, user_cmp_cb, "allocate users"); + peers = ao2_t_container_alloc(hash_peer_size, peer_hash_cb, peer_cmp_cb, "allocate peers"); + peers_by_ip = ao2_t_container_alloc(hash_peer_size, peer_iphash_cb, peer_ipcmp_cb, "allocate peers_by_ip"); + dialogs = ao2_t_container_alloc(hash_dialog_size, dialog_hash_cb, dialog_cmp_cb, "allocate dialogs"); + + ASTOBJ_CONTAINER_INIT(®l); /* Registry object list -- not searched for anything */ if (!(sched = sched_context_create())) { ast_log(LOG_ERROR, "Unable to create scheduler context\n"); @@ -21565,9 +22483,12 @@ static int load_module(void) /*! \brief PBX unload module API */ static int unload_module(void) { - struct sip_pvt *p, *pl; + struct sip_pvt *p; struct sip_threadinfo *th; struct ast_context *con; + struct ao2_iterator i; + + ast_sched_dump(sched); /* First, take us out of the channel type list */ ast_channel_unregister(&sip_tech); @@ -21595,7 +22516,7 @@ static int unload_module(void) ast_manager_unregister("SIPpeers"); ast_manager_unregister("SIPshowpeer"); ast_manager_unregister("SIPshowregistry"); - + /* Kill TCP/TLS server threads */ if (sip_tcp_desc.master) ast_tcptls_server_stop(&sip_tcp_desc); @@ -21615,13 +22536,13 @@ static int unload_module(void) AST_LIST_TRAVERSE_SAFE_END; AST_LIST_UNLOCK(&threadl); - dialoglist_lock(); /* Hangup all dialogs if they have an owner */ - for (p = dialoglist; p ; p = p->next) { + i = ao2_iterator_init(dialogs, 0); + while ((p = ao2_t_iterator_next(&i, "iterate thru dialogs"))) { if (p->owner) ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD); + ao2_t_ref(p, -1, "toss dialog ptr from iterator_next"); } - dialoglist_unlock(); ast_mutex_lock(&monlock); if (monitor_thread && (monitor_thread != AST_PTHREADT_STOP) && (monitor_thread != AST_PTHREADT_NULL)) { @@ -21632,27 +22553,19 @@ static int unload_module(void) monitor_thread = AST_PTHREADT_STOP; ast_mutex_unlock(&monlock); -restartdestroy: - dialoglist_lock(); /* Destroy all the dialogs and free their memory */ - p = dialoglist; - while (p) { - pl = p; - p = p->next; - if (__sip_destroy(pl, TRUE, TRUE) < 0) { - /* Something is still bridged, let it react to getting a hangup */ - dialoglist = p; - dialoglist_unlock(); - usleep(1); - goto restartdestroy; - } + i = ao2_iterator_init(dialogs, 0); + while ((p = ao2_t_iterator_next(&i, "iterate thru dialogs"))) { + dialog_unlink_all(p, TRUE, TRUE); + ao2_t_ref(p, -1, "throw away iterator result"); } - dialoglist = NULL; - dialoglist_unlock(); /* Free memory for local network address mask */ ast_free_ha(localaddr); + clear_realm_authentication(authl); + + if (default_tls_cfg.certfile) ast_free(default_tls_cfg.certfile); if (default_tls_cfg.cipher) @@ -21662,14 +22575,14 @@ restartdestroy: if (default_tls_cfg.capath) ast_free(default_tls_cfg.capath); - ASTOBJ_CONTAINER_DESTROYALL(&userl, sip_destroy_user); - ASTOBJ_CONTAINER_DESTROY(&userl); - ASTOBJ_CONTAINER_DESTROYALL(&peerl, sip_destroy_peer); - ASTOBJ_CONTAINER_DESTROY(&peerl); ASTOBJ_CONTAINER_DESTROYALL(®l, sip_registry_destroy); ASTOBJ_CONTAINER_DESTROY(®l); - clear_realm_authentication(authl); + ao2_t_ref(peers, -1, "unref the peers table"); + ao2_t_ref(peers_by_ip, -1, "unref the peers_by_ip table"); + ao2_t_ref(users, -1, "unref the users table"); + ao2_t_ref(dialogs, -1, "unref the dialogs table"); + clear_sip_domains(); close(sipsock); sched_context_destroy(sched); diff --git a/configs/sip.conf.sample b/configs/sip.conf.sample index 1080777aa583242979c47cb0fa15e4878e4ffdf3..10293a77ca1e70a11ee51e3b88ea55b0585a5be7 100644 --- a/configs/sip.conf.sample +++ b/configs/sip.conf.sample @@ -67,7 +67,21 @@ context=default ; Default context for incoming calls ;match_auth_username=yes ; if available, match user entry using the ; 'username' field from the authentication line ; instead of the From: field. - +;; +;; hash table sizes. For maximum efficiency, adjust the following +;; values to be slightly larger than the maximum number of users/peers. +;; Too large, and space is wasted. Too small, and things will run slower. +;; 563 is probably way too big for small (home) applications, but it +;; should cover most small/medium sites. +;; it is recommended to make the sizes be a prime number! +;; This was internally set to 17 for small-memory applications... +;; All tables default to 563, except when compiled in LOW_MEMORY mode, +;; in which case, they default to 17. You can override this by uncommenting +;; the following, and changing the values. +;hash_users=563 +;hash_peers=563 +;hash_dialogs=563 + allowoverlap=no ; Disable overlap dialing support. (Default is yes) ;allowtransfer=no ; Disable all transfers (unless enabled in peers or users) ; Default is enabled @@ -126,7 +140,7 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls ; Disabling DNS SRV lookups disables the ; ability to place SIP calls based on domain ; names to some other SIP users on the Internet - + ;domain=mydomain.tld ; Set default domain for this host ; If configured, Asterisk will only allow ; INVITE and REFER to non-local domains diff --git a/doc/chan_sip-perf-testing.txt b/doc/chan_sip-perf-testing.txt new file mode 100644 index 0000000000000000000000000000000000000000..85b22bddc76d590f09a358a137db87b0e3bcce00 --- /dev/null +++ b/doc/chan_sip-perf-testing.txt @@ -0,0 +1,110 @@ +Measuring the SIP channel driver's Performance +============================================== + +This file documents the methods I used to measure +the performance of the SIP channel driver, in +terms of maximum simultaneous calls and how quickly +it could handle incoming calls. + +Knowing these limitations can be valuable to those +implementing PBX's in 'large' environments. Will your +installation handle expected call volume? + +Quoting these numbers can be totally useless for other +installations. Minor changes like the amount of RAM +in a system, the speed of the ethernet, the amount of +cache in the CPU, the CPU clock speed, whether or not +you log CDR's, etc. can affect the numbers greatly. + +In my set up, I had a dedicated test machine running Asterisk, +and another machine which ran sipp, connected together with +ethernet. + +The version of sipp that I used was sipp-2.0.1; however, +I have reason to believe that other versions would work +just as well. + +On the asterisk machine, I included the following in my +extensions.ael file: + +context test11 +{ + s => { + Answer(); + while (1) { + Background(demo-instruct); + } + Hangup(); + } + _X. => { + Answer(); + while (1) { + Background(demo-instruct); + } + Hangup(); + } +} + +Basically, incoming SIP calls are answered, and +the demo-instruct sound file is played endlessly +to the caller. This test depends on the calling +party to hang up, thus allowing sipp to determine +the length of a call. + +The sip.conf file has this entry: + +[asterisk02] +type=friend +context=test11 +host=192.168.134.240 ;; the address of the host you will be running sipp on +user=sipp +canreinvite=no +disallow=all +allow=ulaw + +Note that it's pretty simplistic; no authentication beyond the host ip, +and it uses ulaw, which is pretty efficient, low-cpu-intensive codec. + + +To measure the impact of incoming call traffic on the Asterisk +machine, I run vmstat. It gives me an idea of the cpu usage by +Asterisk. The most common failure mode of Asterisk at high call volumes, +is that the CPU reaches 100% utilization, and then cannot keep up with +the workload, resulting in timeouts and other failures, which swiftly +compound and cascade, until gross failure ensues. Watch the CPU Idle % +numbers. + +I learned to split the testing into two modes: one for just call call processing +power, in the which we had relatively few simultaneous calls in place, +and another where we allow the the number of simultaneous calls to quickly +reach a set maximum, and then rerun sipp, looking for the maximum. + +Call processing power is measured with extremely short duration calls: + + ./sipp -sn uac 192.168.134.252 -s 12 -d 100 -l 256 + +The above tells sipp to call your asterisk test machine (192.168.134.252) +at extension 12, each call lasts just .1 second, with a limit of 256 simultaneous +calls. The simultaneous calls will be the rate/sec of incoming calls times the call length, +so 1 simultaneous call at 10 calls/sec, and 45 at 450 calls/sec. Setting the limit +to 256 implies you do not intend to test above 2560 calls/sec. + +Sipp starts at 10 calls/sec, and you can slowly increase the speed by hitting '*' or '+'. +Watch your cpu utilization on the asterisk server. When you approach 100%, you have found +your limit. + + +Simultaneous calls can be measured with very long duration calls: + +./sipp -sn uac 192.168.134.252 -s 12 -d 100000 -l 270 + +This will place 100 sec duration calls to Asterisk. The number of simultaneous +calls will increase until the maximum of 270 is reached. If Asterisk survives +this number and is not at 100% cpu utilization, you can stop sipp and run it again +with a higher -l argument. + + +By changing one Asterisk parameter at a time, you can get a feel for how much that change +will affect performance. + +