Newer
Older
*
* This function sets pvt's outboundproxy pointer to the one referenced
* by the proxy parameter. Because proxy may be a refcounted object, and
* because pvt's old outboundproxy may also be a refcounted object, we need
* to maintain the proper refcounts.
*
* \param pvt The sip_pvt for which we wish to set the outboundproxy
* \param proxy The sip_proxy which we will point pvt towards.
* \return Returns void
*/
static void ref_proxy(struct sip_pvt *pvt, struct sip_proxy *proxy)
{
struct sip_proxy *old_obproxy = pvt->outboundproxy;
Olle Johansson
committed
/* The sip_cfg.outboundproxy is statically allocated, and so
* we don't ever need to adjust refcounts for it
*/
Olle Johansson
committed
if (proxy && proxy != &sip_cfg.outboundproxy) {
ao2_ref(proxy, +1);
}
pvt->outboundproxy = proxy;
Olle Johansson
committed
if (old_obproxy && old_obproxy != &sip_cfg.outboundproxy) {
ao2_ref(old_obproxy, -1);
}
}
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
/*!
* \brief Unlink a dialog from the dialogs container, as well as any other places
* that it may be currently stored.
*
* \note A reference to the dialog must be held before calling this function, and this
* function does not release that reference.
*/
static void *dialog_unlink_all(struct sip_pvt *dialog, int lockowner, int lockdialoglist)
{
struct sip_pkt *cp;
dialog_ref(dialog, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done");
ao2_t_unlink(dialogs, dialog, "unlinking dialog via ao2_unlink");
/* Unlink us from the owner (channel) if we have one */
if (dialog->owner) {
if (lockowner)
ast_channel_lock(dialog->owner);
ast_debug(1, "Detaching from channel %s\n", dialog->owner->name);
dialog->owner->tech_pvt = dialog_unref(dialog->owner->tech_pvt, "resetting channel dialog ptr in unlink_all");
if (lockowner)
ast_channel_unlock(dialog->owner);
}
if (dialog->registry) {
if (dialog->registry->call == dialog)
dialog->registry->call = dialog_unref(dialog->registry->call, "nulling out the registry's call dialog field in unlink_all");
dialog->registry = registry_unref(dialog->registry, "delete dialog->registry");
}
if (dialog->stateid > -1) {
ast_extension_state_del(dialog->stateid, NULL);
dialog_unref(dialog, "removing extension_state, should unref the associated dialog ptr that was stored there.");
dialog->stateid = -1; /* shouldn't we 'zero' this out? */
}
/* Remove link from peer to subscription of MWI */
if (dialog->relatedpeer && dialog->relatedpeer->mwipvt == dialog)
dialog->relatedpeer->mwipvt = dialog_unref(dialog->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt");
if (dialog->relatedpeer && dialog->relatedpeer->call == dialog)
dialog->relatedpeer->call = dialog_unref(dialog->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself");
/* remove all current packets in this dialog */
while((cp = dialog->packets)) {
dialog->packets = dialog->packets->next;
AST_SCHED_DEL(sched, cp->retransid);
dialog_unref(cp->owner, "remove all current packets in this dialog, and the pointer to the dialog too as part of __sip_destroy");
Joshua Colp
committed
if (cp->data) {
ast_free(cp->data);
}
ast_free(cp);
}
AST_SCHED_DEL_UNREF(sched, dialog->waitid, dialog_unref(dialog, "when you delete the waitid sched, you should dec the refcount for the stored dialog ptr"));
AST_SCHED_DEL_UNREF(sched, dialog->initid, dialog_unref(dialog, "when you delete the initid sched, you should dec the refcount for the stored dialog ptr"));
if (dialog->autokillid > -1)
AST_SCHED_DEL_UNREF(sched, dialog->autokillid, dialog_unref(dialog, "when you delete the autokillid sched, you should dec the refcount for the stored dialog ptr"));
if (dialog->request_queue_sched_id > -1) {
AST_SCHED_DEL_UNREF(sched, dialog->request_queue_sched_id, dialog_unref(dialog, "when you delete the request_queue_sched_id sched, you should dec the refcount for the stored dialog ptr"));
}
Joshua Colp
committed
if (dialog->t38id > -1) {
AST_SCHED_DEL_UNREF(sched, dialog->t38id, dialog_unref(dialog, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr"));
}
dialog_unref(dialog, "Let's unbump the count in the unlink so the poor pvt can disappear if it is time");
return NULL;
}
static void *registry_unref(struct sip_registry *reg, char *tag)
ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount - 1);
ASTOBJ_UNREF(reg, sip_registry_destroy);
Olle Johansson
committed
/*! \brief Add object reference to SIP registry */
static struct sip_registry *registry_addref(struct sip_registry *reg, char *tag)
Olle Johansson
committed
{
ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount + 1);
Olle Johansson
committed
return ASTOBJ_REF(reg); /* Add pointer to registry in packet */
}
/*! \brief Interface structure with callbacks used to connect to UDPTL module*/
static struct ast_udptl_protocol sip_udptl = {
type: "SIP",
get_udptl_info: sip_get_udptl_peer,
set_udptl_peer: sip_set_udptl_peer,
};
static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
/*! \brief Convert transfer status to string */
static const char *referstatus2str(enum referstatus rstatus)
return map_x_s(referstatusstrings, rstatus, "");
}
static inline void pvt_set_needdestroy(struct sip_pvt *pvt, const char *reason)
{
append_history(pvt, "NeedDestroy", "Setting needdestroy because %s", reason);
pvt->needdestroy = 1;
}
Olle Johansson
committed
/*! \brief Initialize the initital request packet in the pvt structure.
This packet is used for creating replies and future requests in
a dialog */
Kevin P. Fleming
committed
static void initialize_initreq(struct sip_pvt *p, struct sip_request *req)
Olle Johansson
committed
{
if (p->initreq.headers)
ast_debug(1, "Initializing already initialized SIP dialog %s (presumably reinvite)\n", p->callid);
else
ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
Olle Johansson
committed
/* Use this as the basis */
copy_request(&p->initreq, req);
parse_request(&p->initreq);
if (req->debug)
Olle Johansson
committed
ast_verbose("Initreq: %d headers, %d lines\n", p->initreq.headers, p->initreq.lines);
Olle Johansson
committed
}
/*! \brief Encapsulate setting of SIP_ALREADYGONE to be able to trace it with debugging */
static void sip_alreadygone(struct sip_pvt *dialog)
{
ast_debug(3, "Setting SIP_ALREADYGONE on dialog %s\n", dialog->callid);
dialog->alreadygone = 1;
/*! Resolve DNS srv name or host name in a sip_proxy structure */
static int proxy_update(struct sip_proxy *proxy)
{
/* if it's actually an IP address and not a name,
there's no need for a managed lookup */
if (!inet_aton(proxy->name, &proxy->ip.sin_addr)) {
/* Ok, not an IP address, then let's check if it's a domain or host */
/* XXX Todo - if we have proxy port, don't do SRV */
Olle Johansson
committed
if (ast_get_ip_or_srv(&proxy->ip, proxy->name, sip_cfg.srvlookup ? "_sip._udp" : NULL) < 0) {
ast_log(LOG_WARNING, "Unable to locate host '%s'\n", proxy->name);
return FALSE;
}
}
proxy->last_dnsupdate = time(NULL);
return TRUE;
}
/*! \brief Allocate and initialize sip proxy */
static struct sip_proxy *proxy_allocate(char *name, char *port, int force)
{
struct sip_proxy *proxy;
Joshua Colp
committed
if (ast_strlen_zero(name)) {
return NULL;
}
proxy = ao2_alloc(sizeof(*proxy), NULL);
if (!proxy)
return NULL;
proxy->force = force;
ast_copy_string(proxy->name, name, sizeof(proxy->name));
Joshua Colp
committed
proxy->ip.sin_port = htons((!ast_strlen_zero(port) ? atoi(port) : STANDARD_SIP_PORT));
proxy_update(proxy);
return proxy;
}
/*! \brief Get default outbound proxy or global proxy */
static struct sip_proxy *obproxy_get(struct sip_pvt *dialog, struct sip_peer *peer)
{
if (peer && peer->outboundproxy) {
if (sipdebug)
ast_debug(1, "OBPROXY: Applying peer OBproxy to this call\n");
append_history(dialog, "OBproxy", "Using peer obproxy %s", peer->outboundproxy->name);
return peer->outboundproxy;
}
Olle Johansson
committed
if (sip_cfg.outboundproxy.name[0]) {
if (sipdebug)
ast_debug(1, "OBPROXY: Applying global OBproxy to this call\n");
Olle Johansson
committed
append_history(dialog, "OBproxy", "Using global obproxy %s", sip_cfg.outboundproxy.name);
return &sip_cfg.outboundproxy;
if (sipdebug)
ast_debug(1, "OBPROXY: Not applying OBproxy to this call\n");
return NULL;
}
/*! \brief returns true if 'name' (with optional trailing whitespace)
* matches the sip method 'id'.
* Strictly speaking, SIP methods are case SENSITIVE, but we do
* a case-insensitive comparison to be more tolerant.
* following Jon Postel's rule: Be gentle in what you accept, strict with what you send
*/
static int method_match(enum sipmethod id, const char *name)
{
int len = strlen(sip_methods[id].text);
int l_name = name ? strlen(name) : 0;
/* true if the string is long enough, and ends with whitespace, and matches */
return (l_name >= len && name[len] < 33 &&
!strncasecmp(sip_methods[id].text, name, len));
}
/*! \brief find_sip_method: Find SIP method from header */
static int find_sip_method(const char *msg)
{
int i, res = 0;
if (ast_strlen_zero(msg))
return 0;
for (i = 1; i < ARRAY_LEN(sip_methods) && !res; i++) {
res = sip_methods[i].id;
}
return res;
}
/*! \brief Parse supported header in incoming packet */
static unsigned int parse_sip_options(struct sip_pvt *pvt, const char *supported)
{
char *temp;
unsigned int profile = 0;
return 0;
temp = ast_strdupa(supported);
if (sipdebug)
ast_debug(3, "Begin: parsing SIP \"Supported: %s\"\n", supported);
if ( (sep = strchr(next, ',')) != NULL)
*sep++ = '\0';
next = ast_skip_blanks(next);
if (sipdebug)
ast_debug(3, "Found SIP option: -%s-\n", next);
for (i = 0; i < ARRAY_LEN(sip_options); i++) {
if (!strcasecmp(next, sip_options[i].text)) {
profile |= sip_options[i].id;
if (sipdebug)
ast_debug(3, "Matched SIP option: %s\n", next);
}
}
Russell Bryant
committed
/* This function is used to parse both Suported: and Require: headers.
Let the caller of this function know that an unknown option tag was
encountered, so that if the UAC requires it then the request can be
rejected with a 420 response. */
if (!found)
profile |= SIP_OPT_UNKNOWN;
if (!found && sipdebug) {
if (!strncasecmp(next, "x-", 2))
ast_debug(3, "Found private SIP option, not supported: %s\n", next);
else
ast_debug(3, "Found no match for SIP option: %s (Please file bug report!)\n", next);
}
if (pvt)
pvt->sipoptions = profile;
}
/*! \brief See if we pass debug IP filter */
static inline int sip_debug_test_addr(const struct sockaddr_in *addr)
Russell Bryant
committed
if (!sipdebug)
return 0;
if (debugaddr.sin_addr.s_addr) {
if (((ntohs(debugaddr.sin_port) != 0)
&& (debugaddr.sin_port != addr->sin_port))
|| (debugaddr.sin_addr.s_addr != addr->sin_addr.s_addr))
return 0;
}
return 1;
}
Olle Johansson
committed
/*! \brief The real destination address for a write */
static const struct sockaddr_in *sip_real_dst(const struct sip_pvt *p)
{
if (p->outboundproxy)
return &p->outboundproxy->ip;
return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT) ? &p->recv : &p->sa;
Olle Johansson
committed
/*! \brief Display SIP nat mode */
static const char *sip_nat_mode(const struct sip_pvt *p)
{
return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) ? "NAT" : "no NAT";
/*! \brief Test PVT for debugging output */
static inline int sip_debug_test_pvt(struct sip_pvt *p)
{
Russell Bryant
committed
if (!sipdebug)
return sip_debug_test_addr(sip_real_dst(p));
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
/*! \brief Return int representing a bit field of transport types found in const char *transport */
static int get_transport_str2enum(const char *transport)
{
int res = 0;
if (ast_strlen_zero(transport)) {
return res;
}
if (!strcasecmp(transport, "udp")) {
res |= SIP_TRANSPORT_UDP;
}
if (!strcasecmp(transport, "tcp")) {
res |= SIP_TRANSPORT_TCP;
}
if (!strcasecmp(transport, "tls")) {
res |= SIP_TRANSPORT_TLS;
}
return res;
}
/*! \brief Return configuration of transports for a device */
static inline const char *get_transport_list(unsigned int transports) {
switch (transports) {
Brett Bryant
committed
case SIP_TRANSPORT_UDP:
return "UDP";
case SIP_TRANSPORT_TCP:
return "TCP";
case SIP_TRANSPORT_TLS:
return "TLS";
case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TCP:
return "TCP,UDP";
case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TLS:
return "TLS,UDP";
case SIP_TRANSPORT_TCP | SIP_TRANSPORT_TLS:
return "TLS,TCP";
default:
return transports ?
"TLS,TCP,UDP" : "UNKNOWN";
Brett Bryant
committed
}
}
/*! \brief Return transport as string */
static inline const char *get_transport(enum sip_transport t)
{
switch (t) {
case SIP_TRANSPORT_UDP:
return "UDP";
case SIP_TRANSPORT_TCP:
return "TCP";
case SIP_TRANSPORT_TLS:
return "TLS";
}
return "UNKNOWN";
}
/*! \brief Return transport of dialog.
\note this is based on a false assumption. We don't always use the
outbound proxy for all requests in a dialog. It depends on the
"force" parameter. The FIRST request is always sent to the ob proxy.
\todo Fix this function to work correctly
*/
Brett Bryant
committed
static inline const char *get_transport_pvt(struct sip_pvt *p)
{
if (p->outboundproxy && p->outboundproxy->transport) {
set_socket_transport(&p->socket, p->outboundproxy->transport);
}
Brett Bryant
committed
return get_transport(p->socket.type);
}
/*! \brief Transmit SIP message
Sends a SIP request or response on a given socket (in the pvt)
Called by retrans_pkt, send_request, send_response and
__sip_reliable_xmit
*/
static int __sip_xmit(struct sip_pvt *p, struct ast_str *data, int len)
const struct sockaddr_in *dst = sip_real_dst(p);
Tilghman Lesher
committed
ast_debug(2, "Trying to put '%.10s' onto %s socket destined for %s:%d\n", data->str, get_transport_pvt(p), ast_inet_ntoa(dst->sin_addr), htons(dst->sin_port));
Tilghman Lesher
committed
if (sip_prepare_socket(p) < 0)
Tilghman Lesher
committed
if (p->socket.tcptls_session)
Russell Bryant
committed
ast_mutex_lock(&p->socket.tcptls_session->lock);
if (p->socket.type & SIP_TRANSPORT_UDP) {
Tilghman Lesher
committed
res = sendto(p->socket.fd, data->str, len, 0, (const struct sockaddr *)dst, sizeof(struct sockaddr_in));
} else if (p->socket.tcptls_session) {
if (p->socket.tcptls_session->f) {
Tilghman Lesher
committed
res = ast_tcptls_server_write(p->socket.tcptls_session, data->str, len);
} else {
Russell Bryant
committed
ast_debug(2, "No p->socket.tcptls_session->f len=%d\n", len);
}
} else {
ast_debug(2, "Socket type is TCP but no tcptls_session is present to write to\n");
return XMIT_ERROR;
}
Russell Bryant
committed
if (p->socket.tcptls_session)
ast_mutex_unlock(&p->socket.tcptls_session->lock);
Olle Johansson
committed
if (res == -1) {
switch (errno) {
case EBADF: /* Bad file descriptor - seems like this is generated when the host exist, but doesn't accept the UDP packet */
case EHOSTUNREACH: /* Host can't be reached */
case ENETDOWN: /* Inteface down */
case ENETUNREACH: /* Network failure */
case ECONNREFUSED: /* ICMP port unreachable */
res = XMIT_ERROR; /* Don't bother with trying to transmit again */
Olle Johansson
committed
}
}
Tilghman Lesher
committed
if (res != len)
Russell Bryant
committed
ast_log(LOG_WARNING, "sip_xmit of %p (len %d) to %s:%d returned %d: %s\n", data, len, ast_inet_ntoa(dst->sin_addr), ntohs(dst->sin_port), res, strerror(errno));
/*! \brief Build a Via header for a request */
static void build_via(struct sip_pvt *p)
/* Work around buggy UNIDEN UIP200 firmware */
const char *rport = (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT)) ? ";rport" : "";
/* z9hG4bK is a magic cookie. See RFC 3261 section 8.1.1.7 */
snprintf(p->via, sizeof(p->via), "SIP/2.0/%s %s:%d;branch=z9hG4bK%08x%s",
get_transport_pvt(p),
ast_inet_ntoa(p->ourip.sin_addr),
ntohs(p->ourip.sin_port), (int) p->branch, rport);
/*! \brief NAT fix - decide which IP address to use for Asterisk server?
*
* Using the localaddr structure built up with localnet statements in sip.conf
* apply it to their address to see if we need to substitute our
* externip or can get away with our internal bindaddr
* 'us' is always overwritten.
static void ast_sip_ouraddrfor(struct in_addr *them, struct sockaddr_in *us, struct sip_pvt *p)
{
struct sockaddr_in theirs;
/* Set want_remap to non-zero if we want to remap 'us' to an externally
* reachable IP address and port. This is done if:
* 1. we have a localaddr list (containing 'internal' addresses marked
* as 'deny', so ast_apply_ha() will return AST_SENSE_DENY on them,
* and AST_SENSE_ALLOW on 'external' ones);
* 2. either stunaddr or externip is set, so we know what to use as the
* externally visible address;
* 3. the remote address, 'them', is external;
* 4. the address returned by ast_ouraddrfor() is 'internal' (AST_SENSE_DENY
* when passed to ast_apply_ha() so it does need to be remapped.
* This fourth condition is checked later.
*/
Jason Parker
committed
int want_remap;
Joshua Colp
committed
*us = internip; /* starting guess for the internal address */
/* now ask the system what would it use to talk to 'them' */
ast_ouraddrfor(them, &us->sin_addr);
Mark Spencer
committed
theirs.sin_addr = *them;
Jason Parker
committed
want_remap = localaddr &&
(externip.sin_addr.s_addr || stunaddr.sin_addr.s_addr) &&
ast_apply_ha(localaddr, &theirs) == AST_SENSE_ALLOW ;
if (want_remap &&
Olle Johansson
committed
(!sip_cfg.matchexterniplocally || !ast_apply_ha(localaddr, us)) ) {
/* if we used externhost or stun, see if it is time to refresh the info */
if (externexpire && time(NULL) >= externexpire) {
if (stunaddr.sin_addr.s_addr) {
ast_stun_request(sipsock, &stunaddr, NULL, &externip);
} else {
if (ast_parse_arg(externhost, PARSE_INADDR, &externip))
ast_log(LOG_NOTICE, "Warning: Re-lookup of '%s' failed!\n", externhost);
}
externexpire = time(NULL) + externrefresh;
if (externip.sin_addr.s_addr)
*us = externip;
else
ast_log(LOG_WARNING, "stun failed\n");
ast_debug(1, "Target address %s is not local, substituting externip\n",
ast_inet_ntoa(*(struct in_addr *)&them->s_addr));
/* no remapping, but we bind to a specific address, so use it. */
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
switch (p->socket.type) {
case SIP_TRANSPORT_TCP:
if (sip_tcp_desc.local_address.sin_addr.s_addr) {
*us = sip_tcp_desc.local_address;
} else {
us->sin_port = sip_tcp_desc.local_address.sin_port;
}
break;
case SIP_TRANSPORT_TLS:
if (sip_tls_desc.local_address.sin_addr.s_addr) {
*us = sip_tls_desc.local_address;
} else {
us->sin_port = sip_tls_desc.local_address.sin_port;
}
break;
case SIP_TRANSPORT_UDP:
/* fall through on purpose */
default:
if (bindaddr.sin_addr.s_addr) {
*us = bindaddr;
}
}
} else if (bindaddr.sin_addr.s_addr) {
*us = bindaddr;
}
ast_debug(3, "Setting SIP_TRANSPORT_%s with address %s:%d\n", get_transport(p->socket.type), ast_inet_ntoa(us->sin_addr), ntohs(us->sin_port));
/*! \brief Append to SIP dialog history with arg list */
static __attribute__((format(printf, 2, 0))) void append_history_va(struct sip_pvt *p, const char *fmt, va_list ap)
Mark Spencer
committed
{
Olle Johansson
committed
char buf[80], *c = buf; /* max history length */
struct sip_history *hist;
int l;
Olle Johansson
committed
vsnprintf(buf, sizeof(buf), fmt, ap);
strsep(&c, "\r\n"); /* Trim up everything after \r or \n */
l = strlen(buf) + 1;
if (!(hist = ast_calloc(1, sizeof(*hist) + l)))
Olle Johansson
committed
return;
if (!p->history && !(p->history = ast_calloc(1, sizeof(*p->history)))) {
Tilghman Lesher
committed
ast_free(hist);
Olle Johansson
committed
return;
Mark Spencer
committed
}
Olle Johansson
committed
memcpy(hist->event, buf, l);
if (p->history_entries == MAX_HISTORY_ENTRIES) {
struct sip_history *oldest;
oldest = AST_LIST_REMOVE_HEAD(p->history, list);
p->history_entries--;
ast_free(oldest);
}
Olle Johansson
committed
AST_LIST_INSERT_TAIL(p->history, hist, list);
Olle Johansson
committed
}
/*! \brief Append to SIP dialog history with arg list */
Olle Johansson
committed
static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
Olle Johansson
committed
{
Olle Johansson
committed
Olle Johansson
committed
return;
if (!p->do_history && !recordhistory && !dumphistory)
return;
va_start(ap, fmt);
append_history_va(p, fmt, ap);
va_end(ap);
Olle Johansson
committed
Olle Johansson
committed
return;
Mark Spencer
committed
}
Olle Johansson
committed
/*! \brief Retransmit SIP message if no answer (Called from scheduler) */
static int retrans_pkt(const void *data)
struct sip_pkt *pkt = (struct sip_pkt *)data, *prev, *cur = NULL;
int reschedule = DEFAULT_RETRANS;
Olle Johansson
committed
int xmitres = 0;
if (pkt->retrans < MAX_RETRANS) {
pkt->retrans++;
if (!pkt->timer_t1) { /* Re-schedule using timer_a and timer_t1 */
if (sipdebug)
ast_debug(4, "SIP TIMER: Not rescheduling id #%d:%s (Method %d) (No timer T1)\n", pkt->retransid, sip_methods[pkt->method].text, pkt->method);
} else {
int siptimer_a;
if (sipdebug)
ast_debug(4, "SIP TIMER: Rescheduling retransmission #%d (%d) %s - %d\n", pkt->retransid, pkt->retrans, sip_methods[pkt->method].text, pkt->method);
if (!pkt->timer_a)
pkt->timer_a = 2 ;
else
pkt->timer_a = 2 * pkt->timer_a;
/* For non-invites, a maximum of 4 secs */
siptimer_a = pkt->timer_t1 * pkt->timer_a; /* Double each time */
if (pkt->method != SIP_INVITE && siptimer_a > 4000)
siptimer_a = 4000;
/* Reschedule re-transmit */
reschedule = siptimer_a;
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);
if (sip_debug_test_pvt(pkt->owner)) {
const struct sockaddr_in *dst = sip_real_dst(pkt->owner);
ast_verbose("Retransmitting #%d (%s) to %s:%d:\n%s\n---\n",
pkt->retrans, sip_nat_mode(pkt->owner),
Russell Bryant
committed
ast_inet_ntoa(dst->sin_addr),
Tilghman Lesher
committed
ntohs(dst->sin_port), pkt->data->str);
Tilghman Lesher
committed
append_history(pkt->owner, "ReTx", "%d %s", reschedule, pkt->data->str);
Olle Johansson
committed
xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen);
ast_log(LOG_WARNING, "Network error on retransmit in dialog %s\n", pkt->owner->callid);
else
Olle Johansson
committed
return reschedule;
}
/* Too many retries */
Olle Johansson
committed
if (pkt->owner && 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, "Maximum retries exceeded on transmission %s for seqno %d (%s %s) -- See doc/sip-retransmit.txt.\n",
pkt->owner->callid, pkt->seqno,
pkt->is_fatal ? "Critical" : "Non-critical", pkt->is_resp ? "Response" : "Request");
} else if (pkt->method == SIP_OPTIONS && sipdebug) {
ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s) -- See doc/sip-retransmit.txt.\n", pkt->owner->callid);
Olle Johansson
committed
}
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)");
} else
append_history(pkt->owner, "MaxRetries", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
pkt->retransid = -1;
while(pkt->owner->owner && ast_channel_trylock(pkt->owner->owner)) {
sip_pvt_unlock(pkt->owner); /* SIP_PVT, not channel */
if (pkt->owner->owner && !pkt->owner->owner->hangupcause)
pkt->owner->owner->hangupcause = AST_CAUSE_NO_USER_RESPONSE;
ast_log(LOG_WARNING, "Hanging up call %s - no reply to our critical packet (see doc/sip-retransmit.txt).\n", pkt->owner->callid);
Michiel van Baak
committed
ast_queue_hangup_with_cause(pkt->owner->owner, AST_CAUSE_PROTOCOL_ERROR);
} else {
/* If no channel owner, destroy now */
Olle Johansson
committed
/* 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");
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. */
if (pkt->owner->owner)
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);
if (pkt->owner)
pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
if (pkt->data)
ast_free(pkt->data);
pkt->data = NULL;
ast_free(pkt);
return 0;
}
/* error case */
ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n");
sip_pvt_unlock(pkt->owner);
return 0;
Olle Johansson
committed
/*! \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, int seqno, int resp, struct ast_str *data, int len, int fatal, int sipmethod)
int siptimer_a = DEFAULT_RETRANS;
Olle Johansson
committed
int xmitres = 0;
if (sipmethod == SIP_INVITE) {
/* Note this is a pending invite */
p->pendinginvite = seqno;
}
/* 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 & SIP_TRANSPORT_UDP)) {
xmitres = __sip_xmit(p, data, len); /* Send packet */
if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */
append_history(p, "XmitErr", "%s", fatal ? "(Critical)" : "(Non-critical)");
return AST_FAILURE;
} else {
if (!(pkt = ast_calloc(1, sizeof(*pkt) + len + 1)))
Olle Johansson
committed
return AST_FAILURE;
/* copy data, add a terminator and save length */
if (!(pkt->data = ast_str_create(len))) {
ast_free(pkt);
return AST_FAILURE;
}
Tilghman Lesher
committed
ast_str_set(&pkt->data, 0, "%s%s", data->str, "\0");
/* copy other parameters from the caller */
pkt->method = sipmethod;
pkt->is_resp = resp;
pkt->is_fatal = fatal;
pkt->owner = dialog_ref(p, "__sip_reliable_xmit: setting pkt->owner");
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 %30d", &respid) == 1) {
pkt->response_code = respid;
}
}
pkt->timer_t1 = p->timer_t1; /* Set SIP timer T1 */
if (pkt->timer_t1)
siptimer_a = pkt->timer_t1 * 2;
/* Schedule retransmission */
Tilghman Lesher
committed
AST_SCHED_REPLACE_VARIABLE(pkt->retransid, sched, siptimer_a, retrans_pkt, pkt, 1);
if (sipdebug)
ast_debug(4, "*** SIP TIMER: Initializing retransmit timer on packet: Id #%d\n", pkt->retransid);
Olle Johansson
committed
xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen); /* 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");
Mark Michelson
committed
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");
Mark Michelson
committed
ast_free(pkt->data);
ast_free(pkt);
Olle Johansson
committed
return AST_FAILURE;
Olle Johansson
committed
return AST_SUCCESS;
/*! \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;
/* If this is a subscription, tell the phone that we got a timeout */
if (p->subscribed) {
Olle Johansson
committed
transmit_state_notify(p, AST_EXTENSION_DEACTIVATED, 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) {
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 (method_match(SIP_CANCEL, method_str) || method_match(SIP_BYE, method_str)) {
pvt_set_needdestroy(p, "autodestruct");
}
}
return 10000;
} else {
/* They've had their chance to respond. Time to bail */
__sip_pretend_ack(p);
}
Olle Johansson
committed
if (p->subscribed == MWI_NOTIFICATION)
if (p->relatedpeer)
p->relatedpeer = unref_peer(p->relatedpeer, "__sip_autodestruct: unref peer p->relatedpeer"); /* Remove link to peer. If it's realtime, make sure it's gone from memory) */
Olle Johansson
committed
/* Reset schedule ID */
p->autokillid = -1;
Olle Johansson
committed
ast_log(LOG_WARNING, "Autodestruct on dialog '%s' with owner in place (Method: %s)\n", p->callid, sip_methods[p->method].text);
Michiel van Baak
committed
ast_queue_hangup_with_cause(p->owner, AST_CAUSE_PROTOCOL_ERROR);
} 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);
dialog_unlink_all(p, TRUE, TRUE); /* once it's unlinked and unrefd everywhere, it'll be freed automagically */
/* dialog_unref(p, "unref dialog-- no other matching conditions"); -- unlink all now should finish off the dialog's references and free it. */
/* sip_destroy(p); */ /* Go ahead and destroy dialog. All attempts to recover is done */
/* sip_destroy also absorbs the reference */
dialog_unref(p, "The ref to a dialog passed to this sched callback is going out of scope; unref it.");
Olle Johansson
committed
static void sip_scheddestroy(struct sip_pvt *p, int ms)
Olle Johansson
committed
if (p->timer_t1 == 0) {
p->timer_t1 = global_t1; /* Set timer T1 if not set (RFC 3261) */
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))
Olle Johansson
committed
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");
Olle Johansson
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"));
Russell Bryant
committed
if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_schedid > 0)
stop_session_timer(p);
/*! \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.
*/
static int sip_cancel_destroy(struct sip_pvt *p)
if (p->autokillid > -1) {
int res3;
if (!(res3 = ast_sched_del(sched, p->autokillid))) {
append_history(p, "CancelDestroy", "");
p->autokillid = -1;
dialog_unref(p, "dialog unrefd because autokillid is de-sched'd");
/*! \brief Acknowledges receipt of a packet and stops retransmission
* called with p locked*/
static int __sip_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
struct sip_pkt *cur, *prev = NULL;
const char *msg = "Not Found"; /* used only for debugging */
Kevin P. Fleming
committed
/* 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){
ref_proxy(p, NULL);
}
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) {
if (!resp && (seqno == p->pendinginvite)) {
ast_debug(1, "Acked pending invite %d\n", p->pendinginvite);
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);
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
/* 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");
if (cur->data)
ast_free(cur->data);
ast_debug(1, "Stopping retransmission on '%s' of %s %d: Match %s\n",
p->callid, resp ? "Response" : "Request", seqno, msg);
/*! \brief Pretend to ack all packets
static 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;
}
cur = p->packets;
Tilghman Lesher
committed
method = (cur->method) ? cur->method : find_sip_method(cur->data->str);
__sip_ack(p, cur->seqno, cur->is_resp, method);
}
}
/*! \brief Acks receipt of packet, keep it around (used for provisional responses) */
static int __sip_semi_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
for (cur = p->packets; cur; cur = cur->next) {
if (cur->seqno == seqno && cur->is_resp == resp &&
Tilghman Lesher
committed
(cur->is_resp || method_match(sipmethod, cur->data->str))) {
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);
ast_debug(1, "(Provisional) Stopping retransmission (but retaining packet) on '%s' %s %d: %s\n", p->callid, resp ? "Response" : "Request", seqno, res == -1 ? "Not Found" : "Found");