Newer
Older
/* Reschedule re-transmit */
ast_debug(4, "** SIP timers: Rescheduling retransmission %d to %d ms (t1 %d ms (Retrans id #%d)) \n",
pkt->retrans + 1,
siptimer_a,
pkt->timer_t1,
pkt->retransid);
Joshua Colp
committed
if (sip_debug_test_pvt(pkt->owner)) {
const struct ast_sockaddr *dst = sip_real_dst(pkt->owner);
ast_verbose("Retransmitting #%d (%s) to %s:\n%s\n---\n",
pkt->retrans, sip_nat_mode(pkt->owner),
Joshua Colp
committed
append_history(pkt->owner, "ReTx", "%d %s", reschedule, ast_str_buffer(pkt->data));
xmitres = __sip_xmit(pkt->owner, pkt->data);
/* If there was no error during the network transmission, schedule the next retransmission,
* but if the next retransmission is going to be beyond our timeout period, mark the packet's
* stop_retrans value and set the next retransmit to be the exact time of timeout. This will
* allow any responses to the packet to be processed before the packet is destroyed on the next
* call to this function by the scheduler. */
if (xmitres != XMIT_ERROR) {
if (reschedule >= diff) {
pkt->retrans_stop = 1;
reschedule = diff;
}
Joshua Colp
committed
}
/* At this point, either the packet's retransmission timed out, or there was a
* transmission error, either way destroy the scheduler item and this packet. */
pkt->retransid = -1; /* Kill this scheduler item */
if (pkt->method != SIP_OPTIONS && xmitres == 0) {
if (pkt->is_fatal || sipdebug) { /* Tell us if it's critical or if we're debugging */
ast_log(LOG_WARNING, "Retransmission timeout reached on transmission %s for seqno %u (%s %s) -- See https://wiki.asterisk.org/wiki/display/AST/SIP+Retransmissions\n"
"Packet timed out after %dms with no response\n",
pkt->owner->callid,
pkt->seqno,
pkt->is_fatal ? "Critical" : "Non-critical",
pkt->is_resp ? "Response" : "Request",
(int) ast_tvdiff_ms(ast_tvnow(), pkt->time_sent));
} else if (pkt->method == SIP_OPTIONS && sipdebug) {
ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s) -- See https://wiki.asterisk.org/wiki/display/AST/SIP+Retransmissions\n", pkt->owner->callid);
if (xmitres == XMIT_ERROR) {
ast_log(LOG_WARNING, "Transmit error :: Cancelling transmission on Call ID %s\n", pkt->owner->callid);
append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
append_history(pkt->owner, "MaxRetries", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
if (pkt->is_fatal) {
while(pkt->owner->owner && ast_channel_trylock(pkt->owner->owner)) {
sip_pvt_unlock(pkt->owner); /* SIP_PVT, not channel */
usleep(1);
sip_pvt_lock(pkt->owner);
}
if (pkt->owner->owner && !ast_channel_hangupcause(pkt->owner->owner)) {
ast_channel_hangupcause_set(pkt->owner->owner, AST_CAUSE_NO_USER_RESPONSE);
ast_log(LOG_WARNING, "Hanging up call %s - no reply to our critical packet (see https://wiki.asterisk.org/wiki/display/AST/SIP+Retransmissions).\n", pkt->owner->callid);
if (pkt->is_resp &&
(pkt->response_code >= 200) &&
(pkt->response_code < 300) &&
pkt->owner->pendinginvite &&
ast_test_flag(&pkt->owner->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) {
/* This is a timeout of the 2XX response to a pending INVITE. In this case terminate the INVITE
* transaction just as if we received the ACK, but immediately hangup with a BYE (sip_hangup
* will send the BYE as long as the dialog is not set as "alreadygone")
* RFC 3261 section 13.3.1.4.
* "If the server retransmits the 2xx response for 64*T1 seconds without receiving
* an ACK, the dialog is confirmed, but the session SHOULD be terminated. This is
* accomplished with a BYE, as described in Section 15." */
pkt->owner->invitestate = INV_TERMINATED;
pkt->owner->pendinginvite = 0;
} else {
/* there is nothing left to do, mark the dialog as gone */
sip_alreadygone(pkt->owner);
}
ast_queue_hangup_with_cause(pkt->owner->owner, AST_CAUSE_NO_USER_RESPONSE);
ast_channel_unlock(pkt->owner->owner);
} else {
/* If no channel owner, destroy now */
/* Let the peerpoke system expire packets when the timer expires for poke_noanswer */
if (pkt->method != SIP_OPTIONS && pkt->method != SIP_REGISTER) {
pvt_set_needdestroy(pkt->owner, "no response to critical packet");
sip_alreadygone(pkt->owner);
append_history(pkt->owner, "DialogKill", "Killing this failed dialog immediately");
}
}
}
if (pkt->method == SIP_BYE) {
/* We're not getting answers on SIP BYE's. Tear down the call anyway. */
ast_channel_unlock(pkt->owner->owner);
append_history(pkt->owner, "ByeFailure", "Remote peer doesn't respond to bye. Destroying call anyway.");
pvt_set_needdestroy(pkt->owner, "no response to BYE");
/* Remove the packet */
for (prev = NULL, cur = pkt->owner->packets; cur; prev = cur, cur = cur->next) {
if (cur == pkt) {
UNLINK(cur, pkt->owner->packets, prev);
sip_pvt_unlock(pkt->owner);
pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
}
if (pkt->data) {
pkt->data = NULL;
ast_free(pkt);
return 0;
}
}
/* error case */
ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n");
sip_pvt_unlock(pkt->owner);
/*!
* \internal
* \brief Transmit packet with retransmits
* \return 0 on success, -1 on failure to allocate packet
*/
static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, uint32_t seqno, int resp, struct ast_str *data, int fatal, int sipmethod)
struct sip_pkt *pkt = NULL;
int siptimer_a = DEFAULT_RETRANS;
int xmitres = 0;
if (sipmethod == SIP_INVITE) {
/* Note this is a pending invite */
p->pendinginvite = seqno;
Olle Johansson
committed
}
/* If the transport is something reliable (TCP or TLS) then don't really send this reliably */
/* I removed the code from retrans_pkt that does the same thing so it doesn't get loaded into the scheduler */
/*! \todo According to the RFC some packets need to be retransmitted even if its TCP, so this needs to get revisited */
if (!(p->socket.type & AST_TRANSPORT_UDP)) {
xmitres = __sip_xmit(p, data); /* Send packet */
if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */
append_history(p, "XmitErr", "%s", fatal ? "(Critical)" : "(Non-critical)");
return AST_FAILURE;
} else {
return AST_SUCCESS;
}
}
if (!(pkt = ast_calloc(1, sizeof(*pkt)))) {
/* copy data, add a terminator and save length */
if (!(pkt->data = ast_str_create(ast_str_strlen(data)))) {
ast_free(pkt);
return AST_FAILURE;
}
ast_str_set(&pkt->data, 0, "%s%s", ast_str_buffer(data), "\0");
/* copy other parameters from the caller */
pkt->method = sipmethod;
pkt->seqno = seqno;
pkt->is_resp = resp;
pkt->is_fatal = fatal;
pkt->owner = dialog_ref(p, "__sip_reliable_xmit: setting pkt->owner");
pkt->next = p->packets;
p->packets = pkt; /* Add it to the queue */
if (resp) {
/* Parse out the response code */
if (sscanf(ast_str_buffer(pkt->data), "SIP/2.0 %30u", &respid) == 1) {
pkt->response_code = respid;
}
}
pkt->timer_t1 = p->timer_t1; /* Set SIP timer T1 */
pkt->retransid = -1;
David Vossel
committed
siptimer_a = pkt->timer_t1;
pkt->time_sent = ast_tvnow(); /* time packet was sent */
pkt->retrans_stop_time = 64 * (pkt->timer_t1 ? pkt->timer_t1 : DEFAULT_TIMER_T1); /* time in ms after pkt->time_sent to stop retransmission */
/* Schedule retransmission */
AST_SCHED_REPLACE_VARIABLE(pkt->retransid, sched, siptimer_a, retrans_pkt, pkt, 1);
ast_debug(4, "*** SIP TIMER: Initializing retransmit timer on packet: Id #%d\n", pkt->retransid);
xmitres = __sip_xmit(pkt->owner, pkt->data); /* Send packet */
if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */
append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
ast_log(LOG_ERROR, "Serious Network Trouble; __sip_xmit returns error for pkt data\n");
AST_SCHED_DEL(sched, pkt->retransid);
p->packets = pkt->next;
pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
ast_free(pkt->data);
ast_free(pkt);
return AST_FAILURE;
/* This is odd, but since the retrans timer starts at 500ms and the do_monitor thread
* only wakes up every 1000ms by default, we have to poke the thread here to make
* sure it successfully detects this must be retransmitted in less time than
* it usually sleeps for. Otherwise it might not retransmit this packet for 1000ms. */
if (monitor_thread != AST_PTHREADT_NULL) {
pthread_kill(monitor_thread, SIGURG);
}
/*! \brief Kill a SIP dialog (called only by the scheduler)
* The scheduler has a reference to this dialog when p->autokillid != -1,
* and we are called using that reference. So if the event is not
* rescheduled, we need to call dialog_unref().
*/
static int __sip_autodestruct(const void *data)
struct sip_pvt *p = (struct sip_pvt *)data;
struct ast_channel *owner;
/* If this is a subscription, tell the phone that we got a timeout */
if (p->subscribed && p->subscribed != MWI_NOTIFICATION && p->subscribed != CALL_COMPLETION) {
struct state_notify_data data = { 0, };
data.state = AST_EXTENSION_DEACTIVATED;
transmit_state_notify(p, &data, 1, TRUE); /* Send last notification */
p->subscribed = NONE;
append_history(p, "Subscribestatus", "timeout");
ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", p->callid ? p->callid : "<unknown>");
return 10000; /* Reschedule this destruction so that we know that it's gone */
}
/* If there are packets still waiting for delivery, delay the destruction */
if (p->packets) {
if (!p->needdestroy) {
char method_str[31];
ast_debug(3, "Re-scheduled destruction of SIP call %s\n", p->callid ? p->callid : "<unknown>");
append_history(p, "ReliableXmit", "timeout");
if (sscanf(p->lastmsg, "Tx: %30s", method_str) == 1 || sscanf(p->lastmsg, "Rx: %30s", method_str) == 1) {
if (p->ongoing_reinvite || method_match(SIP_CANCEL, method_str) || method_match(SIP_BYE, method_str)) {
pvt_set_needdestroy(p, "autodestruct");
}
/* They've had their chance to respond. Time to bail */
__sip_pretend_ack(p);
Russell Bryant
committed
/* Reset schedule ID */
p->autokillid = -1;
Russell Bryant
committed
/*
* Lock both the pvt and the channel safely so that we can queue up a frame.
*/
owner = sip_pvt_lock_full(p);
if (owner) {
ast_log(LOG_WARNING, "Autodestruct on dialog '%s' with owner %s in place (Method: %s). Rescheduling destruction for 10000 ms\n", p->callid, ast_channel_name(owner), sip_methods[p->method].text);
ast_queue_hangup_with_cause(owner, AST_CAUSE_PROTOCOL_ERROR);
ast_channel_unlock(owner);
ast_channel_unref(owner);
Mark Michelson
committed
sip_pvt_unlock(p);
return 10000;
} else if (p->refer && !p->alreadygone) {
ast_debug(3, "Finally hanging up channel after transfer: %s\n", p->callid);
transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1);
append_history(p, "ReferBYE", "Sending BYE on transferer call leg %s", p->callid);
sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
} else {
append_history(p, "AutoDestroy", "%s", p->callid);
ast_debug(3, "Auto destroying SIP dialog '%s'\n", p->callid);
sip_pvt_unlock(p);
dialog_unlink_all(p); /* once it's unlinked and unrefd everywhere, it'll be freed automagically */
sip_pvt_lock(p);
/* dialog_unref(p, "unref dialog-- no other matching conditions"); -- unlink all now should finish off the dialog's references and free it. */
/* sip_destroy(p); */ /* Go ahead and destroy dialog. All attempts to recover is done */
/* sip_destroy also absorbs the reference */
}
sip_pvt_unlock(p);
dialog_unref(p, "The ref to a dialog passed to this sched callback is going out of scope; unref it.");
}
/*! \brief Schedule final destruction of SIP dialog. This can not be canceled.
* This function is used to keep a dialog around for a period of time in order
* to properly respond to any retransmits. */
void sip_scheddestroy_final(struct sip_pvt *p, int ms)
{
if (p->final_destruction_scheduled) {
return; /* already set final destruction */
}
sip_scheddestroy(p, ms);
if (p->autokillid != -1) {
p->final_destruction_scheduled = 1;
}
}
/*! \brief Schedule destruction of SIP dialog */
void sip_scheddestroy(struct sip_pvt *p, int ms)
if (p->final_destruction_scheduled) {
return; /* already set final destruction */
}
if (ms < 0) {
if (p->timer_t1 == 0) {
p->timer_t1 = global_t1; /* Set timer T1 if not set (RFC 3261) */
}
if (p->timer_b == 0) {
p->timer_b = global_timer_b; /* Set timer B if not set (RFC 3261) */
}
ms = p->timer_t1 * 64;
if (sip_debug_test_pvt(p)) {
ast_verbose("Scheduling destruction of SIP dialog '%s' in %d ms (Method: %s)\n", p->callid, ms, sip_methods[p->method].text);
}
if (sip_cancel_destroy(p)) {
ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
Brett Bryant
committed
append_history(p, "SchedDestroy", "%d ms", ms);
p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p, "setting ref as passing into ast_sched_add for __sip_autodestruct"));
if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_schedid > 0) {
/*! \brief Cancel destruction of SIP dialog.
* Be careful as this also absorbs the reference - if you call it
* from within the scheduler, this might be the last reference.
*/
int sip_cancel_destroy(struct sip_pvt *p)
if (p->final_destruction_scheduled) {
append_history(p, "CancelDestroy", "");
AST_SCHED_DEL_UNREF(sched, p->autokillid, dialog_unref(p, "remove ref for autokillid"));
/*! \brief Acknowledges receipt of a packet and stops retransmission
* called with p locked*/
int __sip_ack(struct sip_pvt *p, uint32_t seqno, int resp, int sipmethod)
struct sip_pkt *cur, *prev = NULL;
const char *msg = "Not Found"; /* used only for debugging */
int res = FALSE;
/* If we have an outbound proxy for this dialog, then delete it now since
the rest of the requests in this dialog needs to follow the routing.
If obforcing is set, we will keep the outbound proxy during the whole
dialog, regardless of what the SIP rfc says
*/
if (p->outboundproxy && !p->outboundproxy->force) {
for (cur = p->packets; cur; prev = cur, cur = cur->next) {
if (cur->seqno != seqno || cur->is_resp != resp) {
if (cur->is_resp || cur->method == sipmethod) {
res = TRUE;
msg = "Found";
if (!resp && (seqno == p->pendinginvite)) {
ast_debug(1, "Acked pending invite %u\n", p->pendinginvite);
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
p->pendinginvite = 0;
}
if (cur->retransid > -1) {
if (sipdebug)
ast_debug(4, "** SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", cur->retransid);
}
/* This odd section is designed to thwart a
* race condition in the packet scheduler. There are
* two conditions under which deleting the packet from the
* scheduler can fail.
*
* 1. The packet has been removed from the scheduler because retransmission
* is being attempted. The problem is that if the packet is currently attempting
* retransmission and we are at this point in the code, then that MUST mean
* that retrans_pkt is waiting on p's lock. Therefore we will relinquish the
* lock temporarily to allow retransmission.
*
* 2. The packet has reached its maximum number of retransmissions and has
* been permanently removed from the packet scheduler. If this is the case, then
* the packet's retransid will be set to -1. The atomicity of the setting and checking
* of the retransid to -1 is ensured since in both cases p's lock is held.
while (cur->retransid > -1 && ast_sched_del(sched, cur->retransid)) {
sip_pvt_unlock(p);
usleep(1);
sip_pvt_lock(p);
UNLINK(cur, p->packets, prev);
dialog_unref(cur->owner, "unref pkt cur->owner dialog from sip ack before freeing pkt");
ast_debug(1, "Stopping retransmission on '%s' of %s %u: Match %s\n",
p->callid, resp ? "Response" : "Request", seqno, msg);
return res;
}
/*! \brief Pretend to ack all packets
* called with p locked */
void __sip_pretend_ack(struct sip_pvt *p)
{
struct sip_pkt *cur = NULL;
while (p->packets) {
int method;
if (cur == p->packets) {
ast_log(LOG_WARNING, "Have a packet that doesn't want to give up! %s\n", sip_methods[cur->method].text);
return;
method = (cur->method) ? cur->method : find_sip_method(ast_str_buffer(cur->data));
__sip_ack(p, cur->seqno, cur->is_resp, method);
/*! \brief Acks receipt of packet, keep it around (used for provisional responses) */
int __sip_semi_ack(struct sip_pvt *p, uint32_t seqno, int resp, int sipmethod)
{
struct sip_pkt *cur;
int res = FALSE;
for (cur = p->packets; cur; cur = cur->next) {
if (cur->seqno == seqno && cur->is_resp == resp &&
(cur->is_resp || method_match(sipmethod, ast_str_buffer(cur->data)))) {
/* this is our baby */
if (cur->retransid > -1) {
if (sipdebug)
ast_debug(4, "*** SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", cur->retransid, sip_methods[sipmethod].text);
AST_SCHED_DEL(sched, cur->retransid);
res = TRUE;
break;
Mark Spencer
committed
}
ast_debug(1, "(Provisional) Stopping retransmission (but retaining packet) on '%s' %s %u: %s\n", p->callid, resp ? "Response" : "Request", seqno, res == -1 ? "Not Found" : "Found");
return res;
}
/*! \brief Copy SIP request, parse it */
static void parse_copy(struct sip_request *dst, const struct sip_request *src)
{
copy_request(dst, src);
parse_request(dst);
}
/*! \brief add a blank line if no body */
static void add_blank(struct sip_request *req)
{
if (!req->lines) {
/* Add extra empty return. add_header() reserves 4 bytes so cannot be truncated */
ast_str_append(&req->data, 0, "\r\n");
}
static int send_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp)
{
const char *msg = NULL;
struct ast_channel *chan;
int res = 0;
int old_sched_id = pvt->provisional_keepalive_sched_id;
chan = sip_pvt_lock_full(pvt);
/* Check that nothing has changed while we were waiting for the lock */
if (old_sched_id != pvt->provisional_keepalive_sched_id) {
/* Keepalive has been cancelled or rescheduled, clean up and leave */
if (chan) {
ast_channel_unlock(chan);
chan = ast_channel_unref(chan);
}
sip_pvt_unlock(pvt);
dialog_unref(pvt, "dialog ref for provisional keepalive");
return 0;
}
Mark Spencer
committed
if (!pvt->last_provisional || !strncasecmp(pvt->last_provisional, "100", 3)) {
msg = "183 Session Progress";
}
if (pvt->invitestate < INV_COMPLETED) {
if (with_sdp) {
transmit_response_with_sdp(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq, XMIT_UNRELIABLE, FALSE, FALSE);
} else {
transmit_response(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq);
res = PROVIS_KEEPALIVE_TIMEOUT;
Mark Spencer
committed
if (chan) {
ast_channel_unlock(chan);
chan = ast_channel_unref(chan);
}
if (!res) {
pvt->provisional_keepalive_sched_id = -1;
}
sip_pvt_unlock(pvt);
if (!res) {
dialog_unref(pvt, "dialog ref for provisional keepalive");
}
return res;
static int send_provisional_keepalive(const void *data)
{
struct sip_pvt *pvt = (struct sip_pvt *) data;
Mark Spencer
committed
return send_provisional_keepalive_full(pvt, 0);
static int send_provisional_keepalive_with_sdp(const void *data)
{
struct sip_pvt *pvt = (void *) data;
Sean Bright
committed
return send_provisional_keepalive_full(pvt, 1);
}
Sean Bright
committed
static void update_provisional_keepalive(struct sip_pvt *pvt, int with_sdp)
{
AST_SCHED_DEL_UNREF(sched, pvt->provisional_keepalive_sched_id, dialog_unref(pvt, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
Sean Bright
committed
pvt->provisional_keepalive_sched_id = ast_sched_add(sched, PROVIS_KEEPALIVE_TIMEOUT,
with_sdp ? send_provisional_keepalive_with_sdp : send_provisional_keepalive, dialog_ref(pvt, "Increment refcount to pass dialog pointer to sched callback"));
Sean Bright
committed
}
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
static void add_required_respheader(struct sip_request *req)
{
struct ast_str *str;
int i;
if (!req->reqsipoptions) {
return;
}
str = ast_str_create(32);
for (i = 0; i < ARRAY_LEN(sip_options); ++i) {
if (!(req->reqsipoptions & sip_options[i].id)) {
continue;
}
if (ast_str_strlen(str) > 0) {
ast_str_append(&str, 0, ", ");
}
ast_str_append(&str, 0, "%s", sip_options[i].text);
}
if (ast_str_strlen(str) > 0) {
add_header(req, "Require", ast_str_buffer(str));
}
ast_free(str);
}
/*! \brief Transmit response on SIP request*/
static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, uint32_t seqno)
Kevin P. Fleming
committed
add_blank(req);
if (sip_debug_test_pvt(p)) {
const struct ast_sockaddr *dst = sip_real_dst(p);
ast_verbose("\n<--- %sTransmitting (%s) to %s --->\n%s\n<------------>\n",
reliable ? "Reliably " : "", sip_nat_mode(p),
append_history(p, reliable ? "TxRespRel" : "TxResp", "%s / %s - %s", ast_str_buffer(tmp.data), sip_get_header(&tmp, "CSeq"),
(tmp.method == SIP_RESPONSE || tmp.method == SIP_UNKNOWN) ? REQ_OFFSET_TO_STR(&tmp, rlpart2) : sip_methods[tmp.method].text);
/* If we are sending a final response to an INVITE, stop retransmitting provisional responses */
if (p->initreq.method == SIP_INVITE && reliable == XMIT_CRITICAL) {
AST_SCHED_DEL_UNREF(sched, p->provisional_keepalive_sched_id, dialog_unref(p, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
__sip_reliable_xmit(p, seqno, 1, req->data, (reliable == XMIT_CRITICAL), req->method) :
__sip_xmit(p, req->data);
/*!
* \internal
* \brief Send SIP Request to the other part of the dialogue
* \return see \ref __sip_xmit
*/
static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, uint32_t seqno)
/* If we have an outbound proxy, reset peer address
Only do this once.
*/
if (p->outboundproxy) {
p->sa = p->outboundproxy->ip;
add_blank(req);
if (sip_debug_test_pvt(p)) {
if (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT)) {
ast_verbose("%sTransmitting (NAT) to %s:\n%s\n---\n", reliable ? "Reliably " : "", ast_sockaddr_stringify(&p->recv), ast_str_buffer(req->data));
ast_verbose("%sTransmitting (no NAT) to %s:\n%s\n---\n", reliable ? "Reliably " : "", ast_sockaddr_stringify(&p->sa), ast_str_buffer(req->data));
append_history(p, reliable ? "TxReqRel" : "TxReq", "%s / %s - %s", ast_str_buffer(tmp.data), sip_get_header(&tmp, "CSeq"), sip_methods[tmp.method].text);
__sip_reliable_xmit(p, seqno, 0, req->data, (reliable == XMIT_CRITICAL), req->method) :
__sip_xmit(p, req->data);
static void enable_dsp_detect(struct sip_pvt *p)
Joshua Colp
committed
{
Joshua Colp
committed
Joshua Colp
committed
return;
David Vossel
committed
}
Joshua Colp
committed
if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
(ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
if (p->rtp) {
ast_rtp_instance_dtmf_mode_set(p->rtp, AST_RTP_DTMF_MODE_INBAND);
}
features |= DSP_FEATURE_DIGIT_DETECT;
Joshua Colp
committed
if (ast_test_flag(&p->flags[1], SIP_PAGE2_FAX_DETECT_CNG)) {
features |= DSP_FEATURE_FAX_DETECT;
Joshua Colp
committed
}
if (!features) {
return;
Brett Bryant
committed
}
if (!(p->dsp = ast_dsp_new())) {
return;
Brett Bryant
committed
}
ast_dsp_set_features(p->dsp, features);
if (global_relaxdtmf) {
ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
}
Brett Bryant
committed
}
static void disable_dsp_detect(struct sip_pvt *p)
if (p->dsp) {
ast_dsp_free(p->dsp);
p->dsp = NULL;
/*! \brief Set an option on a SIP dialog */
static int sip_setoption(struct ast_channel *chan, int option, void *data, int datalen)
{
int res = -1;
struct sip_pvt *p = ast_channel_tech_pvt(chan);
ast_log(LOG_ERROR, "Attempt to Ref a null pointer. sip private structure is gone!\n");
return -1;
switch (option) {
case AST_OPTION_FORMAT_READ:
res = ast_rtp_instance_set_read_format(p->rtp, *(struct ast_format **) data);
break;
case AST_OPTION_FORMAT_WRITE:
res = ast_rtp_instance_set_write_format(p->rtp, *(struct ast_format **) data);
break;
case AST_OPTION_DIGIT_DETECT:
if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
(ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
char *cp = (char *) data;
ast_debug(1, "%sabling digit detection on %s\n", *cp ? "En" : "Dis", ast_channel_name(chan));
if (*cp) {
enable_dsp_detect(p);
} else {
disable_dsp_detect(p);
}
res = 0;
}
break;
case AST_OPTION_SECURE_SIGNALING:
p->req_secure_signaling = *(unsigned int *) data;
res = 0;
break;
case AST_OPTION_SECURE_MEDIA:
ast_set2_flag(&p->flags[1], *(unsigned int *) data, SIP_PAGE2_USE_SRTP);
res = 0;
break;
return res;
}
/*! \brief Query an option on a SIP dialog */
static int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen)
{
int res = -1;
enum ast_t38_state state = T38_STATE_UNAVAILABLE;
struct sip_pvt *p = (struct sip_pvt *) ast_channel_tech_pvt(chan);
switch (option) {
case AST_OPTION_T38_STATE:
/* Make sure we got an ast_t38_state enum passed in */
if (*datalen != sizeof(enum ast_t38_state)) {
ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_STATE option. Expected %d, got %d\n", (int)sizeof(enum ast_t38_state), *datalen);
/* Now if T38 support is enabled we need to look and see what the current state is to get what we want to report back */
if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT)) {
switch (p->t38.state) {
case T38_LOCAL_REINVITE:
case T38_PEER_REINVITE:
state = T38_STATE_NEGOTIATING;
break;
case T38_ENABLED:
state = T38_STATE_NEGOTIATED;
break;
case T38_REJECTED:
state = T38_STATE_REJECTED;
break;
default:
state = T38_STATE_UNKNOWN;
}
}
*((enum ast_t38_state *) data) = state;
res = 0;
break;
case AST_OPTION_DIGIT_DETECT:
cp = (char *) data;
*cp = p->dsp ? 1 : 0;
ast_debug(1, "Reporting digit detection %sabled on %s\n", *cp ? "en" : "dis", ast_channel_name(chan));
case AST_OPTION_SECURE_SIGNALING:
*((unsigned int *) data) = p->req_secure_signaling;
res = 0;
break;
case AST_OPTION_SECURE_MEDIA:
*((unsigned int *) data) = ast_test_flag(&p->flags[1], SIP_PAGE2_USE_SRTP) ? 1 : 0;
res = 0;
break;
case AST_OPTION_DEVICE_NAME:
if (p && p->outgoing_call) {
cp = (char *) data;
ast_copy_string(cp, p->dialstring, *datalen);
res = 0;
}
/* We purposely break with a return of -1 in the
* implied else case here
*/
break;
default:
break;
}
/*! \brief Locate closing quote in a string, skipping escaped quotes.
* optionally with a limit on the search.
* start must be past the first quote.
const char *find_closing_quote(const char *start, const char *lim)
char last_char = '\0';
const char *s;
for (s = start; *s && s != lim; last_char = *s++) {
if (*s == '"' && last_char != '\\')
break;
}
return s;
}
Olle Johansson
committed
/*! \brief Send message with Access-URL header, if this is an HTML URL only! */
static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen)
{
struct sip_pvt *p = ast_channel_tech_pvt(chan);
if (subclass != AST_HTML_URL)
return -1;
ast_string_field_build(p, url, "<%s>;mode=active", data);
if (sip_debug_test_pvt(p))
ast_debug(1, "Send URL %s, state = %u!\n", data, ast_channel_state(chan));
switch (ast_channel_state(chan)) {
case AST_STATE_RING:
transmit_response(p, "100 Trying", &p->initreq);
break;
case AST_STATE_RINGING:
transmit_response(p, "180 Ringing", &p->initreq);
break;
case AST_STATE_UP:
if (!p->pendinginvite) { /* We are up, and have no outstanding invite */
transmit_reinvite_with_sdp(p, FALSE, FALSE);
} else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
}
ast_log(LOG_WARNING, "Don't know how to send URI when state is %u!\n", ast_channel_state(chan));
/*! \brief Deliver SIP call ID for the call */
static const char *sip_get_callid(struct ast_channel *chan)
{
return ast_channel_tech_pvt(chan) ? ((struct sip_pvt *) ast_channel_tech_pvt(chan))->callid : "";
/*!
* \internal
* \brief Send SIP MESSAGE text within a call
* \note Called from PBX core sendtext() application
*/
static int sip_sendtext(struct ast_channel *ast, const char *text)
{
struct sip_pvt *dialog = ast_channel_tech_pvt(ast);
/* NOT ast_strlen_zero, because a zero-length message is specifically
* allowed by RFC 3428 (See section 10, Examples) */
if(!is_method_allowed(&dialog->allowed_methods, SIP_MESSAGE)) {
ast_debug(2, "Trying to send MESSAGE to device that does not support it.\n");
return 0;
debug = sip_debug_test_pvt(dialog);
if (debug) {
ast_verbose("Sending text %s on %s\n", text, ast_channel_name(ast));
/* Setup to send text message */
sip_pvt_lock(dialog);
destroy_msg_headers(dialog);
ast_string_field_set(dialog, msg_body, text);
transmit_message(dialog, 0, 0);
sip_pvt_unlock(dialog);
/*! \brief Update peer object in realtime storage
If the Asterisk system name is set in asterisk.conf, we will use
that name and store that in the "regserver" field in the sippeers
table to facilitate multi-server setups.
*/
static void realtime_update_peer(const char *peername, struct ast_sockaddr *addr, const char *defaultuser, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms, const char *path)
char ipaddr[INET6_ADDRSTRLEN];
char regseconds[20];
char *tablename = NULL;
char str_lastms[20];
const char *sysname = ast_config_AST_SYSTEM_NAME;
char *syslabel = NULL;
Olle Johansson
committed
time_t nowtime = time(NULL) + expirey;
const char *fc = fullcontact ? "fullcontact" : NULL;
int realtimeregs = ast_check_realtime("sipregs");
tablename = realtimeregs ? "sipregs" : "sippeers";
Olle Johansson
committed
snprintf(str_lastms, sizeof(str_lastms), "%d", lastms);
snprintf(regseconds, sizeof(regseconds), "%d", (int)nowtime); /* Expiration time */
ast_copy_string(ipaddr, ast_sockaddr_isnull(addr) ? "" : ast_sockaddr_stringify_addr(addr), sizeof(ipaddr));
ast_copy_string(port, ast_sockaddr_port(addr) ? ast_sockaddr_stringify_port(addr) : "", sizeof(port));
if (ast_strlen_zero(sysname)) { /* No system name, disable this */
} else if (sip_cfg.rtsave_sysname) {
/* XXX IMPORTANT: Anytime you add a new parameter to be updated, you
* must also add it to contrib/scripts/asterisk.ldap-schema,
* contrib/scripts/asterisk.ldif,
* and to configs/res_ldap.conf.sample as described in
/* This is ugly, we need something better ;-) */
if (sip_cfg.rtsave_path) {
if (fc) {
ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
"port", port, "regseconds", regseconds,
deprecated_username ? "username" : "defaultuser", defaultuser,
"useragent", useragent, "lastms", str_lastms,
"path", path, /* Path data can be NULL */
fc, fullcontact, syslabel, sysname, SENTINEL); /* note fc and syslabel _can_ be NULL */
} else {
ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
"port", port, "regseconds", regseconds,
"useragent", useragent, "lastms", str_lastms,
deprecated_username ? "username" : "defaultuser", defaultuser,
"path", path, /* Path data can be NULL */
syslabel, sysname, SENTINEL); /* note syslabel _can_ be NULL */
}
if (fc) {
ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
"port", port, "regseconds", regseconds,
deprecated_username ? "username" : "defaultuser", defaultuser,
"useragent", useragent, "lastms", str_lastms,
fc, fullcontact, syslabel, sysname, SENTINEL); /* note fc and syslabel _can_ be NULL */
} else {
ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,