Update chan_iax2 to use linkedlists.h for users and peers. Modify the way get_from_jb and expire_registry works to get rid of certain crash scenarios. Finally - change the way expire_registry works when realtime autoclear is enabled to be a bit more efficient.

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@24422 65c4cc65-6c06-0410-ace0-fbb531ad65f3
1.4
Joshua Colp 20 years ago
parent 7cc4511fa2
commit b82b277790

@ -308,8 +308,8 @@ struct iax2_user {
struct ast_codec_pref prefs; struct ast_codec_pref prefs;
struct ast_ha *ha; struct ast_ha *ha;
struct iax2_context *contexts; struct iax2_context *contexts;
struct iax2_user *next;
struct ast_variable *vars; struct ast_variable *vars;
AST_LIST_ENTRY(iax2_user) entry;
}; };
struct iax2_peer { struct iax2_peer {
@ -357,7 +357,7 @@ struct iax2_peer {
int smoothing; /*!< Sample over how many units to determine historic ms */ int smoothing; /*!< Sample over how many units to determine historic ms */
struct ast_ha *ha; struct ast_ha *ha;
struct iax2_peer *next; 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)) #define IAX2_TRUNK_PREFACE (sizeof(struct iax_frame) + sizeof(struct ast_iax2_meta_hdr) + sizeof(struct ast_iax2_meta_trunk_hdr))
@ -628,15 +628,9 @@ static struct ast_iax2_queue {
ast_mutex_t lock; ast_mutex_t lock;
} iaxq; } iaxq;
static struct ast_user_list { static AST_LIST_HEAD_STATIC(users, iax2_user);
struct iax2_user *users;
ast_mutex_t lock;
} userl;
static struct ast_peer_list { static AST_LIST_HEAD_STATIC(peers, iax2_peer);
struct iax2_peer *peers;
ast_mutex_t lock;
} peerl;
static struct ast_firmware_list { static struct ast_firmware_list {
struct iax_firmware *wares; struct iax_firmware *wares;
@ -1009,14 +1003,18 @@ static int uncompress_subclass(unsigned char csub)
static struct iax2_peer *find_peer(const char *name, int realtime) static struct iax2_peer *find_peer(const char *name, int realtime)
{ {
struct iax2_peer *peer; struct iax2_peer *peer = NULL;
ast_mutex_lock(&peerl.lock);
for(peer = peerl.peers; peer; peer = peer->next) { /* Grab peer from linked list */
AST_LIST_LOCK(&peers);
AST_LIST_TRAVERSE(&peers, peer, entry) {
if (!strcasecmp(peer->name, name)) { if (!strcasecmp(peer->name, name)) {
break; break;
} }
} }
ast_mutex_unlock(&peerl.lock); AST_LIST_UNLOCK(&peers);
/* Now go for realtime if applicable */
if(!peer && realtime) if(!peer && realtime)
peer = realtime_peer(name, NULL); peer = realtime_peer(name, NULL);
return peer; return peer;
@ -1024,23 +1022,21 @@ static struct iax2_peer *find_peer(const char *name, int realtime)
static int iax2_getpeername(struct sockaddr_in sin, char *host, int len, int lockpeer) static int iax2_getpeername(struct sockaddr_in sin, char *host, int len, int lockpeer)
{ {
struct iax2_peer *peer; struct iax2_peer *peer = NULL;
int res = 0; int res = 0;
if (lockpeer) if (lockpeer)
ast_mutex_lock(&peerl.lock); AST_LIST_LOCK(&peers);
peer = peerl.peers; AST_LIST_TRAVERSE(&peers, peer, entry) {
while (peer) {
if ((peer->addr.sin_addr.s_addr == sin.sin_addr.s_addr) && if ((peer->addr.sin_addr.s_addr == sin.sin_addr.s_addr) &&
(peer->addr.sin_port == sin.sin_port)) { (peer->addr.sin_port == sin.sin_port)) {
ast_copy_string(host, peer->name, len); ast_copy_string(host, peer->name, len);
res = 1; res = 1;
break; break;
} }
peer = peer->next;
} }
if (lockpeer) if (lockpeer)
ast_mutex_unlock(&peerl.lock); AST_LIST_UNLOCK(&peers);
if (!peer) { if (!peer) {
peer = realtime_peer(NULL, &sin); peer = realtime_peer(NULL, &sin);
if (peer) { if (peer) {
@ -2134,20 +2130,20 @@ 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) static char *complete_iax2_show_peer(const char *line, const char *word, int pos, int state)
{ {
int which = 0; int which = 0;
struct iax2_peer *p; struct iax2_peer *p = NULL;
char *res = NULL; char *res = NULL;
int wordlen = strlen(word); int wordlen = strlen(word);
/* 0 - iax2; 1 - show; 2 - peer; 3 - <peername> */ /* 0 - iax2; 1 - show; 2 - peer; 3 - <peername> */
if (pos == 3) { if (pos == 3) {
ast_mutex_lock(&peerl.lock); AST_LIST_LOCK(&peers);
for (p = peerl.peers ; p ; p = p->next) { AST_LIST_TRAVERSE(&peers, p, entry) {
if (!strncasecmp(p->name, word, wordlen) && ++which > state) { if (!strncasecmp(p->name, word, wordlen) && ++which > state) {
res = ast_strdup(p->name); res = ast_strdup(p->name);
break; break;
} }
} }
ast_mutex_unlock(&peerl.lock); AST_LIST_UNLOCK(&peers);
} }
return res; return res;
@ -2304,66 +2300,75 @@ static void unwrap_timestamp(struct iax_frame *fr)
#ifdef NEWJB #ifdef NEWJB
static int get_from_jb(void *p); static int get_from_jb(void *p);
static void update_jbsched(struct chan_iax2_pvt *pvt) { static void update_jbsched(struct chan_iax2_pvt *pvt)
int when; {
int when;
when = ast_tvdiff_ms(ast_tvnow(), pvt->rxcore);
when = ast_tvdiff_ms(ast_tvnow(), pvt->rxcore);
/* fprintf(stderr, "now = %d, next=%d\n", when, jb_next(pvt->jb)); */
/* fprintf(stderr, "now = %d, next=%d\n", when, jb_next(pvt->jb)); */
when = jb_next(pvt->jb) - when;
/* fprintf(stderr, "when = %d\n", when); */ when = jb_next(pvt->jb) - when;
/* fprintf(stderr, "when = %d\n", when); */
if(pvt->jbid > -1) ast_sched_del(sched, pvt->jbid);
if(pvt->jbid > -1) ast_sched_del(sched, pvt->jbid);
if(when <= 0) {
/* XXX should really just empty until when > 0.. */ if(when <= 0) {
when = 1; /* XXX should really just empty until when > 0.. */
} when = 1;
}
pvt->jbid = ast_sched_add(sched, when, get_from_jb, (void *)pvt);
pvt->jbid = ast_sched_add(sched, when, get_from_jb, CALLNO_TO_PTR(pvt->callno));
/* Signal scheduler thread */
signal_condition(&sched_lock, &sched_cond); /* Signal scheduler thread */
signal_condition(&sched_lock, &sched_cond);
} }
static void __get_from_jb(void *p) static void __get_from_jb(void *p)
{ {
/* make sure pvt is valid! */ int callno = PTR_TO_CALLNO(p);
struct chan_iax2_pvt *pvt = p; struct chan_iax2_pvt *pvt = iaxs[callno];
struct iax_frame *fr; struct iax_frame *fr;
jb_frame frame; jb_frame frame;
int ret; int ret;
long now; long now;
long next; long next;
struct timeval tv; struct timeval tv;
ast_mutex_lock(&iaxsl[pvt->callno]); /* Make sure we have a valid private structure before going on */
/* fprintf(stderr, "get_from_jb called\n"); */ ast_mutex_lock(&iaxsl[callno]);
pvt->jbid = -1; pvt = iaxs[callno];
if (!pvt) {
gettimeofday(&tv,NULL); /* No go! */
/* round up a millisecond since ast_sched_runq does; */ ast_mutex_unlock(&iaxsl[callno]);
/* prevents us from spinning while waiting for our now */ return;
/* to catch up with runq's now */ }
tv.tv_usec += 1000;
now = ast_tvdiff_ms(tv, pvt->rxcore);
if(now >= (next = jb_next(pvt->jb))) { /* fprintf(stderr, "get_from_jb called\n"); */
pvt->jbid = -1;
gettimeofday(&tv,NULL);
/* round up a millisecond since ast_sched_runq does; */
/* prevents us from spinning while waiting for our now */
/* to catch up with runq's now */
tv.tv_usec += 1000;
now = ast_tvdiff_ms(tv, pvt->rxcore);
if(now >= (next = jb_next(pvt->jb))) {
ret = jb_get(pvt->jb,&frame,now,ast_codec_interp_len(pvt->voiceformat)); ret = jb_get(pvt->jb,&frame,now,ast_codec_interp_len(pvt->voiceformat));
switch(ret) { switch(ret) {
case JB_OK: case JB_OK:
/*if(frame.type == JB_TYPE_VOICE && next + 20 != jb_next(pvt->jb)) fprintf(stderr, "NEXT %ld is not %ld+20!\n", jb_next(pvt->jb), next); */ /*if(frame.type == JB_TYPE_VOICE && next + 20 != jb_next(pvt->jb)) fprintf(stderr, "NEXT %ld is not %ld+20!\n", jb_next(pvt->jb), next); */
fr = frame.data; fr = frame.data;
__do_deliver(fr); __do_deliver(fr);
break; break;
case JB_INTERP: case JB_INTERP:
{ {
struct ast_frame af; struct ast_frame af;
/*if(next + 20 != jb_next(pvt->jb)) fprintf(stderr, "NEXT %ld is not %ld+20!\n", jb_next(pvt->jb), next); */ /*if(next + 20 != jb_next(pvt->jb)) fprintf(stderr, "NEXT %ld is not %ld+20!\n", jb_next(pvt->jb), next); */
/* create an interpolation frame */ /* create an interpolation frame */
/*fprintf(stderr, "Making Interpolation frame\n"); */ /*fprintf(stderr, "Making Interpolation frame\n"); */
af.frametype = AST_FRAME_VOICE; af.frametype = AST_FRAME_VOICE;
@ -2375,28 +2380,28 @@ static void __get_from_jb(void *p)
af.data = NULL; af.data = NULL;
af.delivery = ast_tvadd(pvt->rxcore, ast_samp2tv(next, 1000)); af.delivery = ast_tvadd(pvt->rxcore, ast_samp2tv(next, 1000));
af.offset=AST_FRIENDLY_OFFSET; af.offset=AST_FRIENDLY_OFFSET;
/* queue the frame: For consistency, we would call __do_deliver here, but __do_deliver wants an iax_frame, /* queue the frame: For consistency, we would call __do_deliver here, but __do_deliver wants an iax_frame,
* which we'd need to malloc, and then it would free it. That seems like a drag */ * which we'd need to malloc, and then it would free it. That seems like a drag */
if (iaxs[pvt->callno] && !ast_test_flag(iaxs[pvt->callno], IAX_ALREADYGONE)) if (!ast_test_flag(iaxs[callno], IAX_ALREADYGONE))
iax2_queue_frame(pvt->callno, &af); iax2_queue_frame(callno, &af);
} }
break; break;
case JB_DROP: case JB_DROP:
/*if(next != jb_next(pvt->jb)) fprintf(stderr, "NEXT %ld is not next %ld!\n", jb_next(pvt->jb), next); */ /*if(next != jb_next(pvt->jb)) fprintf(stderr, "NEXT %ld is not next %ld!\n", jb_next(pvt->jb), next); */
iax2_frame_free(frame.data); iax2_frame_free(frame.data);
break; break;
case JB_NOFRAME: case JB_NOFRAME:
case JB_EMPTY: case JB_EMPTY:
/* do nothing */ /* do nothing */
break; break;
default: default:
/* shouldn't happen */ /* shouldn't happen */
break; break;
} }
} }
update_jbsched(pvt); update_jbsched(pvt);
ast_mutex_unlock(&iaxsl[pvt->callno]); ast_mutex_unlock(&iaxsl[callno]);
} }
static int get_from_jb(void *data) static int get_from_jb(void *data)
@ -2803,10 +2808,9 @@ static struct iax2_peer *realtime_peer(const char *peername, struct sockaddr_in
ast_sched_del(sched, peer->expire); ast_sched_del(sched, peer->expire);
peer->expire = ast_sched_add(sched, (global_rtautoclear) * 1000, expire_registry, peer); peer->expire = ast_sched_add(sched, (global_rtautoclear) * 1000, expire_registry, peer);
} }
ast_mutex_lock(&peerl.lock); AST_LIST_LOCK(&peers);
peer->next = peerl.peers; AST_LIST_INSERT_HEAD(&peers, peer, entry);
peerl.peers = peer; AST_LIST_UNLOCK(&peers);
ast_mutex_unlock(&peerl.lock);
if (ast_test_flag(peer, IAX_DYNAMIC)) if (ast_test_flag(peer, IAX_DYNAMIC))
reg_source_db(peer); reg_source_db(peer);
} else { } else {
@ -2861,10 +2865,9 @@ static struct iax2_user *realtime_user(const char *username)
if (ast_test_flag((&globalflags), IAX_RTCACHEFRIENDS)) { if (ast_test_flag((&globalflags), IAX_RTCACHEFRIENDS)) {
ast_set_flag(user, IAX_RTCACHEFRIENDS); ast_set_flag(user, IAX_RTCACHEFRIENDS);
ast_mutex_lock(&userl.lock); AST_LIST_LOCK(&users);
user->next = userl.users; AST_LIST_INSERT_HEAD(&users, user, entry);
userl.users = user; AST_LIST_UNLOCK(&users);
ast_mutex_unlock(&userl.lock);
} else { } else {
ast_set_flag(user, IAX_TEMPONLY); ast_set_flag(user, IAX_TEMPONLY);
} }
@ -3520,19 +3523,19 @@ static int iax2_write(struct ast_channel *c, struct ast_frame *f);
static int iax2_getpeertrunk(struct sockaddr_in sin) static int iax2_getpeertrunk(struct sockaddr_in sin)
{ {
struct iax2_peer *peer; struct iax2_peer *peer = NULL;
int res = 0; int res = 0;
ast_mutex_lock(&peerl.lock);
peer = peerl.peers; AST_LIST_LOCK(&peers);
while(peer) { AST_LIST_TRAVERSE(&peers, peer, entry) {
if ((peer->addr.sin_addr.s_addr == sin.sin_addr.s_addr) && if ((peer->addr.sin_addr.s_addr == sin.sin_addr.s_addr) &&
(peer->addr.sin_port == sin.sin_port)) { (peer->addr.sin_port == sin.sin_port)) {
res = ast_test_flag(peer, IAX_TRUNK); res = ast_test_flag(peer, IAX_TRUNK);
break; break;
} }
peer = peer->next;
} }
ast_mutex_unlock(&peerl.lock); AST_LIST_UNLOCK(&peers);
return res; return res;
} }
@ -4270,7 +4273,7 @@ static int iax2_show_users(int fd, int argc, char *argv[])
#define FORMAT "%-15.15s %-20.20s %-15.15s %-15.15s %-5.5s %-5.10s\n" #define FORMAT "%-15.15s %-20.20s %-15.15s %-15.15s %-5.5s %-5.10s\n"
#define FORMAT2 "%-15.15s %-20.20s %-15.15d %-15.15s %-5.5s %-5.10s\n" #define FORMAT2 "%-15.15s %-20.20s %-15.15d %-15.15s %-5.5s %-5.10s\n"
struct iax2_user *user; struct iax2_user *user = NULL;
char auth[90]; char auth[90];
char *pstr = ""; char *pstr = "";
@ -4288,32 +4291,32 @@ static int iax2_show_users(int fd, int argc, char *argv[])
return RESULT_SHOWUSAGE; return RESULT_SHOWUSAGE;
} }
ast_mutex_lock(&userl.lock);
ast_cli(fd, FORMAT, "Username", "Secret", "Authen", "Def.Context", "A/C","Codec Pref"); ast_cli(fd, FORMAT, "Username", "Secret", "Authen", "Def.Context", "A/C","Codec Pref");
for(user=userl.users;user;user=user->next) { AST_LIST_LOCK(&users);
AST_LIST_TRAVERSE(&users, user, entry) {
if (havepattern && regexec(&regexbuf, user->name, 0, NULL, 0)) if (havepattern && regexec(&regexbuf, user->name, 0, NULL, 0))
continue; continue;
if (!ast_strlen_zero(user->secret)) { if (!ast_strlen_zero(user->secret)) {
ast_copy_string(auth,user->secret,sizeof(auth)); ast_copy_string(auth,user->secret,sizeof(auth));
} else if (!ast_strlen_zero(user->inkeys)) { } else if (!ast_strlen_zero(user->inkeys)) {
snprintf(auth, sizeof(auth), "Key: %-15.15s ", user->inkeys); snprintf(auth, sizeof(auth), "Key: %-15.15s ", user->inkeys);
} else } else
ast_copy_string(auth, "-no secret-", sizeof(auth)); ast_copy_string(auth, "-no secret-", sizeof(auth));
if(ast_test_flag(user,IAX_CODEC_NOCAP)) if(ast_test_flag(user,IAX_CODEC_NOCAP))
pstr = "REQ Only"; pstr = "REQ Only";
else if(ast_test_flag(user,IAX_CODEC_NOPREFS)) else if(ast_test_flag(user,IAX_CODEC_NOPREFS))
pstr = "Disabled"; pstr = "Disabled";
else else
pstr = ast_test_flag(user,IAX_CODEC_USER_FIRST) ? "Caller" : "Host"; pstr = ast_test_flag(user,IAX_CODEC_USER_FIRST) ? "Caller" : "Host";
ast_cli(fd, FORMAT2, user->name, auth, user->authmethods, ast_cli(fd, FORMAT2, user->name, auth, user->authmethods,
user->contexts ? user->contexts->context : context, user->contexts ? user->contexts->context : context,
user->ha ? "Yes" : "No", pstr); user->ha ? "Yes" : "No", pstr);
} }
ast_mutex_unlock(&userl.lock); AST_LIST_UNLOCK(&users);
if (havepattern) if (havepattern)
regfree(&regexbuf); regfree(&regexbuf);
@ -4335,7 +4338,7 @@ static int __iax2_show_peers(int manager, int fd, struct mansession *s, int argc
#define FORMAT2 "%-15.15s %-15.15s %s %-15.15s %-8s %s %-10s%s" #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" #define FORMAT "%-15.15s %-15.15s %s %-15.15s %-5d%s %s %-10s%s"
struct iax2_peer *peer; struct iax2_peer *peer = NULL;
char name[256]; char name[256];
char iabuf[INET_ADDRSTRLEN]; char iabuf[INET_ADDRSTRLEN];
int registeredonly=0; int registeredonly=0;
@ -4374,12 +4377,14 @@ static int __iax2_show_peers(int manager, int fd, struct mansession *s, int argc
return RESULT_SHOWUSAGE; return RESULT_SHOWUSAGE;
} }
ast_mutex_lock(&peerl.lock);
if (s) if (s)
astman_append(s, FORMAT2, "Name/Username", "Host", " ", "Mask", "Port", " ", "Status", term); astman_append(s, FORMAT2, "Name/Username", "Host", " ", "Mask", "Port", " ", "Status", term);
else else
ast_cli(fd, FORMAT2, "Name/Username", "Host", " ", "Mask", "Port", " ", "Status", term); ast_cli(fd, FORMAT2, "Name/Username", "Host", " ", "Mask", "Port", " ", "Status", term);
for (peer = peerl.peers;peer;peer = peer->next) {
AST_LIST_LOCK(&peers);
AST_LIST_TRAVERSE(&peers, peer, entry) {
char nm[20]; char nm[20];
char status[20]; char status[20];
char srch[2000]; char srch[2000];
@ -4404,31 +4409,31 @@ static int __iax2_show_peers(int manager, int fd, struct mansession *s, int argc
unmonitored_peers++; unmonitored_peers++;
ast_copy_string(nm, ast_inet_ntoa(iabuf, sizeof(iabuf), peer->mask), sizeof(nm)); ast_copy_string(nm, ast_inet_ntoa(iabuf, sizeof(iabuf), peer->mask), sizeof(nm));
snprintf(srch, sizeof(srch), FORMAT, name, snprintf(srch, sizeof(srch), FORMAT, name,
peer->addr.sin_addr.s_addr ? ast_inet_ntoa(iabuf, sizeof(iabuf), peer->addr.sin_addr) : "(Unspecified)", peer->addr.sin_addr.s_addr ? ast_inet_ntoa(iabuf, sizeof(iabuf), peer->addr.sin_addr) : "(Unspecified)",
ast_test_flag(peer, IAX_DYNAMIC) ? "(D)" : "(S)", ast_test_flag(peer, IAX_DYNAMIC) ? "(D)" : "(S)",
nm, nm,
ntohs(peer->addr.sin_port), ast_test_flag(peer, IAX_TRUNK) ? "(T)" : " ", ntohs(peer->addr.sin_port), ast_test_flag(peer, IAX_TRUNK) ? "(T)" : " ",
peer->encmethods ? "(E)" : " ", status, term); peer->encmethods ? "(E)" : " ", status, term);
if (s) if (s)
astman_append(s, FORMAT, name, astman_append(s, FORMAT, name,
peer->addr.sin_addr.s_addr ? ast_inet_ntoa(iabuf, sizeof(iabuf), peer->addr.sin_addr) : "(Unspecified)", peer->addr.sin_addr.s_addr ? ast_inet_ntoa(iabuf, sizeof(iabuf), peer->addr.sin_addr) : "(Unspecified)",
ast_test_flag(peer, IAX_DYNAMIC) ? "(D)" : "(S)", ast_test_flag(peer, IAX_DYNAMIC) ? "(D)" : "(S)",
nm, nm,
ntohs(peer->addr.sin_port), ast_test_flag(peer, IAX_TRUNK) ? "(T)" : " ", ntohs(peer->addr.sin_port), ast_test_flag(peer, IAX_TRUNK) ? "(T)" : " ",
peer->encmethods ? "(E)" : " ", status, term); peer->encmethods ? "(E)" : " ", status, term);
else else
ast_cli(fd, FORMAT, name, ast_cli(fd, FORMAT, name,
peer->addr.sin_addr.s_addr ? ast_inet_ntoa(iabuf, sizeof(iabuf), peer->addr.sin_addr) : "(Unspecified)", peer->addr.sin_addr.s_addr ? ast_inet_ntoa(iabuf, sizeof(iabuf), peer->addr.sin_addr) : "(Unspecified)",
ast_test_flag(peer, IAX_DYNAMIC) ? "(D)" : "(S)", ast_test_flag(peer, IAX_DYNAMIC) ? "(D)" : "(S)",
nm, nm,
ntohs(peer->addr.sin_port), ast_test_flag(peer, IAX_TRUNK) ? "(T)" : " ", ntohs(peer->addr.sin_port), ast_test_flag(peer, IAX_TRUNK) ? "(T)" : " ",
peer->encmethods ? "(E)" : " ", status, term); peer->encmethods ? "(E)" : " ", status, term);
total_peers++; total_peers++;
} }
ast_mutex_unlock(&peerl.lock); AST_LIST_UNLOCK(&peers);
if (s) if (s)
astman_append(s,"%d iax2 peers [%d online, %d offline, %d unmonitored]%s", total_peers, online_peers, offline_peers, unmonitored_peers, term); astman_append(s,"%d iax2 peers [%d online, %d offline, %d unmonitored]%s", total_peers, online_peers, offline_peers, unmonitored_peers, term);
@ -4578,13 +4583,14 @@ static int iax2_show_registry(int fd, int argc, char *argv[])
{ {
#define FORMAT2 "%-20.20s %-10.10s %-20.20s %8.8s %s\n" #define FORMAT2 "%-20.20s %-10.10s %-20.20s %8.8s %s\n"
#define FORMAT "%-20.20s %-10.10s %-20.20s %8d %s\n" #define FORMAT "%-20.20s %-10.10s %-20.20s %8d %s\n"
struct iax2_registry *reg; struct iax2_registry *reg = NULL;
char host[80]; char host[80];
char perceived[80]; char perceived[80];
char iabuf[INET_ADDRSTRLEN]; char iabuf[INET_ADDRSTRLEN];
if (argc != 3) if (argc != 3)
return RESULT_SHOWUSAGE; return RESULT_SHOWUSAGE;
ast_mutex_lock(&peerl.lock); AST_LIST_LOCK(&peers);
ast_cli(fd, FORMAT2, "Host", "Username", "Perceived", "Refresh", "State"); ast_cli(fd, FORMAT2, "Host", "Username", "Perceived", "Refresh", "State");
for (reg = registrations;reg;reg = reg->next) { for (reg = registrations;reg;reg = reg->next) {
snprintf(host, sizeof(host), "%s:%d", ast_inet_ntoa(iabuf, sizeof(iabuf), reg->addr.sin_addr), ntohs(reg->addr.sin_port)); snprintf(host, sizeof(host), "%s:%d", ast_inet_ntoa(iabuf, sizeof(iabuf), reg->addr.sin_addr), ntohs(reg->addr.sin_port));
@ -4595,7 +4601,7 @@ static int iax2_show_registry(int fd, int argc, char *argv[])
ast_cli(fd, FORMAT, host, ast_cli(fd, FORMAT, host,
reg->username, perceived, reg->refresh, regstate2str(reg->regstate)); reg->username, perceived, reg->refresh, regstate2str(reg->regstate));
} }
ast_mutex_unlock(&peerl.lock); AST_LIST_UNLOCK(&peers);
return RESULT_SUCCESS; return RESULT_SUCCESS;
#undef FORMAT #undef FORMAT
#undef FORMAT2 #undef FORMAT2
@ -4959,7 +4965,7 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
/* Start pessimistic */ /* Start pessimistic */
int res = -1; int res = -1;
int version = 2; int version = 2;
struct iax2_user *user, *best = NULL; struct iax2_user *user = NULL, *best = NULL;
int bestscore = 0; int bestscore = 0;
int gotcapability=0; int gotcapability=0;
char iabuf[INET_ADDRSTRLEN]; char iabuf[INET_ADDRSTRLEN];
@ -5014,10 +5020,9 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
ast_inet_ntoa(iabuf, sizeof(iabuf), sin->sin_addr), version); ast_inet_ntoa(iabuf, sizeof(iabuf), sin->sin_addr), version);
return res; return res;
} }
ast_mutex_lock(&userl.lock);
/* Search the userlist for a compatible entry, and fill in the rest */ /* Search the userlist for a compatible entry, and fill in the rest */
user = userl.users; AST_LIST_LOCK(&users);
while(user) { AST_LIST_TRAVERSE(&users, user, entry) {
if ((ast_strlen_zero(iaxs[callno]->username) || /* No username specified */ if ((ast_strlen_zero(iaxs[callno]->username) || /* No username specified */
!strcmp(iaxs[callno]->username, user->name)) /* Or this username specified */ !strcmp(iaxs[callno]->username, user->name)) /* Or this username specified */
&& ast_apply_ha(user->ha, sin) /* Access is permitted from this IP */ && ast_apply_ha(user->ha, sin) /* Access is permitted from this IP */
@ -5058,9 +5063,8 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
} }
} }
} }
user = user->next;
} }
ast_mutex_unlock(&userl.lock); AST_LIST_UNLOCK(&users);
user = best; user = best;
if (!user && !ast_strlen_zero(iaxs[callno]->username)) { if (!user && !ast_strlen_zero(iaxs[callno]->username)) {
user = realtime_user(iaxs[callno]->username); user = realtime_user(iaxs[callno]->username);
@ -5473,7 +5477,7 @@ static int authenticate(char *challenge, char *secret, char *keyn, int authmetho
static int authenticate_reply(struct chan_iax2_pvt *p, struct sockaddr_in *sin, struct iax_ies *ies, char *override, char *okey) static int authenticate_reply(struct chan_iax2_pvt *p, struct sockaddr_in *sin, struct iax_ies *ies, char *override, char *okey)
{ {
struct iax2_peer *peer; struct iax2_peer *peer = NULL;
/* Start pessimistic */ /* Start pessimistic */
int res = -1; int res = -1;
int authmethods = 0; int authmethods = 0;
@ -5497,23 +5501,21 @@ static int authenticate_reply(struct chan_iax2_pvt *p, struct sockaddr_in *sin,
/* Normal password authentication */ /* Normal password authentication */
res = authenticate(p->challenge, override, okey, authmethods, &ied, sin, &p->ecx, &p->dcx); res = authenticate(p->challenge, override, okey, authmethods, &ied, sin, &p->ecx, &p->dcx);
} else { } else {
ast_mutex_lock(&peerl.lock); AST_LIST_LOCK(&peers);
peer = peerl.peers; AST_LIST_TRAVERSE(&peers, peer, entry) {
while(peer) {
if ((ast_strlen_zero(p->peer) || !strcmp(p->peer, peer->name)) if ((ast_strlen_zero(p->peer) || !strcmp(p->peer, peer->name))
/* No peer specified at our end, or this is the peer */ /* No peer specified at our end, or this is the peer */
&& (ast_strlen_zero(peer->username) || (!strcmp(peer->username, p->username))) && (ast_strlen_zero(peer->username) || (!strcmp(peer->username, p->username)))
/* No username specified in peer rule, or this is the right username */ /* No username specified in peer rule, or this is the right username */
&& (!peer->addr.sin_addr.s_addr || ((sin->sin_addr.s_addr & peer->mask.s_addr) == (peer->addr.sin_addr.s_addr & peer->mask.s_addr))) && (!peer->addr.sin_addr.s_addr || ((sin->sin_addr.s_addr & peer->mask.s_addr) == (peer->addr.sin_addr.s_addr & peer->mask.s_addr)))
/* No specified host, or this is our host */ /* No specified host, or this is our host */
) { ) {
res = authenticate(p->challenge, peer->secret, peer->outkey, authmethods, &ied, sin, &p->ecx, &p->dcx); res = authenticate(p->challenge, peer->secret, peer->outkey, authmethods, &ied, sin, &p->ecx, &p->dcx);
if (!res) if (!res)
break; break;
} }
peer = peer->next;
} }
ast_mutex_unlock(&peerl.lock); AST_LIST_UNLOCK(&peers);
if (!peer) { if (!peer) {
/* We checked our list and didn't find one. It's unlikely, but possible, /* We checked our list and didn't find one. It's unlikely, but possible,
that we're trying to authenticate *to* a realtime peer */ that we're trying to authenticate *to* a realtime peer */
@ -5830,7 +5832,25 @@ static void prune_peers(void);
static void __expire_registry(void *data) static void __expire_registry(void *data)
{ {
struct iax2_peer *p = 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)) {
/* If we are set to auto clear then remove ourselves */
if (ast_test_flag(p, IAX_RTAUTOCLEAR))
AST_LIST_REMOVE_CURRENT(&peers, entry);
break;
}
}
AST_LIST_TRAVERSE_SAFE_END
AST_LIST_UNLOCK(&peers);
/* Peer is already gone for whatever reason */
if (!p)
return;
ast_log(LOG_DEBUG, "Expiring registration for peer '%s'\n", p->name); ast_log(LOG_DEBUG, "Expiring registration for peer '%s'\n", p->name);
manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "Peer: IAX2/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", p->name); manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "Peer: IAX2/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", p->name);
@ -5846,8 +5866,8 @@ static void __expire_registry(void *data)
iax2_regfunk(p->name, 0); iax2_regfunk(p->name, 0);
if (ast_test_flag(p, IAX_RTAUTOCLEAR)) { if (ast_test_flag(p, IAX_RTAUTOCLEAR)) {
ast_set_flag(p, IAX_DELME); /* We are already gone from the list, so we can just destroy ourselves */
prune_peers(); destroy_peer(p);
} }
} }
@ -5893,7 +5913,7 @@ static void reg_source_db(struct iax2_peer *p)
if (p->expire > -1) if (p->expire > -1)
ast_sched_del(sched, p->expire); ast_sched_del(sched, p->expire);
ast_device_state_changed("IAX2/%s", p->name); /* Activate notification */ ast_device_state_changed("IAX2/%s", p->name); /* Activate notification */
p->expire = ast_sched_add(sched, (p->expiry + 10) * 1000, expire_registry, (void *)p); p->expire = ast_sched_add(sched, (p->expiry + 10) * 1000, expire_registry, (void *)p->name);
if (iax2_regfunk) if (iax2_regfunk)
iax2_regfunk(p->name, 1); iax2_regfunk(p->name, 1);
register_peer_exten(p, 1); register_peer_exten(p, 1);
@ -5971,7 +5991,7 @@ static int update_registry(char *name, struct sockaddr_in *sin, int callno, char
p->expiry = refresh; p->expiry = refresh;
} }
if (p->expiry && sin->sin_addr.s_addr) if (p->expiry && sin->sin_addr.s_addr)
p->expire = ast_sched_add(sched, (p->expiry + 10) * 1000, expire_registry, (void *)p); p->expire = ast_sched_add(sched, (p->expiry + 10) * 1000, expire_registry, (void *)p->name);
iax_ie_append_str(&ied, IAX_IE_USERNAME, p->name); iax_ie_append_str(&ied, IAX_IE_USERNAME, p->name);
iax_ie_append_int(&ied, IAX_IE_DATETIME, iax2_datetime(p->zonetag)); iax_ie_append_int(&ied, IAX_IE_DATETIME, iax2_datetime(p->zonetag));
if (sin->sin_addr.s_addr) { if (sin->sin_addr.s_addr) {
@ -6289,7 +6309,6 @@ static int timing_read(int *id, int fd, short events, void *cbdata)
res = read(fd, buf, sizeof(buf)); res = read(fd, buf, sizeof(buf));
if (res < 1) { if (res < 1) {
ast_log(LOG_WARNING, "Unable to read from timing fd\n"); ast_log(LOG_WARNING, "Unable to read from timing fd\n");
ast_mutex_unlock(&peerl.lock);
return 1; return 1;
} }
} }
@ -6553,7 +6572,7 @@ static int socket_read(int *id, int fd, short events, void *cbdata)
thread->iofd = fd; thread->iofd = fd;
thread->iores = recvfrom(fd, thread->buf, sizeof(thread->buf), 0,(struct sockaddr *) &thread->iosin, &len); thread->iores = recvfrom(fd, thread->buf, sizeof(thread->buf), 0,(struct sockaddr *) &thread->iosin, &len);
if (thread->iores < 0) { if (thread->iores < 0) {
if (errno != ECONNREFUSED) if (errno != ECONNREFUSED && errno != EAGAIN)
ast_log(LOG_WARNING, "Error: %s\n", strerror(errno)); ast_log(LOG_WARNING, "Error: %s\n", strerror(errno));
handle_error(); handle_error();
AST_LIST_LOCK(&idle_list); AST_LIST_LOCK(&idle_list);
@ -8554,21 +8573,17 @@ static int peer_set_srcaddr(struct iax2_peer *peer, const char *srcaddr)
/*! \brief Create peer structure based on configuration */ /*! \brief Create peer structure based on configuration */
static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, int temponly) static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, int temponly)
{ {
struct iax2_peer *peer; struct iax2_peer *peer = NULL;
struct iax2_peer *prev;
struct ast_ha *oldha = NULL; struct ast_ha *oldha = NULL;
int maskfound=0; int maskfound=0;
int found=0; int found=0;
prev = NULL;
ast_mutex_lock(&peerl.lock); AST_LIST_LOCK(&peers);
if (!temponly) { if (!temponly) {
peer = peerl.peers; AST_LIST_TRAVERSE(&peers, peer, entry) {
while(peer) {
if (!strcmp(peer->name, name)) { if (!strcmp(peer->name, name)) {
break; break;
} }
prev = peer;
peer = peer->next;
} }
} else } else
peer = NULL; peer = NULL;
@ -8576,15 +8591,10 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, in
found++; found++;
oldha = peer->ha; oldha = peer->ha;
peer->ha = NULL; peer->ha = NULL;
/* Already in the list, remove it and it will be added back (or FREE'd) */ AST_LIST_REMOVE(&peers, peer, entry);
if (prev) { AST_LIST_UNLOCK(&peers);
prev->next = peer->next;
} else {
peerl.peers = peer->next;
}
ast_mutex_unlock(&peerl.lock);
} else { } else {
ast_mutex_unlock(&peerl.lock); AST_LIST_UNLOCK(&peers);
if ((peer = ast_calloc(1, sizeof(*peer)))) { if ((peer = ast_calloc(1, sizeof(*peer)))) {
peer->expire = -1; peer->expire = -1;
peer->pokeexpire = -1; peer->pokeexpire = -1;
@ -8744,7 +8754,7 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, in
/*! \brief Create in-memory user structure from configuration */ /*! \brief Create in-memory user structure from configuration */
static struct iax2_user *build_user(const char *name, struct ast_variable *v, int temponly) static struct iax2_user *build_user(const char *name, struct ast_variable *v, int temponly)
{ {
struct iax2_user *prev, *user; struct iax2_user *user = NULL;
struct iax2_context *con, *conl = NULL; struct iax2_context *con, *conl = NULL;
struct ast_ha *oldha = NULL; struct ast_ha *oldha = NULL;
struct iax2_context *oldcon = NULL; struct iax2_context *oldcon = NULL;
@ -8752,16 +8762,12 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, in
char *varname = NULL, *varval = NULL; char *varname = NULL, *varval = NULL;
struct ast_variable *tmpvar = NULL; struct ast_variable *tmpvar = NULL;
prev = NULL; AST_LIST_LOCK(&users);
ast_mutex_lock(&userl.lock);
if (!temponly) { if (!temponly) {
user = userl.users; AST_LIST_TRAVERSE(&users, user, entry) {
while(user) {
if (!strcmp(user->name, name)) { if (!strcmp(user->name, name)) {
break; break;
} }
prev = user;
user = user->next;
} }
} else } else
user = NULL; user = NULL;
@ -8772,14 +8778,10 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, in
user->ha = NULL; user->ha = NULL;
user->contexts = NULL; user->contexts = NULL;
/* Already in the list, remove it and it will be added back (or FREE'd) */ /* Already in the list, remove it and it will be added back (or FREE'd) */
if (prev) { AST_LIST_REMOVE(&users, user, entry);
prev->next = user->next; AST_LIST_UNLOCK(&users);
} else {
userl.users = user->next;
}
ast_mutex_unlock(&userl.lock);
} else { } else {
ast_mutex_unlock(&userl.lock); AST_LIST_UNLOCK(&users);
/* This is going to memset'd to 0 in the next block */ /* This is going to memset'd to 0 in the next block */
user = ast_malloc(sizeof(*user)); user = ast_malloc(sizeof(*user));
} }
@ -8894,16 +8896,15 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, in
static void delete_users(void) static void delete_users(void)
{ {
struct iax2_user *user; struct iax2_user *user = NULL;
struct iax2_peer *peer; struct iax2_peer *peer = NULL;
struct iax2_registry *reg, *regl; struct iax2_registry *reg, *regl;
ast_mutex_lock(&userl.lock); AST_LIST_LOCK(&users);
for (user=userl.users;user;) { AST_LIST_TRAVERSE(&users, user, entry)
ast_set_flag(user, IAX_DELME); ast_set_flag(user, IAX_DELME);
user = user->next; AST_LIST_UNLOCK(&users);
}
ast_mutex_unlock(&userl.lock);
for (reg = registrations;reg;) { for (reg = registrations;reg;) {
regl = reg; regl = reg;
reg = reg->next; reg = reg->next;
@ -8922,13 +8923,12 @@ static void delete_users(void)
free(regl); free(regl);
} }
registrations = NULL; registrations = NULL;
ast_mutex_lock(&peerl.lock);
for (peer=peerl.peers;peer;) { AST_LIST_LOCK(&peers);
/* Assume all will be deleted, and we'll find out for sure later */ AST_LIST_TRAVERSE(&peers, peer, entry)
ast_set_flag(peer, IAX_DELME); ast_set_flag(peer, IAX_DELME);
peer = peer->next; AST_LIST_UNLOCK(&peers);
}
ast_mutex_unlock(&peerl.lock);
} }
static void destroy_user(struct iax2_user *user) static void destroy_user(struct iax2_user *user)
@ -8944,21 +8944,18 @@ static void destroy_user(struct iax2_user *user)
static void prune_users(void) static void prune_users(void)
{ {
struct iax2_user *user, *usernext, *userlast = NULL; struct iax2_user *user = NULL;
ast_mutex_lock(&userl.lock);
for (user=userl.users;user;) { AST_LIST_LOCK(&users);
usernext = user->next; AST_LIST_TRAVERSE_SAFE_BEGIN(&users, user, entry) {
if (ast_test_flag(user, IAX_DELME)) { if (ast_test_flag(user, IAX_DELME)) {
destroy_user(user); destroy_user(user);
if (userlast) AST_LIST_REMOVE_CURRENT(&users, entry);
userlast->next = usernext; }
else
userl.users = usernext;
} else
userlast = user;
user = usernext;
} }
ast_mutex_unlock(&userl.lock); AST_LIST_TRAVERSE_SAFE_END
AST_LIST_UNLOCK(&users);
} }
static void destroy_peer(struct iax2_peer *peer) static void destroy_peer(struct iax2_peer *peer)
@ -8987,22 +8984,17 @@ static void destroy_peer(struct iax2_peer *peer)
static void prune_peers(void){ static void prune_peers(void){
/* Prune peers who still are supposed to be deleted */ /* Prune peers who still are supposed to be deleted */
struct iax2_peer *peer, *peerlast, *peernext; struct iax2_peer *peer = NULL;
ast_mutex_lock(&peerl.lock);
peerlast = NULL; AST_LIST_LOCK(&peers);
for (peer=peerl.peers;peer;) { AST_LIST_TRAVERSE_SAFE_BEGIN(&peers, peer, entry) {
peernext = peer->next;
if (ast_test_flag(peer, IAX_DELME)) { if (ast_test_flag(peer, IAX_DELME)) {
destroy_peer(peer); destroy_peer(peer);
if (peerlast) AST_LIST_REMOVE_CURRENT(&peers, entry);
peerlast->next = peernext; }
else
peerl.peers = peernext;
} else
peerlast = peer;
peer=peernext;
} }
ast_mutex_unlock(&peerl.lock); AST_LIST_TRAVERSE_SAFE_END
AST_LIST_UNLOCK(&peers);
} }
static void set_timing(void) static void set_timing(void)
@ -9274,19 +9266,17 @@ static int set_config(char *config_file, int reload)
if (!strcasecmp(utype, "user") || !strcasecmp(utype, "friend")) { if (!strcasecmp(utype, "user") || !strcasecmp(utype, "friend")) {
user = build_user(cat, ast_variable_browse(cfg, cat), 0); user = build_user(cat, ast_variable_browse(cfg, cat), 0);
if (user) { if (user) {
ast_mutex_lock(&userl.lock); AST_LIST_LOCK(&users);
user->next = userl.users; AST_LIST_INSERT_HEAD(&users, user, entry);
userl.users = user; AST_LIST_UNLOCK(&users);
ast_mutex_unlock(&userl.lock);
} }
} }
if (!strcasecmp(utype, "peer") || !strcasecmp(utype, "friend")) { if (!strcasecmp(utype, "peer") || !strcasecmp(utype, "friend")) {
peer = build_peer(cat, ast_variable_browse(cfg, cat), 0); peer = build_peer(cat, ast_variable_browse(cfg, cat), 0);
if (peer) { if (peer) {
ast_mutex_lock(&peerl.lock); AST_LIST_LOCK(&peers);
peer->next = peerl.peers; AST_LIST_INSERT_HEAD(&peers, peer, entry);
peerl.peers = peer; AST_LIST_UNLOCK(&peers);
ast_mutex_unlock(&peerl.lock);
if (ast_test_flag(peer, IAX_DYNAMIC)) if (ast_test_flag(peer, IAX_DYNAMIC))
reg_source_db(peer); reg_source_db(peer);
} }
@ -9307,7 +9297,8 @@ static int reload_config(void)
{ {
char *config = "iax.conf"; char *config = "iax.conf";
struct iax2_registry *reg; struct iax2_registry *reg;
struct iax2_peer *peer; struct iax2_peer *peer = NULL;
ast_copy_string(accountcode, "", sizeof(accountcode)); ast_copy_string(accountcode, "", sizeof(accountcode));
ast_copy_string(language, "", sizeof(language)); ast_copy_string(language, "", sizeof(language));
amaflags = 0; amaflags = 0;
@ -9322,12 +9313,13 @@ static int reload_config(void)
for (reg = registrations; reg; reg = reg->next) for (reg = registrations; reg; reg = reg->next)
iax2_do_register(reg); iax2_do_register(reg);
/* Qualify hosts, too */ /* Qualify hosts, too */
ast_mutex_lock(&peerl.lock); AST_LIST_LOCK(&peers);
for (peer = peerl.peers; peer; peer = peer->next) AST_LIST_TRAVERSE(&peers, peer, entry)
iax2_poke_peer(peer, 0); iax2_poke_peer(peer, 0);
ast_mutex_unlock(&peerl.lock); AST_LIST_UNLOCK(&peers);
reload_firmware(); reload_firmware();
iax_provision_reload(); iax_provision_reload();
return 0; return 0;
} }
@ -10035,8 +10027,6 @@ static int __unload_module(void)
static int unload_module(void *mod) static int unload_module(void *mod)
{ {
ast_mutex_destroy(&iaxq.lock); ast_mutex_destroy(&iaxq.lock);
ast_mutex_destroy(&userl.lock);
ast_mutex_destroy(&peerl.lock);
ast_mutex_destroy(&waresl.lock); ast_mutex_destroy(&waresl.lock);
ast_custom_function_unregister(&iaxpeer_function); ast_custom_function_unregister(&iaxpeer_function);
return __unload_module(); return __unload_module();
@ -10049,8 +10039,8 @@ static int load_module(void *mod)
char *config = "iax.conf"; char *config = "iax.conf";
int res = 0; int res = 0;
int x; int x;
struct iax2_registry *reg; struct iax2_registry *reg = NULL;
struct iax2_peer *peer; struct iax2_peer *peer = NULL;
ast_custom_function_register(&iaxpeer_function); ast_custom_function_register(&iaxpeer_function);
@ -10091,8 +10081,6 @@ static int load_module(void *mod)
ast_netsock_init(netsock); ast_netsock_init(netsock);
ast_mutex_init(&iaxq.lock); ast_mutex_init(&iaxq.lock);
ast_mutex_init(&userl.lock);
ast_mutex_init(&peerl.lock);
ast_mutex_init(&waresl.lock); ast_mutex_init(&waresl.lock);
ast_cli_register_multiple(iax2_cli, sizeof(iax2_cli) / sizeof(iax2_cli[0])); ast_cli_register_multiple(iax2_cli, sizeof(iax2_cli) / sizeof(iax2_cli[0]));
@ -10124,13 +10112,13 @@ static int load_module(void *mod)
for (reg = registrations; reg; reg = reg->next) for (reg = registrations; reg; reg = reg->next)
iax2_do_register(reg); iax2_do_register(reg);
ast_mutex_lock(&peerl.lock); AST_LIST_LOCK(&peers);
for (peer = peerl.peers; peer; peer = peer->next) { AST_LIST_TRAVERSE(&peers, peer, entry) {
if (peer->sockfd < 0) if (peer->sockfd < 0)
peer->sockfd = defaultsockfd; peer->sockfd = defaultsockfd;
iax2_poke_peer(peer, 0); iax2_poke_peer(peer, 0);
} }
ast_mutex_unlock(&peerl.lock); AST_LIST_UNLOCK(&peers);
reload_firmware(); reload_firmware();
iax_provision_reload(); iax_provision_reload();
return res; return res;

Loading…
Cancel
Save