diff options
-rw-r--r-- | channels/chan_iax2.c | 787 | ||||
-rw-r--r-- | include/asterisk/astobj2.h | 515 | ||||
-rw-r--r-- | include/asterisk/strings.h | 20 | ||||
-rw-r--r-- | main/Makefile | 3 | ||||
-rw-r--r-- | main/astobj2.c | 685 |
5 files changed, 1650 insertions, 360 deletions
diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c index e9e2e7465..07917f8c4 100644 --- a/channels/chan_iax2.c +++ b/channels/chan_iax2.c @@ -93,6 +93,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stringfields.h" #include "asterisk/linkedlists.h" #include "asterisk/event.h" +#include "asterisk/astobj2.h" #include "iax2.h" #include "iax2-parser.h" @@ -323,7 +324,6 @@ struct iax2_user { struct ast_ha *ha; struct iax2_context *contexts; struct ast_variable *vars; - AST_LIST_ENTRY(iax2_user) entry; }; struct iax2_peer { @@ -378,7 +378,6 @@ struct iax2_peer { struct ast_event_sub *mwi_event_sub; struct ast_ha *ha; - AST_LIST_ENTRY(iax2_peer) entry; }; #define IAX2_TRUNK_PREFACE (sizeof(struct iax_frame) + sizeof(struct ast_iax2_meta_hdr) + sizeof(struct ast_iax2_meta_trunk_hdr)) @@ -659,9 +658,24 @@ struct chan_iax2_pvt { */ static AST_LIST_HEAD_STATIC(frame_queue, iax_frame); -static AST_LIST_HEAD_STATIC(users, iax2_user); +/*! + * This module will get much higher performance when doing a lot of + * user and peer lookups if the number of buckets is increased from 1. + * However, to maintain old behavior for Asterisk 1.4, these are set to + * 1 by default. When using multiple buckets, search order through these + * containers is considered random, so you will not be able to depend on + * the order the entires are specified in iax.conf for matching order. */ +#ifdef LOW_MEMORY +#define MAX_PEER_BUCKETS 1 +/* #define MAX_PEER_BUCKETS 17 */ +#else +#define MAX_PEER_BUCKETS 1 +/* #define MAX_PEER_BUCKETS 563 */ +#endif +static ao2_container *peers; -static AST_LIST_HEAD_STATIC(peers, iax2_peer); +#define MAX_USER_BUCKETS MAX_PEER_BUCKETS +static ao2_container *users; static AST_LIST_HEAD_STATIC(firmwares, iax_firmware); @@ -701,7 +715,6 @@ static AST_LIST_HEAD_STATIC(dpcache, iax2_dpcache); static void reg_source_db(struct iax2_peer *p); static struct iax2_peer *realtime_peer(const char *peername, struct sockaddr_in *sin); -static void destroy_peer(struct iax2_peer *peer); static int ast_cli_netstats(struct mansession *s, int fd, int limit_fmt); enum iax2_thread_iostate { @@ -892,7 +905,6 @@ static struct ast_frame *iax2_read(struct ast_channel *c); static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly); static struct iax2_user *build_user(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly); static void realtime_update_peer(const char *peername, struct sockaddr_in *sin, time_t regtime); -static void destroy_user(struct iax2_user *user); static void prune_peers(void); static void *iax2_dup_variable_datastore(void *); static void iax2_free_variable_datastore(void *); @@ -1185,51 +1197,112 @@ static int uncompress_subclass(unsigned char csub) } /*! + * \note The only member of the peer passed here guaranteed to be set is the name field + */ +static int peer_hash_cb(const void *obj, const int flags) +{ + const struct iax2_peer *peer = obj; + + return ast_str_hash(peer->name); +} + +/*! + * \note The only member of the peer passed here guaranteed to be set is the name field + */ +static int peer_cmp_cb(void *obj, void *arg, int flags) +{ + struct iax2_peer *peer = obj, *peer2 = arg; + + return !strcasecmp(peer->name, peer2->name) ? CMP_MATCH : 0; +} + +/*! + * \note The only member of the user passed here guaranteed to be set is the name field + */ +static int user_hash_cb(const void *obj, const int flags) +{ + const struct iax2_user *user = obj; + + return ast_str_hash(user->name); +} + +/*! + * \note The only member of the user passed here guaranteed to be set is the name field + */ +static int user_cmp_cb(void *obj, void *arg, int flags) +{ + struct iax2_user *user = obj, *user2 = arg; + + return !strcasecmp(user->name, user2->name) ? CMP_MATCH : 0; +} + +/*! * \note This funtion calls realtime_peer -> reg_source_db -> iax2_poke_peer -> find_callno, * so do not call it with a pvt lock held. */ static struct iax2_peer *find_peer(const char *name, int realtime) { struct iax2_peer *peer = NULL; + struct iax2_peer tmp_peer = { + .name = name, + }; - /* Grab peer from linked list */ - AST_LIST_LOCK(&peers); - AST_LIST_TRAVERSE(&peers, peer, entry) { - if (!strcasecmp(peer->name, name)) { - break; - } - } - AST_LIST_UNLOCK(&peers); + peer = ao2_find(peers, &tmp_peer, OBJ_POINTER); /* Now go for realtime if applicable */ if(!peer && realtime) peer = realtime_peer(name, NULL); + + return peer; +} + +static struct iax2_peer *peer_ref(struct iax2_peer *peer) +{ + ao2_ref(peer, +1); return peer; } -static int iax2_getpeername(struct sockaddr_in sin, char *host, int len, int lockpeer) +static inline struct iax2_peer *peer_unref(struct iax2_peer *peer) +{ + ao2_ref(peer, -1); + return NULL; +} + +static inline struct iax2_user *user_ref(struct iax2_user *user) +{ + ao2_ref(user, +1); + return user; +} + +static inline struct iax2_user *user_unref(struct iax2_user *user) +{ + ao2_ref(user, -1); + return NULL; +} + +static int iax2_getpeername(struct sockaddr_in sin, char *host, int len) { struct iax2_peer *peer = NULL; int res = 0; + ao2_iterator i; - if (lockpeer) - AST_LIST_LOCK(&peers); - AST_LIST_TRAVERSE(&peers, peer, entry) { + i = ao2_iterator_init(peers, 0); + while ((peer = ao2_iterator_next(&i))) { if ((peer->addr.sin_addr.s_addr == sin.sin_addr.s_addr) && (peer->addr.sin_port == sin.sin_port)) { ast_copy_string(host, peer->name, len); + peer_unref(peer); res = 1; break; } + peer_unref(peer); } - if (lockpeer) - AST_LIST_UNLOCK(&peers); + if (!peer) { peer = realtime_peer(NULL, &sin); if (peer) { ast_copy_string(host, peer->name, len); - if (ast_test_flag(peer, IAX_TEMPONLY)) - destroy_peer(peer); + peer_unref(peer); res = 1; } } @@ -1237,7 +1310,7 @@ static int iax2_getpeername(struct sockaddr_in sin, char *host, int len, int loc return res; } -static struct chan_iax2_pvt *new_iax(struct sockaddr_in *sin, int lockpeer, const char *host) +static struct chan_iax2_pvt *new_iax(struct sockaddr_in *sin, const char *host) { struct chan_iax2_pvt *tmp; jb_conf jbconf; @@ -1402,7 +1475,7 @@ static int make_trunk(unsigned short callno, int locked) * * \note Calling this function while holding another pvt lock can cause a deadlock. */ -static int find_callno(unsigned short callno, unsigned short dcallno, struct sockaddr_in *sin, int new, int lockpeer, int sockfd) +static int find_callno(unsigned short callno, unsigned short dcallno, struct sockaddr_in *sin, int new, int sockfd) { int res = 0; int x; @@ -1438,7 +1511,7 @@ static int find_callno(unsigned short callno, unsigned short dcallno, struct soc * this is just checking for a peer that has that IP/port and * assuming that we have a user of the same name. This isn't always * correct, but it will be changed if needed after authentication. */ - if (!iax2_getpeername(*sin, host, sizeof(host), lockpeer)) + if (!iax2_getpeername(*sin, host, sizeof(host))) snprintf(host, sizeof(host), "%s:%d", ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); now = ast_tvnow(); for (x=1;x<TRUNK_CALL_START;x++) { @@ -1452,7 +1525,7 @@ static int find_callno(unsigned short callno, unsigned short dcallno, struct soc ast_log(LOG_WARNING, "No more space\n"); return 0; } - iaxs[x] = new_iax(sin, lockpeer, host); + iaxs[x] = new_iax(sin, host); update_max_nontrunk(); if (iaxs[x]) { if (iaxdebug) @@ -1940,18 +2013,19 @@ static int send_packet(struct iax_frame *f) static void iax2_destroy_helper(struct chan_iax2_pvt *pvt) { - struct iax2_user *user = NULL; - /* Decrement AUTHREQ count if needed */ if (ast_test_flag(pvt, IAX_MAXAUTHREQ)) { - AST_LIST_LOCK(&users); - AST_LIST_TRAVERSE(&users, user, entry) { - if (!strcmp(user->name, pvt->username)) { - user->curauthreq--; - break; - } + struct iax2_user *user; + struct iax2_user tmp_user = { + .name = pvt->username, + }; + + user = ao2_find(users, &tmp_user, OBJ_POINTER); + if (user) { + ast_atomic_fetchadd_int(&user->curauthreq, -1); + user_unref(user); } - AST_LIST_UNLOCK(&users); + ast_clear_flag(pvt, IAX_MAXAUTHREQ); } /* No more pings or lagrq's */ @@ -2191,6 +2265,7 @@ static int iax2_prune_realtime(int fd, int argc, char *argv[]) } else { ast_cli(fd, "SORRY peer %s is not eligible for this operation.\n", argv[3]); } + peer_unref(peer); } else { ast_cli(fd, "SORRY peer %s was not found in the cache.\n", argv[3]); } @@ -2317,8 +2392,7 @@ static int iax2_show_peer(int fd, int argc, char *argv[]) ast_cli(fd, "%s\n",status); ast_cli(fd, " Qualify : every %dms when OK, every %dms when UNREACHABLE (sample smoothing %s)\n", peer->pokefreqok, peer->pokefreqnotok, peer->smoothing ? "On" : "Off"); ast_cli(fd,"\n"); - if (ast_test_flag(peer, IAX_TEMPONLY)) - destroy_peer(peer); + peer_unref(peer); } else { ast_cli(fd,"Peer %s not found.\n", argv[3]); ast_cli(fd,"\n"); @@ -2330,20 +2404,23 @@ static int iax2_show_peer(int fd, int argc, char *argv[]) static char *complete_iax2_show_peer(const char *line, const char *word, int pos, int state) { int which = 0; - struct iax2_peer *p = NULL; + struct iax2_peer *peer; char *res = NULL; int wordlen = strlen(word); + ao2_iterator i; /* 0 - iax2; 1 - show; 2 - peer; 3 - <peername> */ - if (pos == 3) { - AST_LIST_LOCK(&peers); - AST_LIST_TRAVERSE(&peers, p, entry) { - if (!strncasecmp(p->name, word, wordlen) && ++which > state) { - res = ast_strdup(p->name); - break; - } + if (pos != 3) + return NULL; + + i = ao2_iterator_init(peers, 0); + while ((peer = ao2_iterator_next(&i))) { + if (!strncasecmp(peer->name, word, wordlen) && ++which > state) { + res = ast_strdup(peer->name); + peer_unref(peer); + break; } - AST_LIST_UNLOCK(&peers); + peer_unref(peer); } return res; @@ -2788,8 +2865,7 @@ static struct iax2_peer *realtime_peer(const char *peername, struct sockaddr_in if (strcasecmp(tmp->value, "friend") && strcasecmp(tmp->value, "peer")) { /* Whoops, we weren't supposed to exist! */ - destroy_peer(peer); - peer = NULL; + peer = peer_unref(peer); break; } } else if (!strcasecmp(tmp->name, "regseconds")) { @@ -2815,9 +2891,7 @@ static struct iax2_peer *realtime_peer(const char *peername, struct sockaddr_in peer->expire = ast_sched_replace(peer->expire, sched, (global_rtautoclear) * 1000, expire_registry, (void *) peer->name); } - AST_LIST_LOCK(&peers); - AST_LIST_INSERT_HEAD(&peers, peer, entry); - AST_LIST_UNLOCK(&peers); + ao2_link(peers, peer_ref(peer)); if (ast_test_flag(peer, IAX_DYNAMIC)) reg_source_db(peer); } else { @@ -2872,9 +2946,7 @@ static struct iax2_user *realtime_user(const char *username) if (ast_test_flag((&globalflags), IAX_RTCACHEFRIENDS)) { ast_set_flag(user, IAX_RTCACHEFRIENDS); - AST_LIST_LOCK(&users); - AST_LIST_INSERT_HEAD(&users, user, entry); - AST_LIST_UNLOCK(&users); + ao2_link(users, user_ref(user)); } else { ast_set_flag(user, IAX_TEMPONLY); } @@ -2916,6 +2988,7 @@ struct create_addr_info { static int create_addr(const char *peername, struct sockaddr_in *sin, struct create_addr_info *cai) { struct iax2_peer *peer; + int res = -1; ast_clear_flag(cai, IAX_SENDANI | IAX_TRUNK); cai->sockfd = defaultsockfd; @@ -2937,18 +3010,12 @@ static int create_addr(const char *peername, struct sockaddr_in *sin, struct cre cai->found = 1; /* if the peer has no address (current or default), return failure */ - if (!(peer->addr.sin_addr.s_addr || peer->defaddr.sin_addr.s_addr)) { - if (ast_test_flag(peer, IAX_TEMPONLY)) - destroy_peer(peer); - return -1; - } + if (!(peer->addr.sin_addr.s_addr || peer->defaddr.sin_addr.s_addr)) + goto return_unref; /* if the peer is being monitored and is currently unreachable, return failure */ - if (peer->maxms && ((peer->lastms > peer->maxms) || (peer->lastms < 0))) { - if (ast_test_flag(peer, IAX_TEMPONLY)) - destroy_peer(peer); - return -1; - } + if (peer->maxms && ((peer->lastms > peer->maxms) || (peer->lastms < 0))) + goto return_unref; ast_copy_flags(cai, peer, IAX_SENDANI | IAX_TRUNK | IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF); cai->maxtime = peer->maxms; @@ -2976,9 +3043,7 @@ static int create_addr(const char *peername, struct sockaddr_in *sin, struct cre *key++ = '\0'; if (!key || ast_db_get(family, key, cai->secret, sizeof(cai->secret))) { ast_log(LOG_WARNING, "Unable to retrieve database password for family/key '%s'!\n", peer->dbsecret); - if (ast_test_flag(peer, IAX_TEMPONLY)) - destroy_peer(peer); - return -1; + goto return_unref; } } @@ -2990,10 +3055,12 @@ static int create_addr(const char *peername, struct sockaddr_in *sin, struct cre sin->sin_port = peer->defaddr.sin_port; } - if (ast_test_flag(peer, IAX_TEMPONLY)) - destroy_peer(peer); + res = 0; - return 0; +return_unref: + peer_unref(peer); + + return res; } static void __auto_congest(void *nothing) @@ -3604,18 +3671,20 @@ static int iax2_transfer(struct ast_channel *c, const char *dest) static int iax2_getpeertrunk(struct sockaddr_in sin) { - struct iax2_peer *peer = NULL; + struct iax2_peer *peer; int res = 0; + ao2_iterator i; - AST_LIST_LOCK(&peers); - AST_LIST_TRAVERSE(&peers, peer, entry) { + i = ao2_iterator_init(peers, 0); + while ((peer = ao2_iterator_next(&i))) { if ((peer->addr.sin_addr.s_addr == sin.sin_addr.s_addr) && (peer->addr.sin_port == sin.sin_port)) { res = ast_test_flag(peer, IAX_TRUNK); + peer_unref(peer); break; } + peer_unref(peer); } - AST_LIST_UNLOCK(&peers); return res; } @@ -4374,6 +4443,7 @@ static int iax2_show_users(int fd, int argc, char *argv[]) struct iax2_user *user = NULL; char auth[90]; char *pstr = ""; + ao2_iterator i; switch (argc) { case 5: @@ -4390,8 +4460,9 @@ static int iax2_show_users(int fd, int argc, char *argv[]) } ast_cli(fd, FORMAT, "Username", "Secret", "Authen", "Def.Context", "A/C","Codec Pref"); - AST_LIST_LOCK(&users); - AST_LIST_TRAVERSE(&users, user, entry) { + i = ao2_iterator_init(users, 0); + for (user = ao2_iterator_next(&i); user; + user_unref(user), user = ao2_iterator_next(&i)) { if (havepattern && regexec(®exbuf, user->name, 0, NULL, 0)) continue; @@ -4412,9 +4483,7 @@ static int iax2_show_users(int fd, int argc, char *argv[]) ast_cli(fd, FORMAT2, user->name, auth, user->authmethods, user->contexts ? user->contexts->context : context, user->ha ? "Yes" : "No", pstr); - } - AST_LIST_UNLOCK(&users); if (havepattern) regfree(®exbuf); @@ -4432,6 +4501,7 @@ static int __iax2_show_peers(int manager, int fd, struct mansession *s, int argc int online_peers = 0; int offline_peers = 0; int unmonitored_peers = 0; + ao2_iterator i; #define FORMAT2 "%-15.15s %-15.15s %s %-15.15s %-8s %s %-10s%s" #define FORMAT "%-15.15s %-15.15s %s %-15.15s %-5d%s %s %-10s%s" @@ -4480,8 +4550,9 @@ static int __iax2_show_peers(int manager, int fd, struct mansession *s, int argc else ast_cli(fd, FORMAT2, "Name/Username", "Host", " ", "Mask", "Port", " ", "Status", term); - AST_LIST_LOCK(&peers); - AST_LIST_TRAVERSE(&peers, peer, entry) { + i = ao2_iterator_init(peers, 0); + for (peer = ao2_iterator_next(&i); peer; + peer_unref(peer), peer = ao2_iterator_next(&i)) { char nm[20]; char status[20]; char srch[2000]; @@ -4530,7 +4601,6 @@ static int __iax2_show_peers(int manager, int fd, struct mansession *s, int argc peer->encmethods ? "(E)" : " ", status, term); total_peers++; } - AST_LIST_UNLOCK(&peers); if (s) astman_append(s,"%d iax2 peers [%d online, %d offline, %d unmonitored]%s", total_peers, online_peers, offline_peers, unmonitored_peers, term); @@ -4633,15 +4703,16 @@ static char *complete_iax2_unregister(const char *line, const char *word, int po /* 0 - iax2; 1 - unregister; 2 - <peername> */ if (pos == 2) { - AST_LIST_LOCK(&peers); - AST_LIST_TRAVERSE(&peers, p, entry) { + ao2_iterator i = ao2_iterator_init(peers, 0); + while ((p = ao2_iterator_next(&i))) { if (!strncasecmp(p->name, word, wordlen) && ++which > state && p->expire > 0) { res = ast_strdup(p->name); + peer_unref(p); break; } + peer_unref(p); } - AST_LIST_UNLOCK(&peers); } return res; @@ -5036,6 +5107,7 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies int bestscore = 0; int gotcapability = 0; struct ast_variable *v = NULL, *tmpvar = NULL; + ao2_iterator i; if (!iaxs[callno]) return res; @@ -5090,8 +5162,8 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies return res; } /* Search the userlist for a compatible entry, and fill in the rest */ - AST_LIST_LOCK(&users); - AST_LIST_TRAVERSE(&users, user, entry) { + i = ao2_iterator_init(users, 0); + while ((user = ao2_iterator_next(&i))) { if ((ast_strlen_zero(iaxs[callno]->username) || /* No username specified */ !strcmp(iaxs[callno]->username, user->name)) /* Or this username specified */ && ast_apply_ha(user->ha, sin) /* Access is permitted from this IP */ @@ -5099,6 +5171,8 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies apply_context(user->contexts, iaxs[callno]->context))) { /* Context is permitted */ if (!ast_strlen_zero(iaxs[callno]->username)) { /* Exact match, stop right now. */ + if (best) + user_unref(best); best = user; break; } else if (ast_strlen_zero(user->secret) && ast_strlen_zero(user->inkeys)) { @@ -5107,13 +5181,19 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies /* There was host authentication and we passed, bonus! */ if (bestscore < 4) { bestscore = 4; + if (best) + user_unref(best); best = user; + continue; } } else { /* No host access, but no secret, either, not bad */ if (bestscore < 3) { bestscore = 3; + if (best) + user_unref(best); best = user; + continue; } } } else { @@ -5121,26 +5201,31 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies /* Authentication, but host access too, eh, it's something.. */ if (bestscore < 2) { bestscore = 2; + if (best) + user_unref(best); best = user; + continue; } } else { /* Authentication and no host access... This is our baseline */ if (bestscore < 1) { bestscore = 1; + if (best) + user_unref(best); best = user; + continue; } } } } + user_unref(user); } - AST_LIST_UNLOCK(&users); user = best; if (!user && !ast_strlen_zero(iaxs[callno]->username)) { user = realtime_user(iaxs[callno]->username); if (user && !ast_strlen_zero(iaxs[callno]->context) && /* No context specified */ !apply_context(user->contexts, iaxs[callno]->context)) { /* Context is permitted */ - destroy_user(user); - user = NULL; + user = user_unref(user); } } if (user) { @@ -5219,9 +5304,8 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies ast_string_field_set(iaxs[callno], secret, buf); } else ast_string_field_set(iaxs[callno], secret, user->secret); - if (ast_test_flag(user, IAX_TEMPONLY)) - destroy_user(user); res = 0; + user = user_unref(user); } ast_set2_flag(iaxs[callno], iax2_getpeertrunk(*sin), IAX_TRUNK); return res; @@ -5267,7 +5351,6 @@ static void merge_encryption(struct chan_iax2_pvt *p, unsigned int enc) */ static int authenticate_request(int call_num) { - struct iax2_user *user = NULL; struct iax_ie_data ied; int res = -1, authreq_restrict = 0; char challenge[10]; @@ -5277,17 +5360,18 @@ static int authenticate_request(int call_num) /* If an AUTHREQ restriction is in place, make sure we can send an AUTHREQ back */ if (ast_test_flag(p, IAX_MAXAUTHREQ)) { - AST_LIST_LOCK(&users); - AST_LIST_TRAVERSE(&users, user, entry) { - if (!strcmp(user->name, p->username)) { - if (user->curauthreq == user->maxauthreq) - authreq_restrict = 1; - else - user->curauthreq++; - break; - } + struct iax2_user *user, tmp_user = { + .name = p->username, + }; + + user = ao2_find(users, &tmp_user, OBJ_POINTER); + if (user) { + if (user->curauthreq == user->maxauthreq) + authreq_restrict = 1; + else + user->curauthreq++; + user = user_unref(user); } - AST_LIST_UNLOCK(&users); } /* If the AUTHREQ limit test failed, send back an error */ @@ -5326,21 +5410,19 @@ static int authenticate_verify(struct chan_iax2_pvt *p, struct iax_ies *ies) char rsasecret[256] = ""; int res = -1; int x; - struct iax2_user *user = NULL; + struct iax2_user *user, tmp_user = { + .name = p->username, + }; - AST_LIST_LOCK(&users); - AST_LIST_TRAVERSE(&users, user, entry) { - if (!strcmp(user->name, p->username)) - break; - } + user = ao2_find(users, &tmp_user, OBJ_POINTER); if (user) { if (ast_test_flag(p, IAX_MAXAUTHREQ)) { - user->curauthreq--; + ast_atomic_fetchadd_int(&user->curauthreq, -1); ast_clear_flag(p, IAX_MAXAUTHREQ); } ast_string_field_set(p, host, user->name); + user = user_unref(user); } - AST_LIST_UNLOCK(&users); if (!ast_test_flag(&p->state, IAX_STATE_AUTHENTICATED)) return res; @@ -5402,11 +5484,12 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies * char md5secret[256] = ""; char rsasecret[256] = ""; char secret[256] = ""; - struct iax2_peer *p; + struct iax2_peer *p = NULL; struct ast_key *key; char *keyn; int x; int expire = 0; + int res = -1; ast_clear_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED | IAX_STATE_UNCHANGED); /* iaxs[callno]->peer[0] = '\0'; not necc. any more-- stringfield is pre-inited to null string */ @@ -5433,23 +5516,19 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies * if (!p || !iaxs[callno]) { if (authdebug && !p) ast_log(LOG_NOTICE, "No registration for peer '%s' (from %s)\n", peer, ast_inet_ntoa(sin->sin_addr)); - return -1; + goto return_unref; } if (!ast_test_flag(p, IAX_DYNAMIC)) { if (authdebug) ast_log(LOG_NOTICE, "Peer '%s' is not dynamic (from %s)\n", peer, ast_inet_ntoa(sin->sin_addr)); - if (ast_test_flag(p, IAX_TEMPONLY)) - destroy_peer(p); - return -1; + goto return_unref; } if (!ast_apply_ha(p->ha, sin)) { if (authdebug) ast_log(LOG_NOTICE, "Host %s denied access to register peer '%s'\n", ast_inet_ntoa(sin->sin_addr), p->name); - if (ast_test_flag(p, IAX_TEMPONLY)) - destroy_peer(p); - return -1; + goto return_unref; } if (!inaddrcmp(&p->addr, sin)) ast_set_flag(&iaxs[callno]->state, IAX_STATE_UNCHANGED); @@ -5475,16 +5554,12 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies * if (!keyn) { if (authdebug) ast_log(LOG_NOTICE, "Host %s failed RSA authentication with inkeys '%s'\n", peer, p->inkeys); - if (ast_test_flag(p, IAX_TEMPONLY)) - destroy_peer(p); - return -1; + goto return_unref; } } else { if (authdebug) ast_log(LOG_NOTICE, "Host '%s' trying to do RSA authentication, but we have no inkeys\n", peer); - if (ast_test_flag(p, IAX_TEMPONLY)) - destroy_peer(p); - return -1; + goto return_unref; } } else if (!ast_strlen_zero(md5secret) && (p->authmethods & IAX_AUTH_MD5) && !ast_strlen_zero(iaxs[callno]->challenge)) { struct MD5Context md5; @@ -5508,26 +5583,20 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies * } else { if (authdebug) ast_log(LOG_NOTICE, "Host %s failed MD5 authentication for '%s' (%s != %s)\n", ast_inet_ntoa(sin->sin_addr), p->name, requeststr, md5secret); - if (ast_test_flag(p, IAX_TEMPONLY)) - destroy_peer(p); - return -1; + goto return_unref; } } else if (!ast_strlen_zero(secret) && (p->authmethods & IAX_AUTH_PLAINTEXT)) { /* They've provided a plain text password and we support that */ if (strcmp(secret, p->secret)) { if (authdebug) ast_log(LOG_NOTICE, "Host %s did not provide proper plaintext password for '%s'\n", ast_inet_ntoa(sin->sin_addr), p->name); - if (ast_test_flag(p, IAX_TEMPONLY)) - destroy_peer(p); - return -1; + goto return_unref; } else ast_set_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED); } else if (!ast_strlen_zero(md5secret) || !ast_strlen_zero(secret)) { if (authdebug) ast_log(LOG_NOTICE, "Inappropriate authentication received\n"); - if (ast_test_flag(p, IAX_TEMPONLY)) - destroy_peer(p); - return -1; + goto return_unref; } ast_string_field_set(iaxs[callno], peer, peer); /* Choose lowest expiry number */ @@ -5536,10 +5605,13 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies * ast_device_state_changed("IAX2/%s", p->name); /* Activate notification */ - if (ast_test_flag(p, IAX_TEMPONLY)) - destroy_peer(p); + res = 0; - return 0; +return_unref: + if (p) + peer_unref(p); + + return res; } static int authenticate(const char *challenge, const char *secret, const char *keyn, int authmethods, struct iax_ie_data *ied, struct sockaddr_in *sin, ast_aes_encrypt_key *ecx, ast_aes_decrypt_key *dcx) @@ -5626,8 +5698,8 @@ static int authenticate_reply(struct chan_iax2_pvt *p, struct sockaddr_in *sin, /* Normal password authentication */ res = authenticate(p->challenge, override, okey, authmethods, &ied, sin, &p->ecx, &p->dcx); } else { - AST_LIST_LOCK(&peers); - AST_LIST_TRAVERSE(&peers, peer, entry) { + ao2_iterator i = ao2_iterator_init(peers, 0); + while ((peer = ao2_iterator_next(&i))) { if ((ast_strlen_zero(p->peer) || !strcmp(p->peer, peer->name)) /* No peer specified at our end, or this is the peer */ && (ast_strlen_zero(peer->username) || (!strcmp(peer->username, p->username))) @@ -5636,11 +5708,11 @@ static int authenticate_reply(struct chan_iax2_pvt *p, struct sockaddr_in *sin, /* No specified host, or this is our host */ ) { res = authenticate(p->challenge, peer->secret, peer->outkey, authmethods, &ied, sin, &p->ecx, &p->dcx); + peer_unref(peer); if (!res) break; } } - AST_LIST_UNLOCK(&peers); if (!peer) { /* We checked our list and didn't find one. It's unlikely, but possible, that we're trying to authenticate *to* a realtime peer */ @@ -5649,13 +5721,11 @@ static int authenticate_reply(struct chan_iax2_pvt *p, struct sockaddr_in *sin, if ((peer = realtime_peer(peer_name, NULL))) { ast_mutex_lock(&iaxsl[callno]); if (!(p = iaxs[callno])) { - if (ast_test_flag(peer, IAX_TEMPONLY)) - destroy_peer(peer); + peer_unref(peer); return -1; } res = authenticate(p->challenge, peer->secret,peer->outkey, authmethods, &ied, sin, &p->ecx, &p->dcx); - if (ast_test_flag(peer, IAX_TEMPONLY)) - destroy_peer(peer); + peer_unref(peer); } if (!peer) { ast_mutex_lock(&iaxsl[callno]); @@ -5997,42 +6067,36 @@ static void prune_peers(void); static void __expire_registry(void *data) { char *name = data; - struct iax2_peer *p = NULL; - - /* Go through and grab this peer... and if it needs to be removed... then do it */ - AST_LIST_LOCK(&peers); - AST_LIST_TRAVERSE_SAFE_BEGIN(&peers, p, entry) { - if (!strcasecmp(p->name, name)) { - p->expire = -1; - break; - } - } - AST_LIST_TRAVERSE_SAFE_END - AST_LIST_UNLOCK(&peers); + struct iax2_peer *peer = NULL; + struct iax2_peer tmp_peer = { + .name = name, + }; - /* Peer is already gone for whatever reason */ - if (!p) + peer = ao2_find(peers, &tmp_peer, OBJ_POINTER); + if (!peer) return; - ast_debug(1, "Expiring registration for peer '%s'\n", p->name); - if (ast_test_flag((&globalflags), IAX_RTUPDATE) && (ast_test_flag(p, IAX_TEMPONLY|IAX_RTCACHEFRIENDS))) - realtime_update_peer(p->name, &p->addr, 0); - manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "Peer: IAX2/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", p->name); + peer->expire = -1; + + ast_debug(1, "Expiring registration for peer '%s'\n", peer->name); + if (ast_test_flag((&globalflags), IAX_RTUPDATE) && (ast_test_flag(peer, IAX_TEMPONLY|IAX_RTCACHEFRIENDS))) + realtime_update_peer(peer->name, &peer->addr, 0); + manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "Peer: IAX2/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", peer->name); /* Reset the address */ - memset(&p->addr, 0, sizeof(p->addr)); + memset(&peer->addr, 0, sizeof(peer->addr)); /* Reset expiry value */ - p->expiry = min_reg_expire; - if (!ast_test_flag(p, IAX_TEMPONLY)) - ast_db_del("IAX/Registry", p->name); - register_peer_exten(p, 0); - ast_device_state_changed("IAX2/%s", p->name); /* Activate notification */ + peer->expiry = min_reg_expire; + if (!ast_test_flag(peer, IAX_TEMPONLY)) + ast_db_del("IAX/Registry", peer->name); + register_peer_exten(peer, 0); + ast_device_state_changed("IAX2/%s", peer->name); /* Activate notification */ if (iax2_regfunk) - iax2_regfunk(p->name, 0); + iax2_regfunk(peer->name, 0); - if (ast_test_flag(p, IAX_RTAUTOCLEAR)) { - ast_set_flag(p, IAX_DELME); - prune_peers(); - } + if (ast_test_flag(peer, IAX_RTAUTOCLEAR)) + ao2_unlink(peers, peer); + + peer_unref(peer); } static int expire_registry(void *data) @@ -6097,6 +6161,7 @@ static int update_registry(struct sockaddr_in *sin, int callno, char *devtype, i char data[80]; int version; const char *peer_name; + int res = -1; memset(&ied, 0, sizeof(ied)); @@ -6110,11 +6175,8 @@ static int update_registry(struct sockaddr_in *sin, int callno, char *devtype, i return -1; } ast_mutex_lock(&iaxsl[callno]); - if (!iaxs[callno]) { - if (ast_test_flag(p, IAX_TEMPONLY)) - destroy_peer(p); - return -1; - } + if (!iaxs[callno]) + goto return_unref; if (ast_test_flag((&globalflags), IAX_RTUPDATE) && (ast_test_flag(p, IAX_TEMPONLY|IAX_RTCACHEFRIENDS))) { if (sin->sin_addr.s_addr) { @@ -6153,9 +6215,8 @@ static int update_registry(struct sockaddr_in *sin, int callno, char *devtype, i /* Make sure our call still exists, an INVAL at the right point may make it go away */ if (!iaxs[callno]) { - if (ast_test_flag(p, IAX_TEMPONLY)) - destroy_peer(p); - return 0; + res = 0; + goto return_unref; } /* Store socket fd */ @@ -6223,9 +6284,13 @@ static int update_registry(struct sockaddr_in *sin, int callno, char *devtype, i version = iax_check_version(devtype); if (version) iax_ie_append_short(&ied, IAX_IE_FIRMWAREVER, version); - if (ast_test_flag(p, IAX_TEMPONLY)) - destroy_peer(p); - return send_command_final(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGACK, 0, ied.buf, ied.pos, -1); + + res = 0; + +return_unref: + peer_unref(p); + + return res ? res : send_command_final(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGACK, 0, ied.buf, ied.pos, -1); } static int registry_authrequest(int callno) @@ -6234,6 +6299,7 @@ static int registry_authrequest(int callno) struct iax2_peer *p; char challenge[10]; const char *peer_name; + int res = -1; peer_name = ast_strdupa(iaxs[callno]->peer); @@ -6241,28 +6307,29 @@ static int registry_authrequest(int callno) ast_mutex_unlock(&iaxsl[callno]); p = find_peer(peer_name, 1); ast_mutex_lock(&iaxsl[callno]); - if (!iaxs[callno]) { - if (p && ast_test_flag(p, IAX_TEMPONLY)) - destroy_peer(p); - return -1; + if (!iaxs[callno]) + goto return_unref; + if (!p) { + ast_log(LOG_WARNING, "No such peer '%s'\n", peer_name); + goto return_unref; } - if (p) { - memset(&ied, 0, sizeof(ied)); - iax_ie_append_short(&ied, IAX_IE_AUTHMETHODS, p->authmethods); - if (p->authmethods & (IAX_AUTH_RSA | IAX_AUTH_MD5)) { - /* Build the challenge */ - snprintf(challenge, sizeof(challenge), "%d", (int)ast_random()); - ast_string_field_set(iaxs[callno], challenge, challenge); - /* snprintf(iaxs[callno]->challenge, sizeof(iaxs[callno]->challenge), "%d", (int)ast_random()); */ - iax_ie_append_str(&ied, IAX_IE_CHALLENGE, iaxs[callno]->challenge); - } - iax_ie_append_str(&ied, IAX_IE_USERNAME, peer_name); - if (ast_test_flag(p, IAX_TEMPONLY)) - destroy_peer(p); - return send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGAUTH, 0, ied.buf, ied.pos, -1);; - } - ast_log(LOG_WARNING, "No such peer '%s'\n", peer_name); - return 0; + + memset(&ied, 0, sizeof(ied)); + iax_ie_append_short(&ied, IAX_IE_AUTHMETHODS, p->authmethods); + if (p->authmethods & (IAX_AUTH_RSA | IAX_AUTH_MD5)) { + /* Build the challenge */ + snprintf(challenge, sizeof(challenge), "%d", (int)ast_random()); + ast_string_field_set(iaxs[callno], challenge, challenge); + iax_ie_append_str(&ied, IAX_IE_CHALLENGE, iaxs[callno]->challenge); + } + iax_ie_append_str(&ied, IAX_IE_USERNAME, peer_name); + + res = 0; + +return_unref: + peer_unref(p); + + return res ? res : send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGAUTH, 0, ied.buf, ied.pos, -1);; } static int registry_rerequest(struct iax_ies *ies, int callno, struct sockaddr_in *sin) @@ -6993,7 +7060,7 @@ static int socket_process_meta(int packet_len, struct ast_iax2_meta_hdr *meta, s /* Stop if we don't have enough data */ if (len > packet_len) break; - fr->callno = find_callno(callno & ~IAX_FLAG_FULL, 0, sin, NEW_PREVENT, 1, sockfd); + fr->callno = find_callno(callno & ~IAX_FLAG_FULL, 0, sin, NEW_PREVENT, sockfd); if (!fr->callno) continue; @@ -7178,7 +7245,7 @@ static int socket_process(struct iax2_thread *thread) } /* This is a video frame, get call number */ - fr->callno = find_callno(ntohs(vh->callno) & ~0x8000, dcallno, &sin, new, 1, fd); + fr->callno = find_callno(ntohs(vh->callno) & ~0x8000, dcallno, &sin, new, fd); minivid = 1; } else if ((meta->zeros == 0) && !(ntohs(meta->metacmd) & 0x8000)) return socket_process_meta(res, meta, &sin, fd, fr); @@ -7213,7 +7280,7 @@ static int socket_process(struct iax2_thread *thread) } if (!fr->callno) - fr->callno = find_callno(ntohs(mh->callno) & ~IAX_FLAG_FULL, dcallno, &sin, new, 1, fd); + fr->callno = find_callno(ntohs(mh->callno) & ~IAX_FLAG_FULL, dcallno, &sin, new, fd); if (fr->callno > 0) ast_mutex_lock(&iaxsl[fr->callno]); @@ -8756,7 +8823,7 @@ static int iax2_do_register(struct iax2_registry *reg) if (!reg->callno) { ast_debug(1, "Allocate call number\n"); - reg->callno = find_callno(0, 0, ®->addr, NEW_FORCE, 1, defaultsockfd); + reg->callno = find_callno(0, 0, ®->addr, NEW_FORCE, defaultsockfd); if (reg->callno < 1) { ast_log(LOG_WARNING, "Unable to create call for registration\n"); return -1; @@ -8813,7 +8880,7 @@ static int iax2_provision(struct sockaddr_in *end, int sockfd, char *dest, const memset(&ied, 0, sizeof(ied)); iax_ie_append_raw(&ied, IAX_IE_PROVISIONING, provdata.buf, provdata.pos); - callno = find_callno(0, 0, &sin, NEW_FORCE, 1, cai.sockfd); + callno = find_callno(0, 0, &sin, NEW_FORCE, cai.sockfd); if (!callno) return -1; @@ -8923,6 +8990,15 @@ static int iax2_poke_noanswer(void *data) return 0; } +static int iax2_poke_peer_cb(void *obj, void *arg, int flags) +{ + struct iax2_peer *peer = obj; + + iax2_poke_peer(peer, 0); + + return 0; +} + static int iax2_poke_peer(struct iax2_peer *peer, int heldcall) { if (!peer->maxms || !peer->addr.sin_addr.s_addr) { @@ -8942,7 +9018,7 @@ static int iax2_poke_peer(struct iax2_peer *peer, int heldcall) } if (heldcall) ast_mutex_unlock(&iaxsl[heldcall]); - peer->callno = find_callno(0, 0, &peer->addr, NEW_FORCE, 0, peer->sockfd); + peer->callno = find_callno(0, 0, &peer->addr, NEW_FORCE, peer->sockfd); if (heldcall) ast_mutex_lock(&iaxsl[heldcall]); if (peer->callno < 1) { @@ -9015,7 +9091,7 @@ static struct ast_channel *iax2_request(const char *type, int format, void *data if (pds.port) sin.sin_port = htons(atoi(pds.port)); - callno = find_callno(0, 0, &sin, NEW_FORCE, 1, cai.sockfd); + callno = find_callno(0, 0, &sin, NEW_FORCE, cai.sockfd); if (callno < 1) { ast_log(LOG_WARNING, "Unable to create call\n"); *cause = AST_CAUSE_CONGESTION; @@ -9308,7 +9384,31 @@ static int peer_set_srcaddr(struct iax2_peer *peer, const char *srcaddr) } } - +static void peer_destructor(void *obj) +{ + struct iax2_peer *peer = obj; + + ast_free_ha(peer->ha); + + /* Delete it, it needs to disappear */ + if (peer->expire > -1) + ast_sched_del(sched, peer->expire); + if (peer->pokeexpire > -1) + ast_sched_del(sched, peer->pokeexpire); + if (peer->callno > 0) { + ast_mutex_lock(&iaxsl[peer->callno]); + iax2_destroy(peer->callno); + ast_mutex_unlock(&iaxsl[peer->callno]); + } + + register_peer_exten(peer, 0); + + if (peer->dnsmgr) + ast_dnsmgr_release(peer->dnsmgr); + + ast_string_field_free_pools(peer); +} + /*! \brief Create peer structure based on configuration */ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly) { @@ -9317,38 +9417,30 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st int maskfound = 0; int found = 0; int firstpass = 1; + struct iax2_peer tmp_peer = { + .name = name, + }; - AST_LIST_LOCK(&peers); if (!temponly) { - AST_LIST_TRAVERSE(&peers, peer, entry) { - if (!strcmp(peer->name, name)) { - if (!ast_test_flag(peer, IAX_DELME)) - firstpass = 0; - break; - } - } - } else - peer = NULL; + peer = ao2_find(peers, &tmp_peer, OBJ_POINTER); + if (peer && !ast_test_flag(peer, IAX_DELME)) + firstpass = 0; + } + if (peer) { found++; if (firstpass) { oldha = peer->ha; peer->ha = NULL; } - AST_LIST_REMOVE(&peers, peer, entry); - AST_LIST_UNLOCK(&peers); - } else { - AST_LIST_UNLOCK(&peers); - if ((peer = ast_calloc(1, sizeof(*peer)))) { - peer->expire = -1; - peer->pokeexpire = -1; - peer->sockfd = defaultsockfd; - if (ast_string_field_init(peer, 32)) { - ast_free(peer); - peer = NULL; - } - } + } else if ((peer = ao2_alloc(sizeof(*peer), peer_destructor))) { + peer->expire = -1; + peer->pokeexpire = -1; + peer->sockfd = defaultsockfd; + if (ast_string_field_init(peer, 32)) + peer = peer_unref(peer); } + if (peer) { if (firstpass) { ast_copy_flags(peer, &globalflags, IAX_USEJITTERBUF | IAX_FORCEJITTERBUF); @@ -9430,8 +9522,7 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st ast_clear_flag(peer, IAX_DYNAMIC); if (ast_dnsmgr_lookup(v->value, &peer->addr.sin_addr, &peer->dnsmgr)) { ast_string_field_free_pools(peer); - ast_free(peer); - return NULL; + return peer_unref(peer); } if (!peer->addr.sin_port) peer->addr.sin_port = htons(IAX_DEFAULT_PORTNO); @@ -9441,8 +9532,7 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st } else if (!strcasecmp(v->name, "defaultip")) { if (ast_get_ip(&peer->defaddr, v->value)) { ast_string_field_free_pools(peer); - ast_free(peer); - return NULL; + return peer_unref(peer); } } else if (!strcasecmp(v->name, "sourceaddress")) { peer_set_srcaddr(peer, v->value); @@ -9562,6 +9652,19 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st return peer; } +static void user_destructor(void *obj) +{ + struct iax2_user *user = obj; + + ast_free_ha(user->ha); + free_context(user->contexts); + if(user->vars) { + ast_variables_destroy(user->vars); + user->vars = NULL; + } + ast_string_field_free_pools(user); +} + /*! \brief Create in-memory user structure from configuration */ static struct iax2_user *build_user(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly) { @@ -9574,18 +9677,15 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st int oldcurauthreq = 0; char *varname = NULL, *varval = NULL; struct ast_variable *tmpvar = NULL; - - AST_LIST_LOCK(&users); + struct iax2_user tmp_user = { + .name = name, + }; + if (!temponly) { - AST_LIST_TRAVERSE(&users, user, entry) { - if (!strcmp(user->name, name)) { - if (!ast_test_flag(user, IAX_DELME)) - firstpass = 0; - break; - } - } - } else - user = NULL; + user = ao2_find(users, &tmp_user, OBJ_POINTER); + if (user && !ast_test_flag(user, IAX_DELME)) + firstpass = 0; + } if (user) { if (firstpass) { @@ -9596,12 +9696,9 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st user->contexts = NULL; } /* Already in the list, remove it and it will be added back (or FREE'd) */ - AST_LIST_REMOVE(&users, user, entry); - AST_LIST_UNLOCK(&users); + ao2_unlink(users, user); } else { - AST_LIST_UNLOCK(&users); - /* This is going to memset'd to 0 in the next block */ - user = ast_calloc(1, sizeof(*user)); + user = ao2_alloc(sizeof(*user), user_destructor); } if (user) { @@ -9609,8 +9706,8 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st ast_string_field_free_pools(user); memset(user, 0, sizeof(struct iax2_user)); if (ast_string_field_init(user, 32)) { - ast_free(user); - user = NULL; + user = user_unref(user); + goto cleanup; } user->maxauthreq = maxauthreq; user->curauthreq = oldcurauthreq; @@ -9770,6 +9867,7 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st } ast_clear_flag(user, IAX_DELME); } +cleanup: if (oldha) ast_free_ha(oldha); if (oldcon) @@ -9777,16 +9875,29 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st return user; } +static int peer_delme_cb(void *obj, void *arg, int flags) +{ + struct iax2_peer *peer = obj; + + ast_set_flag(peer, IAX_DELME); + + return 0; +} + +static int user_delme_cb(void *obj, void *arg, int flags) +{ + struct iax2_user *user = obj; + + ast_set_flag(user, IAX_DELME); + + return 0; +} + static void delete_users(void) { - struct iax2_user *user; - struct iax2_peer *peer; struct iax2_registry *reg; - AST_LIST_LOCK(&users); - AST_LIST_TRAVERSE(&users, user, entry) - ast_set_flag(user, IAX_DELME); - AST_LIST_UNLOCK(&users); + ao2_callback(users, 0, user_delme_cb, NULL); AST_LIST_LOCK(®istrations); while ((reg = AST_LIST_REMOVE_HEAD(®istrations, entry))) { @@ -9806,81 +9917,34 @@ static void delete_users(void) } AST_LIST_UNLOCK(®istrations); - AST_LIST_LOCK(&peers); - AST_LIST_TRAVERSE(&peers, peer, entry) - ast_set_flag(peer, IAX_DELME); - AST_LIST_UNLOCK(&peers); -} - -static void destroy_user(struct iax2_user *user) -{ - ast_free_ha(user->ha); - free_context(user->contexts); - if(user->vars) { - ast_variables_destroy(user->vars); - user->vars = NULL; - } - ast_string_field_free_pools(user); - ast_free(user); + ao2_callback(peers, 0, peer_delme_cb, NULL); } static void prune_users(void) { - struct iax2_user *user = NULL; + struct iax2_user *user; + ao2_iterator i; - AST_LIST_LOCK(&users); - AST_LIST_TRAVERSE_SAFE_BEGIN(&users, user, entry) { - if (ast_test_flag(user, IAX_DELME)) { - destroy_user(user); - AST_LIST_REMOVE_CURRENT(&users, entry); - } + i = ao2_iterator_init(users, 0); + while ((user = ao2_iterator_next(&i))) { + if (ast_test_flag(user, IAX_DELME)) + ao2_unlink(users, user); + user_unref(user); } - AST_LIST_TRAVERSE_SAFE_END - AST_LIST_UNLOCK(&users); - } -static void destroy_peer(struct iax2_peer *peer) +/* Prune peers who still are supposed to be deleted */ +static void prune_peers(void) { - ast_free_ha(peer->ha); - - /* Delete it, it needs to disappear */ - if (peer->expire > -1) - ast_sched_del(sched, peer->expire); - if (peer->pokeexpire > -1) - ast_sched_del(sched, peer->pokeexpire); - if (peer->callno > 0) { - ast_mutex_lock(&iaxsl[peer->callno]); - iax2_destroy(peer->callno); - ast_mutex_unlock(&iaxsl[peer->callno]); - } - - register_peer_exten(peer, 0); - - if (peer->dnsmgr) - ast_dnsmgr_release(peer->dnsmgr); - - if (peer->mwi_event_sub) - ast_event_unsubscribe(peer->mwi_event_sub); - - ast_string_field_free_pools(peer); - - ast_free(peer); -} - -static void prune_peers(void){ - /* Prune peers who still are supposed to be deleted */ - struct iax2_peer *peer = NULL; + struct iax2_peer *peer; + ao2_iterator i; - AST_LIST_LOCK(&peers); - AST_LIST_TRAVERSE_SAFE_BEGIN(&peers, peer, entry) { - if (ast_test_flag(peer, IAX_DELME)) { - destroy_peer(peer); - AST_LIST_REMOVE_CURRENT(&peers, entry); - } + i = ao2_iterator_init(peers, 0); + while ((peer = ao2_iterator_next(&i))) { + if (ast_test_flag(peer, IAX_DELME)) + ao2_unlink(peers, peer); + peer_unref(peer); } - AST_LIST_TRAVERSE_SAFE_END - AST_LIST_UNLOCK(&peers); } static void set_timing(void) @@ -10236,17 +10300,15 @@ static int set_config(char *config_file, int reload) /* Start with general parameters, then specific parameters, user and peer */ user = build_user(cat, gen, ast_variable_browse(ucfg, cat), 0); if (user) { - AST_LIST_LOCK(&users); - AST_LIST_INSERT_HEAD(&users, user, entry); - AST_LIST_UNLOCK(&users); + ao2_link(users, user); + user = NULL; } peer = build_peer(cat, gen, ast_variable_browse(ucfg, cat), 0); if (peer) { - AST_LIST_LOCK(&peers); - AST_LIST_INSERT_HEAD(&peers, peer, entry); - AST_LIST_UNLOCK(&peers); if (ast_test_flag(peer, IAX_DYNAMIC)) reg_source_db(peer); + ao2_link(peers, peer); + peer = NULL; } } if (ast_true(registeriax) || (!registeriax && genregisteriax)) { @@ -10282,19 +10344,17 @@ static int set_config(char *config_file, int reload) if (!strcasecmp(utype, "user") || !strcasecmp(utype, "friend")) { user = build_user(cat, ast_variable_browse(cfg, cat), NULL, 0); if (user) { - AST_LIST_LOCK(&users); - AST_LIST_INSERT_HEAD(&users, user, entry); - AST_LIST_UNLOCK(&users); + ao2_link(users, user); + user = NULL; } } if (!strcasecmp(utype, "peer") || !strcasecmp(utype, "friend")) { peer = build_peer(cat, ast_variable_browse(cfg, cat), NULL, 0); if (peer) { - AST_LIST_LOCK(&peers); - AST_LIST_INSERT_HEAD(&peers, peer, entry); - AST_LIST_UNLOCK(&peers); if (ast_test_flag(peer, IAX_DYNAMIC)) reg_source_db(peer); + ao2_link(peers, peer); + peer = NULL; } } else if (strcasecmp(utype, "user")) { ast_log(LOG_WARNING, "Unknown type '%s' for '%s' in %s\n", utype, cat, config_file); @@ -10313,7 +10373,6 @@ static int reload_config(void) { char *config = "iax.conf"; struct iax2_registry *reg; - struct iax2_peer *peer; if (set_config(config, 1) == 1) { prune_peers(); @@ -10327,11 +10386,9 @@ static int reload_config(void) AST_LIST_UNLOCK(®istrations); /* Qualify hosts, too */ - AST_LIST_LOCK(&peers); - AST_LIST_TRAVERSE(&peers, peer, entry) - iax2_poke_peer(peer, 0); - AST_LIST_UNLOCK(&peers); + ao2_callback(peers, 0, iax2_poke_peer_cb, NULL); } + reload_firmware(); iax_provision_reload(1); @@ -10384,7 +10441,7 @@ static int cache_get_callno_locked(const char *data) ast_debug(1, "peer: %s, username: %s, password: %s, context: %s\n", pds.peer, pds.username, pds.password, pds.context); - callno = find_callno(0, 0, &sin, NEW_FORCE, 1, cai.sockfd); + callno = find_callno(0, 0, &sin, NEW_FORCE, cai.sockfd); if (callno < 1) { ast_log(LOG_WARNING, "Unable to create call\n"); return -1; @@ -10723,8 +10780,7 @@ static int function_iaxpeer(struct ast_channel *chan, const char *cmd, char *dat } } - if (ast_test_flag(peer, IAX_TEMPONLY)) - destroy_peer(peer); + peer_unref(peer); return 0; } @@ -10838,8 +10894,7 @@ static int iax2_devicestate(void *data) res = AST_DEVICE_UNKNOWN; } - if (ast_test_flag(p, IAX_TEMPONLY)) - destroy_peer(p); + peer_unref(p); return res; } @@ -11143,6 +11198,9 @@ static int __unload_module(void) for (x = 0; x < IAX_MAX_CALLS; x++) ast_mutex_destroy(&iaxsl[x]); + ao2_ref(peers, -1); + ao2_ref(users, -1); + return 0; } @@ -11153,6 +11211,15 @@ static int unload_module(void) return __unload_module(); } +static int peer_set_sock_cb(void *obj, void *arg, int flags) +{ + struct iax2_peer *peer = obj; + + if (peer->sockfd < 0) + peer->sockfd = defaultsockfd; + + return 0; +} /*! \brief Load IAX2 module, load configuraiton ---*/ static int load_module(void) @@ -11160,8 +11227,16 @@ static int load_module(void) char *config = "iax.conf"; int x = 0; struct iax2_registry *reg = NULL; - struct iax2_peer *peer = NULL; - + + peers = ao2_container_alloc(MAX_PEER_BUCKETS, peer_hash_cb, peer_cmp_cb); + if (!peers) + return AST_MODULE_LOAD_FAILURE; + users = ao2_container_alloc(MAX_USER_BUCKETS, user_hash_cb, user_cmp_cb); + if (!users) { + ao2_ref(peers, -1); + return AST_MODULE_LOAD_FAILURE; + } + ast_custom_function_register(&iaxpeer_function); ast_custom_function_register(&iaxvar_function); @@ -11245,13 +11320,9 @@ static int load_module(void) iax2_do_register(reg); AST_LIST_UNLOCK(®istrations); - AST_LIST_LOCK(&peers); - AST_LIST_TRAVERSE(&peers, peer, entry) { - if (peer->sockfd < 0) - peer->sockfd = defaultsockfd; - iax2_poke_peer(peer, 0); - } - AST_LIST_UNLOCK(&peers); + ao2_callback(peers, 0, peer_set_sock_cb, NULL); + ao2_callback(peers, 0, iax2_poke_peer_cb, NULL); + reload_firmware(); iax_provision_reload(0); diff --git a/include/asterisk/astobj2.h b/include/asterisk/astobj2.h new file mode 100644 index 000000000..73c8ede99 --- /dev/null +++ b/include/asterisk/astobj2.h @@ -0,0 +1,515 @@ +/* + * astobj2 - replacement containers for asterisk data structures. + * + * Copyright (C) 2006 Marta Carbone, Luigi Rizzo - Univ. di Pisa, Italy + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +#ifndef _ASTERISK_ASTOBJ2_H +#define _ASTERISK_ASTOBJ2_H + +#include "asterisk/lock.h" + +/*! \file + * + * \brief Object Model implementing objects and containers. + +These functions implement an abstraction for objects (with +locks and reference counts) and containers for these user-defined objects, +supporting locking, reference counting and callbacks. + +The internal implementation of the container is opaque to the user, +so we can use different data structures as needs arise. + +At the moment, however, the only internal data structure is a hash +table. When other structures will be implemented, the initialization +function may change. + +USAGE - OBJECTS + +An object is a block of memory that must be allocated with the +function ao2_alloc(), and for which the system keeps track (with +abit of help from the programmer) of the number of references around. +When an object has no more references, it is destroyed, by first +invoking whatever 'destructor' function the programmer specifies +(it can be NULL), and then freeing the memory. +This way objects can be shared without worrying who is in charge +of freeing them. + +Basically, creating an object requires the size of the object and +and a pointer to the destructor function: + + struct foo *o; + + o = ao2_alloc(sizeof(struct foo), my_destructor_fn); + +The object returned has a refcount = 1. +Note that the memory for the object is allocated and zeroed. +- We cannot realloc() the object itself. +- We cannot call free(o) to dispose of the object; rather we + tell the system that we do not need the reference anymore: + + ao2_ref(o, -1) + + causing the destructor to be called (and then memory freed) when + the refcount goes to 0. This is also available as ao2_unref(o), + and returns NULL as a convenience, so you can do things like + o = ao2_unref(o); + and clean the original pointer to prevent errors. + +- ao2_ref(o, +1) can be used to modify the refcount on the + object in case we want to pass it around. + + +- other calls on the object are ao2_lock(obj), ao2_unlock(), + ao2_trylock(), to manipulate the lock. + + +USAGE - CONTAINERS + +A containers is an abstract data structure where we can store +objects, search them (hopefully in an efficient way), and iterate +or apply a callback function to them. A container is just an object +itself. + +A container must first be allocated, specifying the initial +parameters. At the moment, this is done as follows: + + <b>Sample Usage:</b> + \code + + ao2_container *c; + + c = ao2_container_alloc(MAX_BUCKETS, my_hash_fn, my_cmp_fn, my_dump_fn); + +where +- MAX_BUCKETS is the number of buckets in the hash table, +- my_hash_fn() is the (user-supplied) function that returns a + hash key for the object (further reduced moduly MAX_BUCKETS + by the container's code); +- my_cmp_fn() is the default comparison function used when doing + searches on the container, +- my_dump_fn() is a helper function used only for debugging. + +A container knows little or nothing about the object itself, +other than the fact that it has been created by ao2_alloc() +All knowledge of the (user-defined) internals of the object +is left to the (user-supplied) functions passed as arguments +to ao2_container_alloc(). + +If we want to insert the object in the container, we should +initialize its fields -- especially, those used by my_hash_fn() -- +to compute the bucket to use. +Once done, we can link an object to a container with + + ao2_link(c, o); + +The function returns NULL in case of errors (and the object +is not inserted in the container). Other values mean success +(we are not supposed to use the value as a pointer to anything). + +\note inserting the object in the container grabs the reference +to the object (which is now owned by the container) so we do not +need to drop ours when we are done. + +\note While an object o is in a container, we expect that +my_hash_fn(o) will always return the same value. The function +does not lock the object to be computed, so modifications of +those fields that affect the computation of the hash should +be done by extractiong the object from the container, and +reinserting it after the change (this is not terribly expensive). + +\note A container with a single buckets is effectively a linked +list. However there is no ordering among elements. + +Objects implement a reference counter keeping the count +of the number of references that reference an object. + +When this number becomes zero the destructor will be +called and the object will be free'd. + */ + +/*! + * Invoked just before freeing the memory for the object. + * It is passed a pointer to user data. + */ +typedef void (*ao2_destructor_fn)(void *); + +void ao2_bt(void); /* backtrace */ +/*! + * Allocate and initialize an object. + * + * \param data_size The sizeof() of user-defined structure. + * \param destructor_fn The function destructor (can be NULL) + * \return A pointer to user data. + * + * Allocates a struct astobj2 with sufficient space for the + * user-defined structure. + * \notes: + * - storage is zeroed; XXX maybe we want a flag to enable/disable this. + * - the refcount of the object just created is 1 + * - the returned pointer cannot be free()'d or realloc()'ed; + * rather, we just call ao2_ref(o, -1); + */ +void *ao2_alloc(const size_t data_size, ao2_destructor_fn destructor_fn); + +/*! + * Reference/unreference an object and return the old refcount. + * + * \param o A pointer to the object + * \param delta Value to add to the reference counter. + * \return The value of the reference counter before the operation. + * + * Increase/decrease the reference counter according + * the value of delta. + * + * If the refcount goes to zero, the object is destroyed. + * + * \note The object must not be locked by the caller of this function, as + * it is invalid to try to unlock it after releasing the reference. + * + * \note if we know the pointer to an object, it is because we + * have a reference count to it, so the only case when the object + * can go away is when we release our reference, and it is + * the last one in existence. + */ +int ao2_ref(void *o, int delta); + +/*! + * Lock an object. + * + * \param a A pointer to the object we want lock. + * \return 0 on success, other values on error. + */ +int ao2_lock(void *a); + +/*! + * Unlock an object. + * + * \param a A pointer to the object we want unlock. + * \return 0 on success, other values on error. + */ +int ao2_unlock(void *a); + +/*! + * + * Containers + +containers are data structures meant to store several objects, +and perform various operations on them. +Internally, objects are stored in lists, hash tables or other +data structures depending on the needs. + +NOTA BENE: at the moment the only container we support is the +hash table and its degenerate form, the list. + +Operations on container include: + + c = ao2_container_alloc(size, cmp_fn, hash_fn) + allocate a container with desired size and default compare + and hash function + + ao2_find(c, arg, flags) + returns zero or more element matching a given criteria + (specified as arg). Flags indicate how many results we + want (only one or all matching entries), and whether we + should unlink the object from the container. + + ao2_callback(c, flags, fn, arg) + apply fn(obj, arg) to all objects in the container. + Similar to find. fn() can tell when to stop, and + do anything with the object including unlinking it. + Note that the entire operation is run with the container + locked, so noone else can change its content while we work on it. + However, we pay this with the fact that doing + anything blocking in the callback keeps the container + blocked. + The mechanism is very flexible because the callback function fn() + can do basically anything e.g. counting, deleting records, etc. + possibly using arg to store the results. + + iterate on a container + this is done with the following sequence + + ao2_container *c = ... // our container + ao2_iterator i; + void *o; + + i = ao2_iterator_init(c, flags); + + while ( (o = ao2_iterator_next(&i)) ) { + ... do something on o ... + ao2_ref(o, -1); + } + + The difference with the callback is that the control + on how to iterate is left to us. + + ao2_ref(c, -1) + dropping a reference to a container destroys it, very simple! + +Containers are astobj2 object themselves, and this is why their +implementation is simple too. + + */ + +/*! + * We can perform different operation on an object. We do this + * according the following flags. + */ +enum search_flags { + /*! unlink the object found */ + OBJ_UNLINK = (1 << 0), + /*! on match, don't return the object or increase its reference count. */ + OBJ_NODATA = (1 << 1), + /*! don't stop at the first match + * \note This is not fully implemented. */ + OBJ_MULTIPLE = (1 << 2), + /*! obj is an object of the same type as the one being searched for. + * This implies that it can be passed to the object's hash function + * for optimized searching. */ + OBJ_POINTER = (1 << 3), +}; + +/*! + * Type of a generic function to generate a hash value from an object. + * + */ +typedef int (*ao2_hash_fn)(const void *obj, const int flags); + +/*! + * valid callback results: + * We return a combination of + * CMP_MATCH when the object matches the request, + * and CMP_STOP when we should not continue the search further. + */ +enum _cb_results { + CMP_MATCH = 0x1, + CMP_STOP = 0x2, +}; + +/*! + * generic function to compare objects. + * This, as other callbacks, should return a combination of + * _cb_results as described above. + * + * \param o object from container + * \param arg search parameters (directly from ao2_find) + * \param flags passed directly from ao2_find + * XXX explain. + */ + +/*! + * Type of a generic callback function + * \param obj pointer to the (user-defined part) of an object. + * \param arg callback argument from ao2_callback() + * \param flags flags from ao2_callback() + * The return values are the same as a compare function. + * In fact, they are the same thing. + */ +typedef int (*ao2_callback_fn)(void *obj, void *arg, int flags); + +/*! + * Here start declarations of containers. + */ + +/*! + * This structure contains the total number of buckets + * and variable size array of object pointers. + * It is opaque, defined in astobj2.c, so we only need + * a type declaration. + */ +typedef struct __ao2_container ao2_container; + +/*! + * Allocate and initialize a container + * with the desired number of buckets. + * + * We allocate space for a struct astobj_container, struct container + * and the buckets[] array. + * + * \param my_hash_fn Pointer to a function computing a hash value. + * \param my_cmp_fn Pointer to a function comparating key-value + * with a string. (can be NULL) + * \return A pointer to a struct container. + * + * destructor is set implicitly. + */ +ao2_container *ao2_container_alloc(const uint n_buckets, + ao2_hash_fn hash_fn, ao2_callback_fn cmp_fn); + +/*! + * Returns the number of elements in a container. + */ +int ao2_container_count(ao2_container *c); + +/* + * Here we have functions to manage objects. + * + * We can use the functions below on any kind of + * object defined by the user. + */ +/*! + * Add an object to a container. + * + * \param c the container to operate on. + * \param obj the object to be added. + * \return NULL on errors, other values on success. + * + * This function insert an object in a container according its key. + * + * \note Remember to set the key before calling this function. + */ +void *ao2_link(ao2_container *c, void *newobj); +void *ao2_unlink(ao2_container *c, void *newobj); + +/*! \struct Used as return value if the flag OBJ_MULTIPLE is set */ +struct ao2_list { + struct ao2_list *next; + void *obj; /* pointer to the user portion of the object */ +}; + +/*! + * ao2_callback() and astob2_find() are the same thing with only one difference: + * the latter uses as a callback the function passed as my_cmp_f() at + * the time of the creation of the container. + * + * \param c A pointer to the container to operate on. + * \param arg passed to the callback. + * \param flags A set of flags specifying the operation to perform, + partially used by the container code, but also passed to + the callback. + * \return A pointer to the object found/marked, + * a pointer to a list of objects matching comparison function, + * NULL if not found. + * If the function returns any objects, their refcount is incremented, + * and the caller is in charge of decrementing them once done. + * Also, in case of multiple values returned, the list used + * to store the objects must be freed by the caller. + * + * This function searches through a container and performs operations + * on objects according on flags passed. + * XXX describe better + * The comparison is done calling the compare function set implicitly. + * The p pointer can be a pointer to an object or to a key, + * we can say this looking at flags value. + * If p points to an object we will search for the object pointed + * by this value, otherwise we serch for a key value. + * If the key is not uniq we only find the first matching valued. + * If we use the OBJ_MARK flags, we mark all the objects matching + * the condition. + * + * The use of flags argument is the follow: + * + * OBJ_UNLINK unlinks the object found + * OBJ_NODATA on match, do return an object + * Callbacks use OBJ_NODATA as a default + * functions such as find() do + * OBJ_MULTIPLE return multiple matches + * Default for _find() is no. + * to a key (not yet supported) + * OBJ_POINTER the pointer is an object pointer + * + * In case we return a list, the callee must take care to destroy + * that list when no longer used. + * + * \note When the returned object is no longer in use, ao2_ref() should + * be used to free the additional reference possibly created by this function. + */ +/* XXX order of arguments to find */ +void *ao2_find(ao2_container *c, void *arg, enum search_flags flags); +void *ao2_callback(ao2_container *c, + enum search_flags flags, + ao2_callback_fn cb_fn, void *arg); + +/*! + * + +When we need to walk through a container, we use +ao2_iterator to keep track of the current position. + +Because the navigation is typically done without holding the +lock on the container across the loop, +objects can be inserted or deleted or moved +while we work. As a consequence, there is no guarantee that +the we manage to touch all the elements on the list, or it +is possible that we touch the same object multiple times. + +An iterator must be first initialized with ao2_iterator_init(), +then we can use o = ao2_iterator_next() to move from one +element to the next. Remember that the object returned by +ao2_iterator_next() has its refcount incremented, +and the reference must be explicitly released when done with it. +Example: + + \code + + ao2_container *c = ... // the container we want to iterate on + ao2_iterator i; + struct my_obj *o; + + i = ao2_iterator_init(c, flags); + + while ( (o = ao2_iterator_next(&i)) ) { + ... do something on o ... + ao2_ref(o, -1); + } + + \endcode + + */ + +/*! + * You are not supposed to know the internals of an iterator! + * We would like the iterator to be opaque, unfortunately + * its size needs to be known if we want to store it around + * without too much trouble. + * Anyways... + * The iterator has a pointer to the container, and a flags + * field specifying various things e.g. whether the container + * should be locked or not while navigating on it. + * The iterator "points" to the current object, which is identified + * by three values: + * - a bucket number; + * - the object_id, which is also the container version number + * when the object was inserted. This identifies the object + * univoquely, however reaching the desired object requires + * scanning a list. + * - a pointer, and a container version when we saved the pointer. + * If the container has not changed its version number, then we + * can safely follow the pointer to reach the object in constant time. + * Details are in the implementation of ao2_iterator_next() + * A freshly-initialized iterator has bucket=0, version = 0. + */ + +struct __ao2_iterator { + /*! the container */ + ao2_container *c; + /*! operation flags */ + int flags; +#define F_AO2I_DONTLOCK 1 /*!< don't lock when iterating */ + /*! current bucket */ + int bucket; + /*! container version */ + uint c_version; + /*! pointer to the current object */ + void *obj; + /*! container version when the object was created */ + uint version; +}; +typedef struct __ao2_iterator ao2_iterator; + +ao2_iterator ao2_iterator_init(ao2_container *c, int flags); + +void *ao2_iterator_next(ao2_iterator *a); + +#endif /* _ASTERISK_ASTOBJ2_H */ diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h index 76b58d715..d542ef8d8 100644 --- a/include/asterisk/strings.h +++ b/include/asterisk/strings.h @@ -23,6 +23,7 @@ #ifndef _ASTERISK_STRINGS_H #define _ASTERISK_STRINGS_H +#include <stdlib.h> #include <string.h> #include <stdarg.h> @@ -418,7 +419,6 @@ int ast_str_make_space(struct ast_str **buf, size_t new_len), (buf); \ }) - /*! * \brief Retrieve a thread locally stored dynamic string * @@ -662,4 +662,22 @@ int __attribute__ ((format (printf, 3, 4))) ast_str_append( } ) +/*! + * \brief Compute a hash value on a string + * + * This famous hash algorithm was written by Dan Bernstein and is + * commonly used. + * + * http://www.cse.yorku.ca/~oz/hash.html + */ +static force_inline int ast_str_hash(const char *str) +{ + int hash = 5381; + + while (*str) + hash = hash * 33 ^ *str++; + + return abs(hash); +} + #endif /* _ASTERISK_STRINGS_H */ diff --git a/main/Makefile b/main/Makefile index ba780b281..fb807488b 100644 --- a/main/Makefile +++ b/main/Makefile @@ -26,7 +26,8 @@ OBJS= io.o sched.o logger.o frame.o loader.o config.o channel.o \ utils.o plc.o jitterbuf.o dnsmgr.o devicestate.o \ netsock.o slinfactory.o ast_expr2.o ast_expr2f.o \ cryptostub.o sha1.o http.o fixedjitterbuf.o abstract_jb.o \ - strcompat.o threadstorage.o dial.o event.o adsistub.o audiohook.o + strcompat.o threadstorage.o dial.o event.o adsistub.o audiohook.o \ + astobj2.o # we need to link in the objects statically, not as a library, because # otherwise modules will not have them available if none of the static diff --git a/main/astobj2.c b/main/astobj2.c new file mode 100644 index 000000000..9d2b0af2c --- /dev/null +++ b/main/astobj2.c @@ -0,0 +1,685 @@ +/* + * astobj2 - replacement containers for asterisk data structures. + * + * Copyright (C) 2006 Marta Carbone, Luigi Rizzo - Univ. di Pisa, Italy + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/* + * Function implementing astobj2 objects. + */ +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/astobj2.h" +#include "asterisk/utils.h" +#include "asterisk/cli.h" + +/*! + * astobj2 objects are always prepended this data structure, + * which contains a lock, a reference counter, + * the flags and a pointer to a destructor. + * The refcount is used to decide when it is time to + * invoke the destructor. + * The magic number is used for consistency check. + * XXX the lock is not always needed, and its initialization may be + * expensive. Consider making it external. + */ +struct __priv_data { + ast_mutex_t lock; + int ref_counter; + ao2_destructor_fn destructor_fn; + /*! for stats */ + size_t data_size; + /*! magic number. This is used to verify that a pointer passed in is a + * valid astobj2 */ + uint32_t magic; +}; + +#define AO2_MAGIC 0xa570b123 + +/*! + * What an astobj2 object looks like: fixed-size private data + * followed by variable-size user data. + */ +struct astobj2 { + struct __priv_data priv_data; + void *user_data[0]; +}; + +struct ao2_stats { + volatile int total_objects; + volatile int total_mem; + volatile int total_containers; + volatile int total_refs; + volatile int total_locked; +}; + +static struct ao2_stats ao2; + +#ifndef HAVE_BKTR /* backtrace support */ +void ao2_bt(void) {} +#else +#include <execinfo.h> /* for backtrace */ + +void ao2_bt(void) +{ + int c, i; +#define N1 20 + void *addresses[N1]; + char **strings; + + c = backtrace(addresses, N1); + strings = backtrace_symbols(addresses,c); + ast_verbose("backtrace returned: %d\n", c); + for(i = 0; i < c; i++) { + ast_verbose("%d: %p %s\n", i, addresses[i], strings[i]); + } + free(strings); +} +#endif + +/*! + * \brief convert from a pointer _p to a user-defined object + * + * \return the pointer to the astobj2 structure + */ +static inline struct astobj2 *INTERNAL_OBJ(void *user_data) +{ + struct astobj2 *p; + + if (!user_data) { + ast_log(LOG_ERROR, "user_data is NULL\n"); + return NULL; + } + + p = (struct astobj2 *) ((char *) user_data - sizeof(*p)); + if (AO2_MAGIC != (p->priv_data.magic) ) { + ast_log(LOG_ERROR, "bad magic number 0x%x for %p\n", p->priv_data.magic, p); + p = NULL; + } + + return p; +} + +/*! + * \brief convert from a pointer _p to an astobj2 object + * + * \return the pointer to the user-defined portion. + */ +#define EXTERNAL_OBJ(_p) ((_p) == NULL ? NULL : (_p)->user_data) + +int ao2_lock(void *user_data) +{ + struct astobj2 *p = INTERNAL_OBJ(user_data); + + if (p == NULL) + return -1; + + ast_atomic_fetchadd_int(&ao2.total_locked, 1); + + return ast_mutex_lock(&p->priv_data.lock); +} + +int ao2_unlock(void *user_data) +{ + struct astobj2 *p = INTERNAL_OBJ(user_data); + + if (p == NULL) + return -1; + + ast_atomic_fetchadd_int(&ao2.total_locked, -1); + + return ast_mutex_unlock(&p->priv_data.lock); +} + +/* + * The argument is a pointer to the user portion. + */ +int ao2_ref(void *user_data, const int delta) +{ + int current_value; + int ret; + struct astobj2 *obj = INTERNAL_OBJ(user_data); + + if (obj == NULL) + return -1; + + /* if delta is 0, just return the refcount */ + if (delta == 0) + return (obj->priv_data.ref_counter); + + /* we modify with an atomic operation the reference counter */ + ret = ast_atomic_fetchadd_int(&obj->priv_data.ref_counter, delta); + ast_atomic_fetchadd_int(&ao2.total_refs, delta); + current_value = ret + delta; + + /* this case must never happen */ + if (current_value < 0) + ast_log(LOG_ERROR, "refcount %d on object %p\n", current_value, user_data); + + if (current_value <= 0) { /* last reference, destroy the object */ + if (obj->priv_data.destructor_fn != NULL) + obj->priv_data.destructor_fn(user_data); + + ast_mutex_destroy(&obj->priv_data.lock); + ast_atomic_fetchadd_int(&ao2.total_mem, - obj->priv_data.data_size); + /* for safety, zero-out the astobj2 header and also the + * first word of the user-data, which we make sure is always + * allocated. */ + bzero(obj, sizeof(struct astobj2 *) + sizeof(void *) ); + free(obj); + ast_atomic_fetchadd_int(&ao2.total_objects, -1); + } + + return ret; +} + +/* + * We always alloc at least the size of a void *, + * for debugging purposes. + */ +void *ao2_alloc(size_t data_size, ao2_destructor_fn destructor_fn) +{ + /* allocation */ + struct astobj2 *obj; + + if (data_size < sizeof(void *)) + data_size = sizeof(void *); + + obj = ast_calloc(1, sizeof(*obj) + data_size); + + if (obj == NULL) + return NULL; + + ast_mutex_init(&obj->priv_data.lock); + obj->priv_data.magic = AO2_MAGIC; + obj->priv_data.data_size = data_size; + obj->priv_data.ref_counter = 1; + obj->priv_data.destructor_fn = destructor_fn; /* can be NULL */ + ast_atomic_fetchadd_int(&ao2.total_objects, 1); + ast_atomic_fetchadd_int(&ao2.total_mem, data_size); + ast_atomic_fetchadd_int(&ao2.total_refs, 1); + + /* return a pointer to the user data */ + return EXTERNAL_OBJ(obj); +} + +/* internal callback to destroy a container. */ +static void container_destruct(void *c); + +/* each bucket in the container is a tailq. */ +AST_LIST_HEAD_NOLOCK(bucket, bucket_list); + +/*! + * A container; stores the hash and callback functions, information on + * the size, the hash bucket heads, and a version number, starting at 0 + * (for a newly created, empty container) + * and incremented every time an object is inserted or deleted. + * The assumption is that an object is never moved in a container, + * but removed and readded with the new number. + * The version number is especially useful when implementing iterators. + * In fact, we can associate a unique, monotonically increasing number to + * each object, which means that, within an iterator, we can store the + * version number of the current object, and easily look for the next one, + * which is the next one in the list with a higher number. + * Since all objects have a version >0, we can use 0 as a marker for + * 'we need the first object in the bucket'. + * + * \todo Linking and unlink objects is typically expensive, as it + * involves a malloc() of a small object which is very inefficient. + * To optimize this, we allocate larger arrays of bucket_list's + * when we run out of them, and then manage our own freelist. + * This will be more efficient as we can do the freelist management while + * we hold the lock (that we need anyways). + */ +struct __ao2_container { + ao2_hash_fn hash_fn; + ao2_callback_fn cmp_fn; + int n_buckets; + /*! Number of elements in the container */ + int elements; + /*! described above */ + int version; + /*! variable size */ + struct bucket buckets[0]; +}; + +/*! + * \brief always zero hash function + * + * it is convenient to have a hash function that always returns 0. + * This is basically used when we want to have a container that is + * a simple linked list. + * + * \returns 0 + */ +static int hash_zero(const void *user_obj, const int flags) +{ + return 0; +} + +/* + * A container is just an object, after all! + */ +ao2_container * +ao2_container_alloc(const uint n_buckets, ao2_hash_fn hash_fn, + ao2_callback_fn cmp_fn) +{ + /* XXX maybe consistency check on arguments ? */ + /* compute the container size */ + size_t container_size = sizeof(ao2_container) + n_buckets * sizeof(struct bucket); + + ao2_container *c = ao2_alloc(container_size, container_destruct); + + if (!c) + return NULL; + + c->version = 1; /* 0 is a reserved value here */ + c->n_buckets = n_buckets; + c->hash_fn = hash_fn ? hash_fn : hash_zero; + c->cmp_fn = cmp_fn; + ast_atomic_fetchadd_int(&ao2.total_containers, 1); + + return c; +} + +/*! + * return the number of elements in the container + */ +int ao2_container_count(ao2_container *c) +{ + return c->elements; +} + +/*! + * A structure to create a linked list of entries, + * used within a bucket. + * XXX \todo this should be private to the container code + */ +struct bucket_list { + AST_LIST_ENTRY(bucket_list) entry; + int version; + struct astobj2 *astobj; /* pointer to internal data */ +}; + +/* + * link an object to a container + */ +void *ao2_link(ao2_container *c, void *user_data) +{ + int i; + /* create a new list entry */ + struct bucket_list *p; + struct astobj2 *obj = INTERNAL_OBJ(user_data); + + if (!obj) + return NULL; + + if (INTERNAL_OBJ(c) == NULL) + return NULL; + + p = ast_calloc(1, sizeof(*p)); + if (!p) + return NULL; + + i = c->hash_fn(user_data, OBJ_POINTER); + + ao2_lock(c); + i %= c->n_buckets; + p->astobj = obj; + p->version = ast_atomic_fetchadd_int(&c->version, 1); + AST_LIST_INSERT_HEAD(&c->buckets[i], p, entry); + ast_atomic_fetchadd_int(&c->elements, 1); + ao2_unlock(c); + + return p; +} + +/*! + * \brief another convenience function is a callback that matches on address + */ +static int match_by_addr(void *user_data, void *arg, int flags) +{ + return (user_data == arg) ? (CMP_MATCH | CMP_STOP) : 0; +} + +/* + * Unlink an object from the container + * and destroy the associated * ao2_bucket_list structure. + */ +void *ao2_unlink(ao2_container *c, void *user_data) +{ + if (INTERNAL_OBJ(user_data) == NULL) /* safety check on the argument */ + return NULL; + + ao2_callback(c, OBJ_UNLINK | OBJ_POINTER | OBJ_NODATA, match_by_addr, user_data); + + return NULL; +} + +/*! + * \brief special callback that matches all + */ +static int cb_true(void *user_data, void *arg, int flags) +{ + return CMP_MATCH; +} + +/*! + * Browse the container using different stategies accoding the flags. + * \return Is a pointer to an object or to a list of object if OBJ_MULTIPLE is + * specified. + */ +void *ao2_callback(ao2_container *c, + const enum search_flags flags, + ao2_callback_fn cb_fn, void *arg) +{ + int i, last; /* search boundaries */ + void *ret = NULL; + + if (INTERNAL_OBJ(c) == NULL) /* safety check on the argument */ + return NULL; + + if ((flags & (OBJ_MULTIPLE | OBJ_NODATA)) == OBJ_MULTIPLE) { + ast_log(LOG_WARNING, "multiple data return not implemented yet (flags %x)\n", flags); + return NULL; + } + + /* override the match function if necessary */ +#if 0 + /* Removing this slightly changes the meaning of OBJ_POINTER, but makes it + * do what I want it to. I'd like to hint to ao2_callback that the arg is + * of the same object type, so it can be passed to the hash function. + * However, I don't want to imply that this is the object being searched for. */ + if (flags & OBJ_POINTER) + cb_fn = match_by_addr; + else +#endif + if (cb_fn == NULL) /* if NULL, match everything */ + cb_fn = cb_true; + /* + * XXX this can be optimized. + * If we have a hash function and lookup by pointer, + * run the hash function. Otherwise, scan the whole container + * (this only for the time being. We need to optimize this.) + */ + if ((flags & OBJ_POINTER)) /* we know hash can handle this case */ + i = c->hash_fn(arg, flags & OBJ_POINTER) % c->n_buckets; + else /* don't know, let's scan all buckets */ + i = -1; /* XXX this must be fixed later. */ + + /* determine the search boundaries: i..last-1 */ + if (i < 0) { + i = 0; + last = c->n_buckets; + } else { + last = i + 1; + } + + ao2_lock(c); /* avoid modifications to the content */ + + for (; i < last ; i++) { + /* scan the list with prev-cur pointers */ + struct bucket_list *cur; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&c->buckets[i], cur, entry) { + int match = cb_fn(EXTERNAL_OBJ(cur->astobj), arg, flags) & (CMP_MATCH | CMP_STOP); + + /* we found the object, performing operations according flags */ + if (match == 0) { /* no match, no stop, continue */ + continue; + } else if (match == CMP_STOP) { /* no match but stop, we are done */ + i = last; + break; + } + /* we have a match (CMP_MATCH) here */ + if (!(flags & OBJ_NODATA)) { /* if must return the object, record the value */ + /* it is important to handle this case before the unlink */ + ret = EXTERNAL_OBJ(cur->astobj); + ao2_ref(ret, 1); + } + + if (flags & OBJ_UNLINK) { /* must unlink */ + struct bucket_list *x = cur; + + /* we are going to modify the container, so update version */ + ast_atomic_fetchadd_int(&c->version, 1); + AST_LIST_REMOVE_CURRENT(&c->buckets[i], entry); + /* update number of elements and version */ + ast_atomic_fetchadd_int(&c->elements, -1); + ao2_ref(EXTERNAL_OBJ(x->astobj), -1); + free(x); /* free the link record */ + } + + if ((match & CMP_STOP) || (flags & OBJ_MULTIPLE) == 0) { + /* We found the only match we need */ + i = last; /* force exit from outer loop */ + break; + } + if (!(flags & OBJ_NODATA)) { +#if 0 /* XXX to be completed */ + /* + * This is the multiple-return case. We need to link + * the object in a list. The refcount is already increased. + */ +#endif + } + } + AST_LIST_TRAVERSE_SAFE_END + } + ao2_unlock(c); + return ret; +} + +/*! + * the find function just invokes the default callback with some reasonable flags. + */ +void *ao2_find(ao2_container *c, void *arg, enum search_flags flags) +{ + return ao2_callback(c, flags, c->cmp_fn, arg); +} + +/*! + * initialize an iterator so we start from the first object + */ +ao2_iterator ao2_iterator_init(ao2_container *c, int flags) +{ + ao2_iterator a = { + .c = c, + .flags = flags + }; + + return a; +} + +/* + * move to the next element in the container. + */ +void * ao2_iterator_next(ao2_iterator *a) +{ + int lim; + struct bucket_list *p = NULL; + + if (INTERNAL_OBJ(a->c) == NULL) + return NULL; + + if (!(a->flags & F_AO2I_DONTLOCK)) + ao2_lock(a->c); + + /* optimization. If the container is unchanged and + * we have a pointer, try follow it + */ + if (a->c->version == a->c_version && (p = a->obj) ) { + if ( (p = AST_LIST_NEXT(p, entry)) ) + goto found; + /* nope, start from the next bucket */ + a->bucket++; + a->version = 0; + a->obj = NULL; + } + + lim = a->c->n_buckets; + + /* Browse the buckets array, moving to the next + * buckets if we don't find the entry in the current one. + * Stop when we find an element with version number greater + * than the current one (we reset the version to 0 when we + * switch buckets). + */ + for (; a->bucket < lim; a->bucket++, a->version = 0) { + /* scan the current bucket */ + AST_LIST_TRAVERSE(&a->c->buckets[a->bucket], p, entry) { + if (p->version > a->version) + goto found; + } + } + +found: + if (p) { + a->version = p->version; + a->obj = p; + a->c_version = a->c->version; + /* inc refcount of returned object */ + ao2_ref(EXTERNAL_OBJ(p->astobj), 1); + } + + if (!(a->flags & F_AO2I_DONTLOCK)) + ao2_unlock(a->c); + + return p ? EXTERNAL_OBJ(p->astobj) : NULL; +} + +/* callback for destroying container. + * we can make it simple as we know what it does + */ +static int cd_cb(void *obj, void *arg, int flag) +{ + ao2_ref(obj, -1); + return 0; +} + +static void container_destruct(void *_c) +{ + ao2_container *c = _c; + + ao2_callback(c, OBJ_UNLINK, cd_cb, NULL); + ast_atomic_fetchadd_int(&ao2.total_containers, -1); +} + +static int print_cb(void *obj, void *arg, int flag) +{ + int *fd = arg; + char *s = (char *)obj; + + ast_cli(*fd, "string <%s>\n", s); + return 0; +} + +/* + * Print stats + */ +static int handle_astobj2_stats(int fd, int argc, char *argv[]) +{ + ast_cli(fd, "Objects : %d\n", ao2.total_objects); + ast_cli(fd, "Containers : %d\n", ao2.total_containers); + ast_cli(fd, "Memory : %d\n", ao2.total_mem); + ast_cli(fd, "Locked : %d\n", ao2.total_locked); + ast_cli(fd, "Refs : %d\n", ao2.total_refs); + return 0; +} + +/* + * This is testing code for astobj + */ +static int handle_astobj2_test(int fd, int argc, char *argv[]) +{ + ao2_container *c1; + int i, lim; + char *obj; + static int prof_id = -1; + + if (prof_id == -1) + prof_id = ast_add_profile("ao2_alloc", 0); + + ast_cli(fd, "argc %d argv %s %s %s\n", argc, argv[0], argv[1], argv[2]); + lim = atoi(argv[2]); + ast_cli(fd, "called astobj_test\n"); + + handle_astobj2_stats(fd, 0, NULL); + /* + * allocate a container with no default callback, and no hash function. + * No hash means everything goes in the same bucket. + */ + c1 = ao2_container_alloc(100, NULL /* no callback */, NULL /* no hash */); + ast_cli(fd, "container allocated as %p\n", c1); + + /* + * fill the container with objects. + * ao2_alloc() gives us a reference which we pass to the + * container when we do the insert. + */ + for (i = 0; i < lim; i++) { + ast_mark(prof_id, 1 /* start */); + obj = ao2_alloc(80, NULL); + ast_mark(prof_id, 0 /* stop */); + ast_cli(fd, "object %d allocated as %p\n", i, obj); + sprintf(obj, "-- this is obj %d --", i); + ao2_link(c1, obj); + } + ast_cli(fd, "testing callbacks\n"); + ao2_callback(c1, 0, print_cb, &fd); + + ast_cli(fd, "testing iterators, remove every second object\n"); + { + ao2_iterator ai; + int x = 0; + + ai = ao2_iterator_init(c1, 0); + while ( (obj = ao2_iterator_next(&ai)) ) { + ast_cli(fd, "iterator on <%s>\n", obj); + if (x++ & 1) + ao2_unlink(c1, obj); + ao2_ref(obj, -1); + } + ast_cli(fd, "testing iterators again\n"); + ai = ao2_iterator_init(c1, 0); + while ( (obj = ao2_iterator_next(&ai)) ) { + ast_cli(fd, "iterator on <%s>\n", obj); + ao2_ref(obj, -1); + } + } + ast_cli(fd, "testing callbacks again\n"); + ao2_callback(c1, 0, print_cb, &fd); + + ast_verbose("now you should see an error message:\n"); + ao2_ref(&i, -1); /* i is not a valid object so we print an error here */ + + ast_cli(fd, "destroy container\n"); + ao2_ref(c1, -1); /* destroy container */ + handle_astobj2_stats(fd, 0, NULL); + return 0; +} + +static struct ast_cli_entry cli_astobj2[] = { + { { "astobj2", "stats", NULL }, + handle_astobj2_stats, "Print astobj2 statistics", }, + { { "astobj2", "test", NULL } , handle_astobj2_test, "Test astobj2", }, +}; + +int astobj2_init(void); +int astobj2_init(void) +{ + ast_cli_register_multiple(cli_astobj2, ARRAY_LEN(cli_astobj2)); + return 0; +} |