diff --git a/channels/chan_mgcp.c b/channels/chan_mgcp.c index f1fa9dec8a5ad2fb194aa77af663e1737884fbbf..49b24dba14b094f80233ae4da119adc0740ec36c 100755 --- a/channels/chan_mgcp.c +++ b/channels/chan_mgcp.c @@ -11,6 +11,26 @@ * the GNU General Public License */ +/* SC: Changes + -- packet retransmit mechanism (simplistic) + -- per endpoint/subchannel mgcp command sequencing. + -- better transaction handling + -- fixed some mem leaks + -- run-time configuration reload + -- distinguish CA and GW default MGCP ports + -- prevent clipping of DTMF tones in an established call + -- fixed a few crash scenarios in 3-way + -- fix for a few cases where asterisk and MGW end-up in conflicting ep states + -- enclose numeric IP in [] for outgoing requests +*/ + +/* SC: TODO + -- piggyback support + -- responseAck support + -- enhance retransmit mechanism (RTO calc. etc.) + -- embedded command support +*/ + #include <stdio.h> #include <pthread.h> #include <string.h> @@ -67,8 +87,11 @@ static char *type = "MGCP"; static char *tdesc = "Media Gateway Control Protocol (MGCP)"; static char *config = "mgcp.conf"; -#define DEFAULT_MGCP_PORT 2427/* From RFC 2705 */ +#define DEFAULT_MGCP_GW_PORT 2427/* From RFC 2705 */ +#define DEFAULT_MGCP_CA_PORT 2727/* From RFC 2705 */ #define MGCP_MAX_PACKET 1500 /* Also from RFC 2543, should sub headers tho */ +#define DEFAULT_RETRANS 1000 /* How frequently to retransmit */ +#define MAX_RETRANS 5 /* Try only 5 times for retransmissions */ /* MGCP rtp stream modes */ #define MGCP_CX_SENDONLY 0 @@ -87,6 +110,17 @@ static char *mgcp_cxmodes[] = { "inactive" }; +/* SC: MGCP commands */ +#define MGCP_CMD_EPCF 0 +#define MGCP_CMD_CRCX 1 +#define MGCP_CMD_MDCX 2 +#define MGCP_CMD_DLCX 3 +#define MGCP_CMD_RQNT 4 +#define MGCP_CMD_NTFY 5 +#define MGCP_CMD_AUEP 6 +#define MGCP_CMD_AUCX 7 +#define MGCP_CMD_RSIP 8 + static char context[AST_MAX_EXTENSION] = "default"; static char language[MAX_LANGUAGE] = ""; @@ -200,8 +234,12 @@ struct mgcp_request { int lines; /* SDP Content */ char *line[MGCP_MAX_LINES]; char data[MGCP_MAX_PACKET]; + int cmd; /* SC: int version of verb = command */ + int trid; /* SC: int version of identifier = transaction id */ + struct mgcp_request *next; /* SC: next in the queue */ }; +/* SC: obsolete static struct mgcp_pkt { int retrans; struct mgcp_endpoint *owner; @@ -209,9 +247,14 @@ static struct mgcp_pkt { char data[MGCP_MAX_PACKET]; struct mgcp_pkt *next; } *packets = NULL; +*/ /* MGCP message for queuing up */ struct mgcp_message { + struct mgcp_endpoint *owner_ep; + struct mgcp_subchannel *owner_sub; + int retrans; + unsigned long expire; unsigned int seqno; int len; struct mgcp_message *next; @@ -224,24 +267,38 @@ struct mgcp_message { #define SUB_ALT 1 struct mgcp_subchannel { + /* SC: subchannel magic string. + Needed to prove that any subchannel pointer passed by asterisk + really points to a valid subchannel memory area. + Ugly.. But serves the purpose for the time being. + */ +#define MGCP_SUBCHANNEL_MAGIC "!978!" + char magic[6]; ast_mutex_t lock; int id; struct ast_channel *owner; struct mgcp_endpoint *parent; struct ast_rtp *rtp; struct sockaddr_in tmpdest; - char txident[80]; + char txident[80]; /* FIXME SC: txident is replaced by rqnt_ident in endpoint. + This should be obsoleted */ char cxident[80]; char callid[80]; +/* SC: obsolete time_t lastouttime; int lastout; +*/ int cxmode; + struct mgcp_request *cx_queue; /* SC: pending CX commands */ + ast_mutex_t cx_queue_lock; /* SC: CX queue lock */ int nat; int iseq; /* Not used? RTP? */ int outgoing; int alreadygone; +/* SC: obsolete int messagepending; - struct mgcp_message *msgs; /* Message queue */ + struct mgcp_message *msgs; +*/ struct mgcp_subchannel *next; /* for out circular linked list */ }; @@ -290,6 +347,13 @@ struct mgcp_endpoint { int immediate; int hookstate; int adsi; + char rqnt_ident[80]; /* SC: request identifier */ + struct mgcp_request *rqnt_queue; /* SC: pending RQNT commands */ + ast_mutex_t rqnt_queue_lock; + struct mgcp_request *cmd_queue; /* SC: pending commands other than RQNT */ + ast_mutex_t cmd_queue_lock; + int delme; /* SC: needed for reload */ + int needaudit; /* SC: needed for reload */ struct ast_dsp *dsp; /* XXX Should there be a dsp/subchannel? XXX */ /* owner is tracked on the subchannels, and the *sub indicates whos in charge */ /* struct ast_channel *owner; */ @@ -303,6 +367,7 @@ struct mgcp_endpoint { static struct mgcp_gateway { /* A gateway containing one or more endpoints */ char name[80]; + int isnamedottedip; /* SC: is the name FQDN or dotted ip */ struct sockaddr_in addr; struct sockaddr_in defaddr; struct in_addr ourip; @@ -310,9 +375,21 @@ static struct mgcp_gateway { int expire; /* XXX Should we ever expire dynamic registrations? XXX */ struct mgcp_endpoint *endpoints; struct ast_ha *ha; +/* SC: obsolete + time_t lastouttime; + int lastout; + int messagepending; +*/ + struct mgcp_message *msgs; /* SC: gw msg queue */ + ast_mutex_t msgs_lock; /* SC: queue lock */ + int retransid; /* SC: retrans timer id */ + int delme; /* SC: needed for reload */ struct mgcp_gateway *next; } *gateways; +static ast_mutex_t mgcp_reload_lock = AST_MUTEX_INITIALIZER; +static int mgcp_reloading = 0; + static ast_mutex_t gatelock = AST_MUTEX_INITIALIZER; static int mgcpsock = -1; @@ -328,6 +405,11 @@ static int transmit_modify_with_sdp(struct mgcp_subchannel *sub, struct ast_rtp static int transmit_connection_del(struct mgcp_subchannel *sub); static int transmit_audit_endpoint(struct mgcp_endpoint *p); static void start_rtp(struct mgcp_subchannel *sub); +static void handle_response(struct mgcp_endpoint *p, struct mgcp_subchannel *sub, + int result, int ident, struct mgcp_request *resp); +static void dump_cmd_queues(struct mgcp_endpoint *p, struct mgcp_subchannel *sub); +static int mgcp_do_reload(void); +static int mgcp_reload(int fd, int argc, char *argv[]); static int has_voicemail(struct mgcp_endpoint *p) { @@ -357,17 +439,18 @@ static int unalloc_sub(struct mgcp_subchannel *sub) ast_rtp_destroy(sub->rtp); sub->rtp = NULL; } + dump_cmd_queues(NULL, sub); /* SC */ return 0; } -static int __mgcp_xmit(struct mgcp_subchannel *sub, char *data, int len) +/* SC: modified for new transport mechanism */ +static int __mgcp_xmit(struct mgcp_gateway *gw, char *data, int len) { - struct mgcp_endpoint *p = sub->parent; int res; - if (p->parent->addr.sin_addr.s_addr) - res=sendto(mgcpsock, data, len, 0, (struct sockaddr *)&p->parent->addr, sizeof(struct sockaddr_in)); + if (gw->addr.sin_addr.s_addr) + res=sendto(mgcpsock, data, len, 0, (struct sockaddr *)&gw->addr, sizeof(struct sockaddr_in)); else - res=sendto(mgcpsock, data, len, 0, (struct sockaddr *)&p->parent->defaddr, sizeof(struct sockaddr_in)); + res=sendto(mgcpsock, data, len, 0, (struct sockaddr *)&gw->defaddr, sizeof(struct sockaddr_in)); if (res != len) { ast_log(LOG_WARNING, "mgcp_xmit returned %d: %s\n", res, strerror(errno)); } @@ -381,77 +464,269 @@ static int send_response(struct mgcp_subchannel *sub, struct mgcp_request *req) if (mgcpdebug) { ast_verbose("Transmitting:\n%s\n to %s:%d\n", req->data, inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port)); } - res = __mgcp_xmit(sub, req->data, req->len); + res = __mgcp_xmit(p->parent, req->data, req->len); if (res > 0) res = 0; return res; } -static void dump_queue(struct mgcp_endpoint *p) +/* SC: modified for new transport framework */ +static void dump_queue(struct mgcp_gateway *gw, struct mgcp_endpoint *p) { - struct mgcp_message *cur; - struct mgcp_subchannel *sub = p->sub; - do { - while(sub->msgs) { - cur = sub->msgs; - sub->msgs = sub->msgs->next; - ast_log(LOG_NOTICE, "Removing message from %s@%s-%d tansaction %d\n", p->name, p->parent->name, sub->id, cur->seqno); - free(cur); + struct mgcp_message *cur, *q = NULL, *w, *prev; + + ast_mutex_lock(&gw->msgs_lock); + prev = NULL, cur = gw->msgs; + while (cur) { + if (!p || cur->owner_ep == p) { + if (prev) + prev->next = cur->next; + else + gw->msgs = cur->next; + + ast_log(LOG_NOTICE, "Removing message from %s tansaction %d\n", + gw->name, cur->seqno); + + w = cur; + cur = cur->next; + if (q) { + w->next = q; + } + else { + w->next = NULL; + } + q = w; } - sub->messagepending = 0; - sub->msgs = NULL; - sub = sub->next; - } while(sub != p->sub); + else { + prev = cur, cur=cur->next; + } + } + ast_mutex_unlock(&gw->msgs_lock); + + while (q) { + cur = q; + q = q->next; + free(cur); + } } -static int mgcp_postrequest(struct mgcp_subchannel *sub, unsigned char *data, int len, unsigned int seqno) +static int retrans_pkt(void *data) +{ + struct mgcp_gateway *gw = (struct mgcp_gateway *)data; + struct mgcp_message *cur, *exq = NULL, *w, *prev; + struct timeval tv; + unsigned long t; + int res = 0; + + if (gettimeofday(&tv, NULL) < 0) { + /* This shouldn't ever happen, but let's be sure */ + ast_log(LOG_NOTICE, "gettimeofday() failed!\n"); + return 0; + } + + t = tv.tv_sec * 1000 + tv.tv_usec / 1000; + + /* find out expired msgs */ + ast_mutex_lock(&gw->msgs_lock); + + prev = NULL, cur = gw->msgs; + while (cur) { + if (cur->retrans < MAX_RETRANS) { + cur->retrans++; + if (mgcpdebug) { + ast_verbose("Retransmitting #%d transaction %d on [%s]\n", cur->retrans, cur->seqno, gw->name); + } + __mgcp_xmit(gw, cur->buf, cur->len); + + prev = cur; + cur = cur->next; + } + else { + if (prev) + prev->next = cur->next; + else + gw->msgs = cur->next; + + ast_log(LOG_WARNING, "Maximum retries exceeded for transaction %d on [%s]\n", cur->seqno, gw->name); + + w = cur; + cur = cur->next; + + if (exq) { + w->next = exq; + } + else { + w->next = NULL; + } + exq = w; + } + } + + if (!gw->msgs) { + gw->retransid = -1; + res = 0; + } + else { + res = 1; + } + ast_mutex_unlock(&gw->msgs_lock); + + while (exq) { + cur = exq; + /* time-out transaction */ + handle_response(cur->owner_ep, cur->owner_sub, 406, cur->seqno, NULL); + exq = exq->next; + free(cur); + } + + return res; +} + +/* SC: modified for the new transaction mechanism */ +static int mgcp_postrequest(struct mgcp_endpoint *p, struct mgcp_subchannel *sub, + unsigned char *data, int len, unsigned int seqno) { struct mgcp_message *msg = malloc(sizeof(struct mgcp_message) + len); struct mgcp_message *cur; - time_t t; + struct mgcp_gateway *gw = ((p && p->parent) ? p->parent : NULL); + struct timeval tv; + if (!msg) { return -1; } + if (!gw) { + return -1; + } +/* SC time(&t); - if (sub->messagepending && (sub->lastouttime + 20 < t)) { + if (gw->messagepending && (gw->lastouttime + 20 < t)) { ast_log(LOG_NOTICE, "Timeout waiting for response to message:%d, lastouttime: %ld, now: %ld. Dumping pending queue\n", - sub->msgs ? sub->msgs->seqno : -1, (long) sub->lastouttime, (long) t); + gw->msgs ? gw->msgs->seqno : -1, (long) gw->lastouttime, (long) t); dump_queue(sub->parent); } +*/ + msg->owner_sub = sub; + msg->owner_ep = p; msg->seqno = seqno; msg->next = NULL; msg->len = len; + msg->retrans = 0; memcpy(msg->buf, data, msg->len); - cur = sub->msgs; + + ast_mutex_lock(&gw->msgs_lock); + cur = gw->msgs; if (cur) { while(cur->next) cur = cur->next; cur->next = msg; } else { - sub->msgs = msg; + gw->msgs = msg; } - if (!sub->messagepending) { - sub->messagepending = 1; - sub->lastout = seqno; - sub->lastouttime = t; - __mgcp_xmit(sub, msg->buf, msg->len); + + if (gettimeofday(&tv, NULL) < 0) { + /* This shouldn't ever happen, but let's be sure */ + ast_log(LOG_NOTICE, "gettimeofday() failed!\n"); + } + else { + msg->expire = tv.tv_sec * 1000 + tv.tv_usec / 1000 + DEFAULT_RETRANS; + + if (gw->retransid == -1) + gw->retransid = ast_sched_add(sched, DEFAULT_RETRANS, retrans_pkt, (void *)gw); + } + ast_mutex_unlock(&gw->msgs_lock); +/* SC + if (!gw->messagepending) { + gw->messagepending = 1; + gw->lastout = seqno; + gw->lastouttime = t; +*/ + __mgcp_xmit(gw, msg->buf, msg->len); /* XXX Should schedule retransmission XXX */ +/* SC } else ast_log(LOG_DEBUG, "Deferring transmission of transaction %d\n", seqno); +*/ return 0; } -static int send_request(struct mgcp_subchannel *sub, struct mgcp_request *req, unsigned int seqno) +/* SC: modified for new transport */ +static int send_request(struct mgcp_endpoint *p, struct mgcp_subchannel *sub, + struct mgcp_request *req, unsigned int seqno) { - int res; - struct mgcp_endpoint *p = sub->parent; - if (mgcpdebug) { - ast_verbose("Posting Request:\n%s to %s:%d\n", req->data, - inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port)); + int res = 0; + struct mgcp_request **queue, *q, *r, *t; + ast_mutex_t *l; + + switch (req->cmd) { + case MGCP_CMD_DLCX: + queue = &sub->cx_queue; + l = &sub->cx_queue_lock; + ast_mutex_lock(l); + q = sub->cx_queue; + /* delete pending cx cmds */ + while (q) { + r = q->next; + free(q); + q = r; + } + *queue = NULL; + break; + + case MGCP_CMD_CRCX: + case MGCP_CMD_MDCX: + queue = &sub->cx_queue; + l = &sub->cx_queue_lock; + ast_mutex_lock(l); + break; + + case MGCP_CMD_RQNT: + queue = &p->rqnt_queue; + l = &p->rqnt_queue_lock; + ast_mutex_lock(l); + break; + + default: + queue = &p->cmd_queue; + l = &p->cmd_queue_lock; + ast_mutex_lock(l); + break; } - - res = mgcp_postrequest(sub, req->data, req->len, seqno); - return res; + + r = (struct mgcp_request *) malloc (sizeof(struct mgcp_request)); + if (!r) { + ast_log(LOG_WARNING, "Cannot post MGCP request: insufficient memory\n"); + ast_mutex_unlock(l); + return -1; + } + memcpy(r, req, sizeof(struct mgcp_request)); + + if (!(*queue)) { + if (mgcpdebug) { + ast_verbose("Posting Request:\n%s to %s:%d\n", req->data, + inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port)); + } + + res = mgcp_postrequest(p, sub, req->data, req->len, seqno); + } + else { + if (mgcpdebug) { + ast_verbose("Queueing Request:\n%s to %s:%d\n", req->data, + inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port)); + } + } + + /* XXX SC: find tail. We could also keep tail in the data struct for faster access */ + for (t = *queue; t && t->next; t = t->next); + + r->next = NULL; + if (t) + t->next = r; + else + *queue = r; + + ast_mutex_unlock(l); + + return res; } static int mgcp_call(struct ast_channel *ast, char *dest, int timeout) @@ -520,18 +795,31 @@ static int mgcp_hangup(struct ast_channel *ast) struct mgcp_subchannel *sub = ast->pvt->pvt; struct mgcp_endpoint *p = sub->parent; - if (mgcpdebug) { - ast_verbose(VERBOSE_PREFIX_3 "MGCP mgcp_hangup(%s) on %s@%s\n", ast->name, p->name, p->parent->name); - } if (option_debug) ast_log(LOG_DEBUG, "mgcp_hangup(%s)\n", ast->name); if (!ast->pvt->pvt) { ast_log(LOG_DEBUG, "Asked to hangup channel not connected\n"); return 0; } + if (strcmp(sub->magic, MGCP_SUBCHANNEL_MAGIC)) { + ast_log(LOG_DEBUG, "Invalid magic. MGCP subchannel freed up already.\n"); + return 0; + } + if (mgcpdebug) { + ast_verbose(VERBOSE_PREFIX_3 "MGCP mgcp_hangup(%s) on %s@%s\n", ast->name, p->name, p->parent->name); + } + if ((p->dtmfinband) && (p->dsp != NULL)){ - ast_dsp_free(p->dsp); - } + /* SC: check whether other channel is active. */ + if (!sub->next->owner) + { + if (mgcpdebug) { + ast_verbose(VERBOSE_PREFIX_2 "MGCP free dsp on %s@%s\n", p->name, p->parent->name); + } + ast_dsp_free(p->dsp); + p->dsp = NULL; + } + } ast_mutex_lock(&sub->lock); sub->owner = NULL; @@ -574,6 +862,13 @@ static int mgcp_hangup(struct ast_channel *ast) sub->rtp = NULL; } + /* SC: Decrement use count */ + ast_mutex_lock(&usecnt_lock); + usecnt--; + ast_mutex_unlock(&usecnt_lock); + ast_update_use_count(); + /* SC: Decrement use count */ + if ((p->hookstate == MGCP_ONHOOK) && (!sub->next->rtp)) { if (has_voicemail(p)) { if (mgcpdebug) { @@ -695,7 +990,10 @@ static int mgcp_answer(struct ast_channel *ast) } else { transmit_modify_request(sub); } - ast_verbose(VERBOSE_PREFIX_3 "MGCP mgcp_answer(%s) on %s@%s-%d\n", ast->name, p->name, p->parent->name, sub->id); + /* SC: verbose level check */ + if (option_verbose > 2) { + ast_verbose(VERBOSE_PREFIX_3 "MGCP mgcp_answer(%s) on %s@%s-%d\n", ast->name, p->name, p->parent->name, sub->id); + } if (ast->_state != AST_STATE_UP) { ast_setstate(ast, AST_STATE_UP); if (option_debug) @@ -871,6 +1169,8 @@ static struct ast_channel *mgcp_new(struct mgcp_subchannel *sub, int state) if (i->dtmfinband) { i->dsp = ast_dsp_new(); ast_dsp_set_features(i->dsp,DSP_FEATURE_DTMF_DETECT); + /* SC: this is to prevent clipping of dtmf tones during dsp processing */ + ast_dsp_digitmode(i->dsp, DSP_DIGITMODE_NOQUELCH); } else { i->dsp = NULL; } @@ -919,7 +1219,10 @@ static struct ast_channel *mgcp_new(struct mgcp_subchannel *sub, int state) tmp = NULL; } } - ast_verbose(VERBOSE_PREFIX_3 "MGCP mgcp_new(%s) created in state: %s\n", tmp->name, ast_state2str(state)); + /* SC: verbose level check */ + if (option_verbose > 2) { + ast_verbose(VERBOSE_PREFIX_3 "MGCP mgcp_new(%s) created in state: %s\n", tmp->name, ast_state2str(state)); + } } else { ast_log(LOG_WARNING, "Unable to allocate channel structure\n"); } @@ -1060,11 +1363,32 @@ static struct mgcp_subchannel *find_subchannel(char *name, int msgid, struct soc ast_verbose(VERBOSE_PREFIX_3 "Registered MGCP gateway '%s' at %s port %d\n", g->name, inet_ntoa(g->addr.sin_addr), ntohs(g->addr.sin_port)); } } + /* SC: not dynamic, check if the name matches */ + else if (name) { + if (strcasecmp(g->name, at)) { + g = g->next; + continue; + } + } + /* SC: not dynamic, no name, check if the addr matches */ + else if (!name && sin) { + if ((g->addr.sin_addr.s_addr != sin->sin_addr.s_addr) || + (g->addr.sin_port != sin->sin_port)) { + g = g->next; + continue; + } + } + else { + g = g->next; + continue; + } + /* SC */ p = g->endpoints; while(p) { if (option_debug) ast_log(LOG_DEBUG, "Searching on %s@%s for subchannel\n", p->name, g->name); if (msgid) { +#if 0 /* SC: new transport mech */ sub = p->sub; do { if (option_debug) @@ -1080,6 +1404,12 @@ static struct mgcp_subchannel *find_subchannel(char *name, int msgid, struct soc if (found) { break; } +#endif + /* SC */ + sub = p->sub; + found = 1; + /* SC */ + break; } else if (name && !strcasecmp(p->name, tmp)) { ast_log(LOG_DEBUG, "Coundn't determine subchannel, assuming current master %s@%s-%d\n", p->name, g->name, p->sub->id); @@ -1364,7 +1694,11 @@ static int init_req(struct mgcp_endpoint *p, struct mgcp_request *req, char *ver return -1; } req->header[req->headers] = req->data + req->len; - snprintf(req->header[req->headers], sizeof(req->data) - req->len, "%s %d %s@%s MGCP 1.0\r\n", verb, oseq, p->name, p->parent->name); + /* SC: check if we need brackets around the gw name */ + if (p->parent->isnamedottedip) + snprintf(req->header[req->headers], sizeof(req->data) - req->len, "%s %d %s@[%s] MGCP 1.0\r\n", verb, oseq, p->name, p->parent->name); + else + snprintf(req->header[req->headers], sizeof(req->data) - req->len, "%s %d %s@%s MGCP 1.0\r\n", verb, oseq, p->name, p->parent->name); req->len += strlen(req->header[req->headers]); if (req->headers < MGCP_MAX_HEADERS) req->headers++; @@ -1514,11 +1848,15 @@ static int transmit_modify_with_sdp(struct mgcp_subchannel *sub, struct ast_rtp add_header(&resp, "C", sub->callid); add_header(&resp, "L", local); add_header(&resp, "M", mgcp_cxmodes[sub->cxmode]); + /* SC: X header should not be sent. kept for compatibility */ add_header(&resp, "X", sub->txident); add_header(&resp, "I", sub->cxident); /*add_header(&resp, "S", "");*/ add_sdp(&resp, sub, rtp); - return send_request(sub, &resp, oseq); + /* SC: fill in new fields */ + resp.cmd = MGCP_CMD_MDCX; + resp.trid = oseq; + return send_request(p, sub, &resp, oseq); /* SC */ } static int transmit_connect_with_sdp(struct mgcp_subchannel *sub, struct ast_rtp *rtp) @@ -1544,10 +1882,14 @@ static int transmit_connect_with_sdp(struct mgcp_subchannel *sub, struct ast_rtp add_header(&resp, "C", sub->callid); add_header(&resp, "L", local); add_header(&resp, "M", mgcp_cxmodes[sub->cxmode]); + /* SC: X header should not be sent. kept for compatibility */ add_header(&resp, "X", sub->txident); /*add_header(&resp, "S", "");*/ add_sdp(&resp, sub, rtp); - return send_request(sub, &resp, oseq); + /* SC: fill in new fields */ + resp.cmd = MGCP_CMD_CRCX; + resp.trid = oseq; + return send_request(p, sub, &resp, oseq); /* SC */ } static int transmit_notify_request(struct mgcp_subchannel *sub, char *tone) @@ -1561,7 +1903,7 @@ static int transmit_notify_request(struct mgcp_subchannel *sub, char *tone) } strncpy(p->curtone, tone, sizeof(p->curtone) - 1); reqprep(&resp, p, "RQNT"); - add_header(&resp, "X", sub->txident); + add_header(&resp, "X", p->rqnt_ident); /* SC */ switch (p->hookstate) { case MGCP_ONHOOK: add_header(&resp, "R", "hd(N)"); @@ -1573,7 +1915,10 @@ static int transmit_notify_request(struct mgcp_subchannel *sub, char *tone) if (strlen(tone)) { add_header(&resp, "S", tone); } - return send_request(sub, &resp, oseq); + /* SC: fill in new fields */ + resp.cmd = MGCP_CMD_RQNT; + resp.trid = oseq; + return send_request(p, NULL, &resp, oseq); /* SC */ } static int transmit_notify_request_with_callerid(struct mgcp_subchannel *sub, char *tone, char *callerid) @@ -1601,7 +1946,7 @@ static int transmit_notify_request_with_callerid(struct mgcp_subchannel *sub, ch } } if (!n) - n = "O"; + n = ""; if (!l) l = ""; @@ -1612,7 +1957,7 @@ static int transmit_notify_request_with_callerid(struct mgcp_subchannel *sub, ch tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, l, n); strncpy(p->curtone, tone, sizeof(p->curtone) - 1); reqprep(&resp, p, "RQNT"); - add_header(&resp, "X", sub->txident); + add_header(&resp, "X", p->rqnt_ident); /* SC */ switch (p->hookstate) { case MGCP_ONHOOK: add_header(&resp, "R", "L/hd(N)"); @@ -1628,7 +1973,10 @@ static int transmit_notify_request_with_callerid(struct mgcp_subchannel *sub, ch ast_verbose(VERBOSE_PREFIX_3 "MGCP Asked to indicate tone: %s on %s@%s-%d in cxmode: %s\n", tone2, p->name, p->parent->name, sub->id, mgcp_cxmodes[sub->cxmode]); } - return send_request(sub, &resp, oseq); + /* SC: fill in new fields */ + resp.cmd = MGCP_CMD_RQNT; + resp.trid = oseq; + return send_request(p, NULL, &resp, oseq); /* SC */ } static int transmit_modify_request(struct mgcp_subchannel *sub) @@ -1647,6 +1995,7 @@ static int transmit_modify_request(struct mgcp_subchannel *sub) reqprep(&resp, p, "MDCX"); add_header(&resp, "C", sub->callid); add_header(&resp, "M", mgcp_cxmodes[sub->cxmode]); + /* SC: X header should not be sent. kept for compatibility */ add_header(&resp, "X", sub->txident); add_header(&resp, "I", sub->cxident); switch (sub->parent->hookstate) { @@ -1657,7 +2006,10 @@ static int transmit_modify_request(struct mgcp_subchannel *sub) add_header(&resp, "R", "L/hu(N),L/hf(N),D/[0-9#*](N)"); break; } - return send_request(sub, &resp, oseq); + /* SC: fill in new fields */ + resp.cmd = MGCP_CMD_MDCX; + resp.trid = oseq; + return send_request(p, sub, &resp, oseq); /* SC */ } @@ -1665,8 +2017,12 @@ static int transmit_audit_endpoint(struct mgcp_endpoint *p) { struct mgcp_request resp; reqprep(&resp, p, "AUEP"); - add_header(&resp, "F", "A,R,D,S,X,N,I,T,O,ES,VS,E,MD,M"); - return send_request(p->sub, &resp, oseq); + /* SC: removed unknown param VS */ + add_header(&resp, "F", "A,R,D,S,X,N,I,T,O,ES,E,MD,M"); + /* SC: fill in new fields */ + resp.cmd = MGCP_CMD_AUEP; + resp.trid = oseq; + return send_request(p, NULL, &resp, oseq); /* SC */ } static int transmit_connection_del(struct mgcp_subchannel *sub) @@ -1678,45 +2034,113 @@ static int transmit_connection_del(struct mgcp_subchannel *sub) sub->cxident, p->name, p->parent->name, sub->id, mgcp_cxmodes[sub->cxmode], sub->callid); } reqprep(&resp, p, "DLCX"); - add_header(&resp, "C", sub->callid); + /* SC: check if call id is avail */ + if (sub->callid[0]) + add_header(&resp, "C", sub->callid); + /* SC: X header should not be sent. kept for compatibility */ add_header(&resp, "X", sub->txident); - add_header(&resp, "I", sub->cxident); - return send_request(sub, &resp, oseq); + /* SC: check if cxident is avail */ + if (sub->cxident[0]) + add_header(&resp, "I", sub->cxident); + /* SC: fill in new fields */ + resp.cmd = MGCP_CMD_DLCX; + resp.trid = oseq; + return send_request(p, sub, &resp, oseq); /* SC */ } -static void handle_response(struct mgcp_subchannel *sub, int result, int ident) +/* SC: cleanup pendng commands */ +static void dump_cmd_queues(struct mgcp_endpoint *p, struct mgcp_subchannel *sub) { - struct mgcp_message *cur; - struct mgcp_endpoint *p = sub->parent; - time_t t; - time(&t); -#if 0 - ast_verbose(VERBOSE_PREFIX_3 "Got response back on sub%d for transaction %d our last send was %d)\n", - sub->id, ident, sub->msgs ? sub->msgs->seqno : -1); - ast_verbose(VERBOSE_PREFIX_3 "Here's out outher chan sub%d for transaction %d our last send was %d)\n", - sub->next->id, ident, sub->next->msgs ? sub->next->msgs->seqno : -1); -#endif - if (sub->msgs && (sub->msgs->seqno == ident)) { - ast_log(LOG_DEBUG, "Got response back on tansaction %d\n", ident); - cur = sub->msgs; - sub->msgs = sub->msgs->next; - ast_log(LOG_DEBUG, "Removing message from %s@%s-%d tansaction %d\n", sub->parent->name, sub->parent->parent->name, sub->id, ident); - free(cur); - if (sub->msgs) { - /* Send next pending message if appropriate */ - sub->messagepending = 1; - sub->lastout = sub->msgs->seqno; - sub->lastouttime = t; - __mgcp_xmit(sub, sub->msgs->buf, sub->msgs->len); - /* XXX Should schedule retransmission XXX */ - } else { - sub->messagepending = 0; + struct mgcp_request *t, *q; + + if (p) { + ast_mutex_lock(&p->rqnt_queue_lock); + for (q = p->rqnt_queue; q; t = q->next, free(q), q=t); + p->rqnt_queue = NULL; + ast_mutex_unlock(&p->rqnt_queue_lock); + + ast_mutex_lock(&p->cmd_queue_lock); + for (q = p->cmd_queue; q; t = q->next, free(q), q=t); + p->cmd_queue = NULL; + ast_mutex_unlock(&p->cmd_queue_lock); + + ast_mutex_lock(&p->sub->cx_queue_lock); + for (q = p->sub->cx_queue; q; t = q->next, free(q), q=t); + p->sub->cx_queue = NULL; + ast_mutex_unlock(&p->sub->cx_queue_lock); + + ast_mutex_lock(&p->sub->next->cx_queue_lock); + for (q = p->sub->next->cx_queue; q; t = q->next, free(q), q=t); + p->sub->next->cx_queue = NULL; + ast_mutex_unlock(&p->sub->next->cx_queue_lock); + } + else if (sub){ + ast_mutex_lock(&sub->cx_queue_lock); + for (q = sub->cx_queue; q; t = q->next, free(q), q=t); + sub->cx_queue = NULL; + ast_mutex_unlock(&sub->cx_queue_lock); + } +} + + +/* SC: remove command transaction from queue */ +static struct mgcp_request *find_command(struct mgcp_endpoint *p, struct mgcp_subchannel *sub, + struct mgcp_request **queue, ast_mutex_t *l, int ident) +{ + struct mgcp_request *prev, *req; + + ast_mutex_lock(l); + for (prev = NULL, req = *queue; req; prev = req, req = req->next) { + if (req->trid == ident) { + /* remove from queue */ + if (!prev) + *queue = req->next; + else + prev->next = req->next; + + /* send next pending command */ + if (*queue) { + if (mgcpdebug) { + ast_verbose("Posting Queued Request:\n%s to %s:%d\n", (*queue)->data, + inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port)); + } + + mgcp_postrequest(p, sub, (*queue)->data, (*queue)->len, (*queue)->trid); + } + break; } - } else { - ast_log(LOG_NOTICE, "Got response back on %s@%s-%d for transaction %d we aren't sending? (current = %d)\n", - sub->parent->name, sub->parent->parent->name, sub->id, ident, sub->msgs ? sub->msgs->seqno : -1); - } - if ((result >= 400) && (result <= 499)) { + } + ast_mutex_unlock(l); + return req; +} + +/* SC: modified for new transport mechanism */ +static void handle_response(struct mgcp_endpoint *p, struct mgcp_subchannel *sub, + int result, int ident, struct mgcp_request *resp) +{ + char *c; + struct mgcp_request *req; + struct mgcp_gateway *gw = p->parent; + + if (result < 200) { + /* provisional response */ + return; + } + + if (sub) + req = find_command(p, sub, &sub->cx_queue, &sub->cx_queue_lock, ident); + else if (!(req = find_command(p, sub, &p->rqnt_queue, &p->rqnt_queue_lock, ident))) + req = find_command(p, sub, &p->cmd_queue, &p->cmd_queue_lock, ident); + + if (!req) { + if (option_verbose > 2) { + ast_verbose(VERBOSE_PREFIX_3 "No command found on [%s] for transaction %d. Ignoring...\n", + gw->name, ident); + } + return; + } + + if (p && (result >= 400) && (result <= 599)) { switch (result) { case 401: p->hookstate = MGCP_OFFHOOK; @@ -1724,16 +2148,124 @@ static void handle_response(struct mgcp_subchannel *sub, int result, int ident) case 402: p->hookstate = MGCP_ONHOOK; break; + case 406: + ast_log(LOG_NOTICE, "Transaction %d timed out\n", ident); + break; + case 407: + ast_log(LOG_NOTICE, "Transaction %d aborted\n", ident); + break; } - ast_log(LOG_NOTICE, "Terminating on result %d from %s@%s-%d\n", result, p->name, p->parent->name, sub->id); - if (sub->owner) - ast_softhangup(sub->owner, AST_SOFTHANGUP_DEV); - } + if (sub) + { + if (sub->owner) { + ast_log(LOG_NOTICE, "Terminating on result %d from %s@%s-%d\n", + result, p->name, p->parent->name, sub ? sub->id:-1); + ast_softhangup(sub->owner, AST_SOFTHANGUP_DEV); + } + } + else { + if (p->sub->next->owner) { + ast_log(LOG_NOTICE, "Terminating on result %d from %s@%s-%d\n", + result, p->name, p->parent->name, sub ? sub->id:-1); + ast_softhangup(p->sub->next->owner, AST_SOFTHANGUP_DEV); + } + + if (p->sub->owner) { + ast_log(LOG_NOTICE, "Terminating on result %d from %s@%s-%d\n", + result, p->name, p->parent->name, sub ? sub->id:-1); + ast_softhangup(p->sub->owner, AST_SOFTHANGUP_DEV); + } + + dump_cmd_queues(p, NULL); + } + } + + if (resp) { + if (req->cmd == MGCP_CMD_CRCX) { + if ((c = get_header(resp, "I"))) { + if (strlen(c)) { + /* SC: if we are hanging up do not process this conn. */ + if (sub->owner) { + if (strlen(sub->cxident)) { + if (strcasecmp(c, sub->cxident)) { + ast_log(LOG_WARNING, "Subchannel already has a cxident. sub->cxident: %s requested %s\n", sub->cxident, c); + } + } + strncpy(sub->cxident, c, sizeof(sub->cxident) - 1); + if (sub->tmpdest.sin_addr.s_addr) { + transmit_modify_with_sdp(sub, NULL); + } + } + else { + /* XXX SC: delete this one + callid and conn id may already be lost. + so the following del conn may have a side effect of + cleaning up the next subchannel */ + transmit_connection_del(sub); + } + } + } + } + + if (req->cmd == MGCP_CMD_AUEP) { + /* Try to determine the hookstate returned from an audit endpoint command */ + if ((c = get_header(resp, "ES"))) { + if (strlen(c)) { + if (strstr(c, "hu")) { + if (p->hookstate != MGCP_ONHOOK) { + /* SC: XXX cleanup if we think we are offhook XXX */ + if ((p->sub->owner || p->sub->next->owner ) && + p->hookstate == MGCP_OFFHOOK) + ast_softhangup(sub->owner, AST_SOFTHANGUP_DEV); + p->hookstate = MGCP_ONHOOK; + + /* SC: update the requested events according to the new hookstate */ + transmit_notify_request(p->sub, ""); + + /* SC: verbose level check */ + if (option_verbose > 2) { + ast_verbose(VERBOSE_PREFIX_3 "Setting hookstate of %s@%s to ONHOOK\n", p->name, gw->name); + } + } + } else if (strstr(c, "hd")) { + if (p->hookstate != MGCP_OFFHOOK) { + p->hookstate = MGCP_OFFHOOK; + + /* SC: update the requested events according to the new hookstate */ + transmit_notify_request(p->sub, ""); + + /* SC: verbose level check */ + if (option_verbose > 2) { + ast_verbose(VERBOSE_PREFIX_3 "Setting hookstate of %s@%s to OFFHOOK\n", p->name, gw->name); + } + } + } + } + } + } + + if (resp && resp->lines) { + /* SC: do not process sdp if we are hanging up. this may be a late response */ + if (sub && sub->owner) { + if (!sub->rtp) + start_rtp(sub); + if (sub->rtp) + process_sdp(sub, resp); + } + } + } + + free(req); } static void start_rtp(struct mgcp_subchannel *sub) { ast_mutex_lock(&sub->lock); + /* SC: check again to be on the safe side */ + if (sub->rtp) { + ast_rtp_destroy(sub->rtp); + sub->rtp = NULL; + } /* Allocate the RTP now */ sub->rtp = ast_rtp_new(sched, io, 1, 0); if (sub->rtp && sub->owner) @@ -1809,8 +2341,14 @@ static void *mgcp_ss(void *data) ast_indicate(chan, -1); strncpy(chan->exten, exten, sizeof(chan->exten)-1); if (strlen(p->callerid)) { - if (!p->hidecallerid) + if (!p->hidecallerid) { + /* SC: free existing chan->callerid */ + if (chan->callerid) + free(chan->callerid); chan->callerid = strdup(p->callerid); + } + if (chan->ani) + free(chan->ani); chan->ani = strdup(p->callerid); } ast_setstate(chan, AST_STATE_RING); @@ -2166,7 +2704,8 @@ static int handle_request(struct mgcp_subchannel *sub, struct mgcp_request *req, ast_verbose(VERBOSE_PREFIX_3 "Received keepalive request from %s@%s\n", p->name, p->parent->name); transmit_response(sub, "200", req, "OK"); } else { - dump_queue(p); + dump_queue(p->parent, p); + dump_cmd_queues(p, NULL); if (option_verbose > 2) { ast_verbose(VERBOSE_PREFIX_3 "Resetting interface %s@%s\n", p->name, p->parent->name); } @@ -2175,6 +2714,10 @@ static int handle_request(struct mgcp_subchannel *sub, struct mgcp_request *req, } transmit_response(sub, "200", req, "OK"); transmit_notify_request(sub, ""); + /* SC: Audit endpoint. + Idea is to prevent lost lines due to race conditions + */ + transmit_audit_endpoint(p); } } else if (!strcasecmp(req->verb, "NTFY")) { /* Acknowledge and be sure we keep looking for the same things */ @@ -2318,8 +2861,11 @@ static int handle_request(struct mgcp_subchannel *sub, struct mgcp_request *req, sub->alreadygone = 1; ast_queue_hangup(sub->owner, 1); } else { - ast_verbose(VERBOSE_PREFIX_3 "MGCP handle_request(%s@%s-%d) ast_channel already destroyed\n", - p->name, p->parent->name, sub->id); + /* SC: verbose level check */ + if (option_verbose > 2) { + ast_verbose(VERBOSE_PREFIX_3 "MGCP handle_request(%s@%s-%d) ast_channel already destroyed\n", + p->name, p->parent->name, sub->id); + } } } if ((p->hookstate == MGCP_ONHOOK) && (!sub->rtp) && (!sub->next->rtp)) { @@ -2352,7 +2898,8 @@ static int handle_request(struct mgcp_subchannel *sub, struct mgcp_request *req, if (strstr(p->curtone, "wt") && (ev[0] == 'A')) { memset(p->curtone, 0, sizeof(p->curtone)); } - } else if (!strcasecmp(ev, "T")) { + } + else if (!strcasecmp(ev, "T")) { /* Digit timeout -- unimportant */ } else { ast_log(LOG_NOTICE, "Received unknown event '%s' from %s@%s\n", ev, p->name, p->parent->name); @@ -2369,7 +2916,6 @@ static int mgcpsock_read(int *id, int fd, short events, void *ignore) struct mgcp_request req; struct sockaddr_in sin; struct mgcp_subchannel *sub; - char *c; int res; int len; int result; @@ -2402,50 +2948,37 @@ static int mgcpsock_read(int *id, int fd, short events, void *ignore) /* Try to find who this message is for, if it's important */ sub = find_subchannel(NULL, ident, &sin); if (sub) { -#if 0 - if ((c = get_header(&req, "X"))) { - if (strlen(c)) { - if ((strcasecmp(sub->txident, c)) && (!strcasecmp(sub->next->txident, c))) { - ast_log(LOG_WARNING, "Response on sub%d message %d appears to have come from out other subchannel", sub->id, ident); - } + struct mgcp_gateway *gw = sub->parent->parent; + struct mgcp_message *cur, *prev; + + ast_mutex_lock(&gw->msgs_lock); + for (prev = NULL, cur = gw->msgs; cur; prev = cur, cur = cur->next) { + if (cur->seqno == ident) { + ast_log(LOG_DEBUG, "Got response back on tansaction %d\n", ident); + if (prev) + prev->next = cur->next; + else + gw->msgs = cur->next; + break; } } -#endif - handle_response(sub, result, ident); - if ((c = get_header(&req, "I"))) { - if (strlen(c)) { - if (strlen(sub->cxident)) { - if (strcasecmp(c, sub->cxident)) { - ast_log(LOG_WARNING, "Subchannel already has a cxident. sub->cxident: %s requested %s\n", sub->cxident, c); - } - } - strncpy(sub->cxident, c, sizeof(sub->cxident) - 1); - if (sub->tmpdest.sin_addr.s_addr) { - transmit_modify_with_sdp(sub, NULL); - } - } - } - /* Try to determine the hookstate returned from an audit endpoint command */ - if ((c = get_header(&req, "ES"))) { - if (strlen(c)) { - if (strstr(c, "hu")) { - sub->parent->hookstate = MGCP_ONHOOK; - ast_verbose(VERBOSE_PREFIX_3 "Setting hookstate of %s@%s to ONHOOK\n", sub->parent->name, sub->parent->parent->name); - } else if (strstr(c, "hd")) { - sub->parent->hookstate = MGCP_OFFHOOK; - ast_verbose(VERBOSE_PREFIX_3 "Setting hookstate of %s@%s to OFFHOOK\n", sub->parent->name, sub->parent->parent->name); - } - } - } + /* stop retrans timer if the queue is empty */ + if (!gw->msgs && (gw->retransid != -1)) { + ast_sched_del(sched, gw->retransid); + gw->retransid = -1; + } - if (req.lines) { - if (!sub->rtp) - start_rtp(sub); - if (sub->rtp) - process_sdp(sub, &req); - } + ast_mutex_unlock(&gw->msgs_lock); + + if (cur) { + handle_response(cur->owner_ep, cur->owner_sub, result, ident, &req); + free(cur); + return 1; + } + ast_log(LOG_NOTICE, "Got response back on [%s] for transaction %d we aren't sending?\n", + gw->name, ident); } } else { if (!req.endpoint || !strlen(req.endpoint) || @@ -2467,22 +3000,11 @@ static int mgcpsock_read(int *id, int fd, short events, void *ignore) static void *do_monitor(void *data) { int res; - struct mgcp_pkt *p; + int reloading; /* struct mgcp_gateway *g; */ /* struct mgcp_endpoint *e; */ /*time_t thispass = 0, lastpass = 0;*/ - sched = sched_context_create(); - if (!sched) { - ast_log(LOG_WARNING, "Unable to create schedule context\n"); - return NULL; - } - io = io_context_create(); - if (!io) { - ast_log(LOG_WARNING, "Unable to create I/O context\n"); - return NULL; - } - /* Add an I/O event to our UDP socket */ if (mgcpsock > -1) ast_io_add(io, mgcpsock, mgcpsock_read, AST_IO_IN, NULL); @@ -2491,17 +3013,23 @@ static void *do_monitor(void *data) (and thus do not have a separate thread) indefinitely */ /* From here on out, we die whenever asked */ for(;;) { - /* Check for interfaces needing to be killed */ + /* Check for a reload request */ + ast_mutex_lock(&mgcp_reload_lock); + reloading = mgcp_reloading; + mgcp_reloading = 0; + ast_mutex_unlock(&mgcp_reload_lock); + if (reloading) { + if (option_verbose > 0) + ast_verbose(VERBOSE_PREFIX_1 "Reloading MGCP\n"); + mgcp_do_reload(); + } + + /* Check for interfaces needing to be killed */ /* Don't let anybody kill us right away. Nobody should lock the interface list and wait for the monitor list, but the other way around is okay. */ ast_mutex_lock(&monlock); /* Lock the network interface */ ast_mutex_lock(&netlock); - p = packets; - while(p) { - /* Handle any retransmissions */ - p = p->next; - } /* XXX THIS IS COMPLETELY HOSED */ /* The gateway goes into a state of panic */ @@ -2540,6 +3068,9 @@ static void *do_monitor(void *data) pthread_testcancel(); /* Wait for sched or io */ res = ast_sched_wait(sched); + /* SC: copied from chan_sip.c */ + if ((res < 0) || (res > 1000)) + res = 1000; res = ast_io_wait(io, res); ast_mutex_lock(&monlock); if (res >= 0) @@ -2634,6 +3165,7 @@ static struct ast_channel *mgcp_request(char *type, int format, void *data) return tmpc; } +/* SC: modified for reload support */ static struct mgcp_gateway *build_gateway(char *cat, struct ast_variable *v) { struct mgcp_gateway *gw; @@ -2641,12 +3173,36 @@ static struct mgcp_gateway *build_gateway(char *cat, struct ast_variable *v) struct mgcp_subchannel *sub; /*char txident[80];*/ int i=0, y=0; + int gw_reload = 0; + int ep_reload = 0; canreinvite = CANREINVITE; - gw = malloc(sizeof(struct mgcp_gateway)); + + /* SC: locate existing gateway */ + gw = gateways; + while (gw) { + if (!strcasecmp(cat, gw->name)) { + /* gateway already exists */ + gw->delme = 0; + gw_reload = 1; + break; + } + gw = gw->next; + } + + if (!gw) + gw = malloc(sizeof(struct mgcp_gateway)); + if (gw) { - memset(gw, 0, sizeof(struct mgcp_gateway)); - gw->expire = -1; - strncpy(gw->name, cat, sizeof(gw->name) - 1); + if (!gw_reload) { + memset(gw, 0, sizeof(struct mgcp_gateway)); + gw->expire = -1; + gw->retransid = -1; /* SC */ + ast_mutex_init(&gw->msgs_lock); + strncpy(gw->name, cat, sizeof(gw->name) - 1); + /* SC: check if the name is numeric ip */ + if (inet_addr(gw->name) != INADDR_NONE) + gw->isnamedottedip = 1; + } while(v) { if (!strcasecmp(v->name, "host")) { if (!strcasecmp(v->value, "dynamic")) { @@ -2665,13 +3221,15 @@ static struct mgcp_gateway *build_gateway(char *cat, struct ast_variable *v) gw->expire = -1; gw->dynamic = 0; if (ast_get_ip(&gw->addr, v->value)) { - free(gw); + if (!gw_reload) + free(gw); return NULL; } } } else if (!strcasecmp(v->name, "defaultip")) { if (ast_get_ip(&gw->defaddr, v->value)) { - free(gw); + if (!gw_reload) + free(gw); return NULL; } } else if (!strcasecmp(v->name, "permit") || @@ -2730,10 +3288,30 @@ static struct mgcp_gateway *build_gateway(char *cat, struct ast_variable *v) } else if (!strcasecmp(v->name, "trunk") || !strcasecmp(v->name, "line")) { - e = malloc(sizeof(struct mgcp_endpoint)); + + /* SC: locate existing endpoint */ + e = gw->endpoints; + while (e) { + if (!strcasecmp(v->value, e->name)) { + /* endpoint already exists */ + e->delme = 0; + ep_reload = 1; + break; + } + e = e->next; + } + + if (!e) { + e = malloc(sizeof(struct mgcp_endpoint)); + ep_reload = 0; + } + if (e) { - memset(e, 0, sizeof(struct mgcp_endpoint)); - strncpy(e->name, v->value, sizeof(e->name) - 1); + if (!ep_reload) { + memset(e, 0, sizeof(struct mgcp_endpoint)); + strncpy(e->name, v->value, sizeof(e->name) - 1); + e->needaudit = 1; + } /* XXX Should we really check for uniqueness?? XXX */ strncpy(e->context, context, sizeof(e->context) - 1); strncpy(e->callerid, callerid, sizeof(e->callerid) - 1); @@ -2743,9 +3321,12 @@ static struct mgcp_gateway *build_gateway(char *cat, struct ast_variable *v) if (strlen(mailbox)) { ast_verbose(VERBOSE_PREFIX_3 "Setting mailbox '%s' on %s@%s\n", mailbox, gw->name, e->name); } - e->msgstate = -1; + if (!ep_reload) { + /* XXX SC: potential issue due to reload */ + e->msgstate = -1; + e->parent = gw; + } e->capability = capability; - e->parent = gw; e->dtmfinband = inbanddtmf; e->adsi = adsi; if (!strcasecmp(v->name, "trunk")) @@ -2762,42 +3343,59 @@ static struct mgcp_gateway *build_gateway(char *cat, struct ast_variable *v) e->callwaiting = callwaiting; e->transfer = transfer; e->threewaycalling = threewaycalling; - e->onhooktime = time(NULL); - /* ASSUME we're onhook */ - e->hookstate = MGCP_ONHOOK; - /*snprintf(txident, sizeof(txident), "%08x", rand());*/ + if (!ep_reload) { + e->onhooktime = time(NULL); + /* ASSUME we're onhook */ + e->hookstate = MGCP_ONHOOK; + snprintf(e->rqnt_ident, sizeof(e->rqnt_ident), "%08x", rand()); + ast_mutex_init(&e->rqnt_queue_lock); + ast_mutex_init(&e->cmd_queue_lock); + } + + for (i = 0, sub = NULL; i < MAX_SUBS; i++) { + if (!ep_reload) { + sub = malloc(sizeof(struct mgcp_subchannel)); + } + else { + if (!sub) + sub = e->sub; + else + sub = sub->next; + } - for (i = 0; i < MAX_SUBS; i++) { - sub = malloc(sizeof(struct mgcp_subchannel)); if (sub) { - ast_verbose(VERBOSE_PREFIX_3 "Allocating subchannel '%d' on %s@%s\n", i, e->name, gw->name); - memset(sub, 0, sizeof(struct mgcp_subchannel)); - sub->parent = e; - sub->id = i; - snprintf(sub->txident, sizeof(sub->txident), "%08x", rand()); - /*strcpy(sub->txident, txident);*/ - sub->cxmode = MGCP_CX_INACTIVE; + if (!ep_reload) { + ast_verbose(VERBOSE_PREFIX_3 "Allocating subchannel '%d' on %s@%s\n", i, e->name, gw->name); + memset(sub, 0, sizeof(struct mgcp_subchannel)); + strncpy(sub->magic, MGCP_SUBCHANNEL_MAGIC, sizeof(sub->magic) - 1); + sub->parent = e; + sub->id = i; + snprintf(sub->txident, sizeof(sub->txident), "%08x", rand()); + sub->cxmode = MGCP_CX_INACTIVE; + sub->next = e->sub; + ast_mutex_init(&sub->cx_queue_lock); + e->sub = sub; + } sub->nat = nat; - sub->next = e->sub; - e->sub = sub; } else { /* XXX Should find a way to clean up our memory */ ast_log(LOG_WARNING, "Out of memory allocating subchannel"); return NULL; } } - /* Make out subs a circular linked list so we can always sping through the whole bunch */ - sub = e->sub; - /* find the end of the list */ - while(sub->next){ - sub = sub->next; - } - /* set the last sub->next to the first sub */ - sub->next = e->sub; - + if (!ep_reload) { + /* Make out subs a circular linked list so we can always sping through the whole bunch */ + sub = e->sub; + /* find the end of the list */ + while(sub->next){ + sub = sub->next; + } + /* set the last sub->next to the first sub */ + sub->next = e->sub; - e->next = gw->endpoints; - gw->endpoints = e; + e->next = gw->endpoints; + gw->endpoints = e; + } } } else ast_log(LOG_WARNING, "Don't know keyword '%s' at line %d\n", v->name, v->lineno); @@ -2807,17 +3405,19 @@ static struct mgcp_gateway *build_gateway(char *cat, struct ast_variable *v) } if (!ntohl(gw->addr.sin_addr.s_addr) && !gw->dynamic) { ast_log(LOG_WARNING, "Gateway '%s' lacks IP address and isn't dynamic\n", gw->name); - free(gw); + if (!gw_reload) + free(gw); return NULL; } if (gw->defaddr.sin_addr.s_addr && !ntohs(gw->defaddr.sin_port)) - gw->defaddr.sin_port = htons(DEFAULT_MGCP_PORT); + gw->defaddr.sin_port = htons(DEFAULT_MGCP_GW_PORT); if (gw->addr.sin_addr.s_addr && !ntohs(gw->addr.sin_port)) - gw->addr.sin_port = htons(DEFAULT_MGCP_PORT); + gw->addr.sin_port = htons(DEFAULT_MGCP_GW_PORT); if (gw->addr.sin_addr.s_addr) if (ast_ouraddrfor(&gw->addr.sin_addr, &gw->ourip)) memcpy(&gw->ourip, &__ourip, sizeof(gw->ourip)); - return gw; + + return (gw_reload ? NULL : gw); } static struct ast_rtp *mgcp_get_rtp_peer(struct ast_channel *chan) @@ -2872,13 +3472,113 @@ static char no_debug_usage[] = "Usage: mgcp no debug\n" " Disables dumping of MGCP packets for debugging purposes\n"; +static char mgcp_reload_usage[] = +"Usage: mgcp reload\n" +" Reloads MGCP configuration from mgcp.conf\n"; + static struct ast_cli_entry cli_debug = { { "mgcp", "debug", NULL }, mgcp_do_debug, "Enable MGCP debugging", debug_usage }; static struct ast_cli_entry cli_no_debug = { { "mgcp", "no", "debug", NULL }, mgcp_no_debug, "Disable MGCP debugging", no_debug_usage }; +static struct ast_cli_entry cli_mgcp_reload = + { { "mgcp", "reload", NULL }, mgcp_reload, "Reload MGCP configuration", mgcp_reload_usage }; -int load_module() +static void destroy_endpoint(struct mgcp_endpoint *e) +{ + struct mgcp_subchannel *sub = e->sub->next, *s; + int i; + + for (i = 0; i < MAX_SUBS; i++) { + ast_mutex_lock(&sub->lock); + if (strlen(sub->cxident)) { + transmit_connection_del(sub); + } + if (sub->rtp) { + ast_rtp_destroy(sub->rtp); + sub->rtp = NULL; + } + memset(sub->magic, 0, sizeof(sub->magic)); + if (sub->owner) { + ast_softhangup(sub->owner, AST_SOFTHANGUP_DEV); + } + dump_cmd_queues(NULL, sub); + ast_mutex_unlock(&sub->lock); + sub = sub->next; + } + + if (e->dsp) { + ast_dsp_free(e->dsp); + } + + dump_queue(e->parent, e); + dump_cmd_queues(e, NULL); + + sub = e->sub; + for (i = 0; (i < MAX_SUBS) && sub; i++) { + s = sub; + sub = sub->next; + free(s); + } + free(e); +} + +static void destroy_gateway(struct mgcp_gateway *g) +{ + if (g->ha) + ast_free_ha(g->ha); + + dump_queue(g, NULL); + + free (g); +} + +static void prune_gateways(void) +{ + struct mgcp_gateway *g, *z, *r; + struct mgcp_endpoint *e, *p, *t; + + ast_mutex_lock(&gatelock); + + /* prune gateways */ + for (z = NULL, g = gateways; g;) { + /* prune endpoints */ + for (p = NULL, e = g->endpoints; e; ) { + if (e->delme || g->delme) { + t = e; + e = e->next; + if (!p) + g->endpoints = e; + else + p->next = e; + destroy_endpoint(t); + } + else { + p = e; + e = e->next; + } + } + + if (g->delme) { + r = g; + g = g->next; + if (!z) + gateways = g; + else + z->next = g; + + destroy_gateway(r); + } + else { + z = g; + g = g->next; + } + } + + ast_mutex_unlock(&gatelock); +} + +static int reload_config(void) { struct ast_config *cfg; struct ast_variable *v; @@ -2945,36 +3645,54 @@ int load_module() } v = v->next; } + + /* SC: mark existing entries for deletion */ + ast_mutex_lock(&gatelock); + g = gateways; + while (g) { + g->delme = 1; + e = g->endpoints; + while (e) { + e->delme = 1; + e = e->next; + } + g = g->next; + } + ast_mutex_unlock(&gatelock); cat = ast_category_browse(cfg, NULL); while(cat) { if (strcasecmp(cat, "general")) { + ast_mutex_lock(&gatelock); g = build_gateway(cat, ast_variable_browse(cfg, cat)); if (g) { if (option_verbose > 2) { ast_verbose(VERBOSE_PREFIX_3 "Added gateway '%s'\n", g->name); } - ast_mutex_lock(&gatelock); - g->next = gateways; - gateways = g; - ast_mutex_unlock(&gatelock); + g->next = gateways; + gateways = g; } + ast_mutex_unlock(&gatelock); } cat = ast_category_browse(cfg, cat); } - + + /* SC: prune deleted entries etc. */ + prune_gateways(); + if (ntohl(bindaddr.sin_addr.s_addr)) { memcpy(&__ourip, &bindaddr.sin_addr, sizeof(__ourip)); } else { hp = gethostbyname(ourhost); if (!hp) { ast_log(LOG_WARNING, "Unable to get our IP address, MGCP disabled\n"); + ast_destroy(cfg); return 0; } memcpy(&__ourip, hp->h_addr, sizeof(__ourip)); } if (!ntohs(bindaddr.sin_port)) - bindaddr.sin_port = ntohs(DEFAULT_MGCP_PORT); + bindaddr.sin_port = ntohs(DEFAULT_MGCP_CA_PORT); bindaddr.sin_family = AF_INET; ast_mutex_lock(&netlock); if (mgcpsock > -1) @@ -3002,25 +3720,12 @@ int load_module() ast_mutex_unlock(&netlock); ast_destroy(cfg); - /* Make sure we can register our mgcp channel type */ - if (ast_channel_register(type, tdesc, capability, mgcp_request)) { - ast_log(LOG_ERROR, "Unable to register channel class %s\n", type); - ast_destroy(cfg); - return -1; - } - mgcp_rtp.type = type; - ast_rtp_proto_register(&mgcp_rtp); - ast_cli_register(&cli_show_endpoints); - ast_cli_register(&cli_audit_endpoint); - ast_cli_register(&cli_debug); - ast_cli_register(&cli_no_debug); - /* And start the monitor for the first time */ - restart_monitor(); - + /* SC: send audit only to the new endpoints */ g = gateways; while (g) { e = g->endpoints; - while (e) { + while (e && e->needaudit) { + e->needaudit = 0; transmit_audit_endpoint(e); ast_verbose(VERBOSE_PREFIX_3 "MGCP Auditing endpoint %s@%s for hookstate\n", e->name, g->name); e = e->next; @@ -3031,6 +3736,66 @@ int load_module() return 0; } +int load_module() +{ + int res; + + sched = sched_context_create(); + if (!sched) { + ast_log(LOG_WARNING, "Unable to create schedule context\n"); + return -1; + } + io = io_context_create(); + if (!io) { + ast_log(LOG_WARNING, "Unable to create I/O context\n"); + return -1; + } + + if (!(res = reload_config())) { + /* Make sure we can register our mgcp channel type */ + if (ast_channel_register(type, tdesc, capability, mgcp_request)) { + ast_log(LOG_ERROR, "Unable to register channel class %s\n", type); + return -1; + } + mgcp_rtp.type = type; + ast_rtp_proto_register(&mgcp_rtp); + ast_cli_register(&cli_show_endpoints); + ast_cli_register(&cli_audit_endpoint); + ast_cli_register(&cli_debug); + ast_cli_register(&cli_no_debug); + ast_cli_register(&cli_mgcp_reload); + + /* And start the monitor for the first time */ + restart_monitor(); + } + + return res; +} + +static int mgcp_do_reload(void) +{ + reload_config(); + return 0; +} + +static int mgcp_reload(int fd, int argc, char *argv[]) +{ + ast_mutex_lock(&mgcp_reload_lock); + if (mgcp_reloading) { + ast_verbose("Previous mgcp reload not yet done\n"); + } else + mgcp_reloading = 1; + ast_mutex_unlock(&mgcp_reload_lock); + restart_monitor(); + return 0; +} + +int reload(void) +{ + mgcp_reload(0, 0, NULL); + return 0; +} + int unload_module() { #if 0