Newer
Older
static int transmit_response_with_t38_sdp(struct sip_pvt *p, char *msg, struct sip_request *req, int retrans);
static struct ast_udptl *sip_get_udptl_peer(struct ast_channel *chan);
static int sip_set_udptl_peer(struct ast_channel *chan, struct ast_udptl *udptl);
Russell Bryant
committed
/*------ Session-Timers functions --------- */
static void proc_422_rsp(struct sip_pvt *p, struct sip_request *rsp);
static int proc_session_timer(const void *vp);
static void stop_session_timer(struct sip_pvt *p);
static void start_session_timer(struct sip_pvt *p);
static void restart_session_timer(struct sip_pvt *p);
static const char *strefresher2str(enum st_refresher r);
static int parse_session_expires(const char *p_hdrval, int *const p_interval, enum st_refresher *const p_ref);
static int parse_minse(const char *p_hdrval, int *const p_interval);
static int st_get_se(struct sip_pvt *, int max);
static enum st_refresher st_get_refresher(struct sip_pvt *);
static enum st_mode st_get_mode(struct sip_pvt *);
static struct sip_st_dlg* sip_st_alloc(struct sip_pvt *const p);
/*! \brief Definition of this channel for PBX channel registration */
static const struct ast_channel_tech sip_tech = {
.description = "Session Initiation Protocol (SIP)",
.capabilities = AST_FORMAT_AUDIO_MASK, /* all audio formats */
Russell Bryant
committed
.properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER,
.requester = sip_request_call, /* called with chan unlocked */
.devicestate = sip_devicestate, /* called with chan unlocked (not chan-specific) */
.call = sip_call, /* called with chan locked */
.hangup = sip_hangup, /* called with chan locked */
.answer = sip_answer, /* called with chan locked */
.read = sip_read, /* called with chan locked */
.write = sip_write, /* called with chan locked */
.write_video = sip_write, /* called with chan locked */
.write_text = sip_write,
.indicate = sip_indicate, /* called with chan locked */
.transfer = sip_transfer, /* called with chan locked */
.fixup = sip_fixup, /* called with chan locked */
.send_digit_begin = sip_senddigit_begin, /* called with chan unlocked */
.send_digit_end = sip_senddigit_end,
.bridge = ast_rtp_bridge, /* XXX chan unlocked ? */
Joshua Colp
committed
.early_bridge = ast_rtp_early_bridge,
.send_text = sip_sendtext, /* called with chan locked */
.func_channel_read = acf_channel_read,
.get_pvt_uniqueid = sip_get_callid,
};
/*! \brief This version of the sip channel tech has no send_digit_begin
* callback so that the core knows that the channel does not want
* DTMF BEGIN frames.
* The struct is initialized just before registering the channel driver,
* and is for use with channels using SIP INFO DTMF.
*/
static struct ast_channel_tech sip_tech_info;
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
static void *sip_tcp_worker_fn(void *);
static struct ast_tls_config sip_tls_cfg;
static struct ast_tls_config default_tls_cfg;
static struct server_args sip_tcp_desc = {
.accept_fd = -1,
.master = AST_PTHREADT_NULL,
.tls_cfg = NULL,
.poll_timeout = -1,
.name = "sip tcp server",
.accept_fn = server_root,
.worker_fn = sip_tcp_worker_fn,
};
static struct server_args sip_tls_desc = {
.accept_fd = -1,
.master = AST_PTHREADT_NULL,
.tls_cfg = &sip_tls_cfg,
.poll_timeout = -1,
.name = "sip tls server",
.accept_fn = server_root,
.worker_fn = sip_tcp_worker_fn,
};
/* wrapper macro to tell whether t points to one of the sip_tech descriptors */
#define IS_SIP_TECH(t) ((t) == &sip_tech || (t) == &sip_tech_info)
/*! \brief map from an integer value to a string.
* If no match is found, return errorstring
*/
static const char *map_x_s(const struct _map_x_s *table, int x, const char *errorstring)
{
const struct _map_x_s *cur;
for (cur = table; cur->s; cur++)
if (cur->x == x)
return cur->s;
return errorstring;
}
/*! \brief map from a string to an integer value, case insensitive.
* If no match is found, return errorvalue.
*/
static int map_s_x(const struct _map_x_s *table, const char *s, int errorvalue)
{
const struct _map_x_s *cur;
for (cur = table; cur->s; cur++)
if (!strcasecmp(cur->s, s))
return cur->x;
return errorvalue;
}
/*! \brief Interface structure with callbacks used to connect to RTP module */
static struct ast_rtp_protocol sip_rtp = {
.type = "SIP",
.get_rtp_info = sip_get_rtp_peer,
.get_vrtp_info = sip_get_vrtp_peer,
.get_trtp_info = sip_get_trtp_peer,
.set_rtp_peer = sip_set_rtp_peer,
.get_codec = sip_get_codec,
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct server_instance *ser);
static void *sip_tcp_helper_thread(void *data)
{
struct sip_pvt *pvt = data;
struct server_instance *ser = pvt->socket.ser;
return _sip_tcp_helper_thread(pvt, ser);
}
static void *sip_tcp_worker_fn(void *data)
{
struct server_instance *ser = data;
return _sip_tcp_helper_thread(NULL, ser);
}
/*! \brief SIP TCP helper function */
static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct server_instance *ser)
{
int res, cl;
struct sip_request req = { 0, } , reqcpy = { 0, };
struct sip_threadinfo *me;
char buf[1024];
me = ast_calloc(1, sizeof(*me));
if (!me)
goto cleanup2;
me->threadid = pthread_self();
me->ser = ser;
if (ser->ssl)
me->type = SIP_TRANSPORT_TLS;
else
me->type = SIP_TRANSPORT_TCP;
AST_LIST_LOCK(&threadl);
AST_LIST_INSERT_TAIL(&threadl, me, list);
AST_LIST_UNLOCK(&threadl);
req.socket.lock = ast_calloc(1, sizeof(*req.socket.lock));
if (!req.socket.lock)
goto cleanup;
ast_mutex_init(req.socket.lock);
for (;;) {
memset(req.data, 0, sizeof(req.data));
req.len = 0;
req.ignore = 0;
req.socket.fd = ser->fd;
if (ser->ssl) {
req.socket.type = SIP_TRANSPORT_TLS;
req.socket.port = htons(ourport_tls);
} else {
req.socket.type = SIP_TRANSPORT_TCP;
req.socket.port = htons(ourport_tcp);
}
res = ast_wait_for_input(ser->fd, -1);
if (res < 0) {
ast_debug(1, "ast_wait_for_input returned %d\n", res);
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
goto cleanup;
}
/* Read in headers one line at a time */
while (req.len < 4 || strncmp((char *)&req.data + req.len - 4, "\r\n\r\n", 4)) {
if (req.socket.lock)
ast_mutex_lock(req.socket.lock);
if (!fgets(buf, sizeof(buf), ser->f))
goto cleanup;
if (req.socket.lock)
ast_mutex_unlock(req.socket.lock);
if (me->stop)
goto cleanup;
strncat(req.data, buf, sizeof(req.data) - req.len);
req.len = strlen(req.data);
}
parse_copy(&reqcpy, &req);
if (sscanf(get_header(&reqcpy, "Content-Length"), "%d", &cl)) {
while (cl > 0) {
if (req.socket.lock)
ast_mutex_lock(req.socket.lock);
if (!fread(buf, (cl < sizeof(buf)) ? cl : sizeof(buf), 1, ser->f))
goto cleanup;
if (req.socket.lock)
ast_mutex_unlock(req.socket.lock);
if (me->stop)
goto cleanup;
cl -= strlen(buf);
strncat(req.data, buf, sizeof(req.data) - req.len);
req.len = strlen(req.data);
}
}
req.socket.ser = ser;
handle_request_do(&req, &ser->requestor);
}
cleanup:
AST_LIST_LOCK(&threadl);
AST_LIST_REMOVE(&threadl, me, list);
AST_LIST_UNLOCK(&threadl);
ast_free(me);
cleanup2:
fclose(ser->f);
ast_free(ser);
ast_free(req.socket.lock);
return NULL;
}
Steve Murphy
committed
#define sip_pvt_lock(x) ast_mutex_lock(&x->pvt_lock)
#define sip_pvt_unlock(x) ast_mutex_unlock(&x->pvt_lock)
/*!
* helper functions to unreference various types of objects.
* By handling them this way, we don't have to declare the
* destructor on each call, which removes the chance of errors.
*/
static void unref_peer(struct sip_peer *peer)
{
ASTOBJ_UNREF(peer, sip_destroy_peer);
}
static void unref_user(struct sip_user *user)
{
ASTOBJ_UNREF(user, sip_destroy_user);
}
static void *registry_unref(struct sip_registry *reg)
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)
{
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, "");
}
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 */
if (ast_get_ip_or_srv(&proxy->ip, proxy->name, global_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;
Tilghman Lesher
committed
proxy = ast_calloc(1, sizeof(*proxy));
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;
}
if (global_outboundproxy.name[0]) {
if (sipdebug)
ast_debug(1, "OBPROXY: Applying global OBproxy to this call\n");
append_history(dialog, "OBproxy", "Using global obproxy %s", global_outboundproxy.name);
return &global_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 < (sizeof(sip_methods) / sizeof(sip_methods[0])) && !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 < (sizeof(sip_options) / sizeof(sip_options[0])); 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) & SIP_NAT_ROUTE ? &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) & SIP_NAT_ROUTE ? "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));
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 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, char *data, int len)
{
const struct sockaddr_in *dst = sip_real_dst(p);
ast_debug(1, "Trying to put '%.10s' onto %s socket...\n", data, get_transport(p->socket.type));
if (sip_prepare_socket(p) < 0)
return XMIT_ERROR;
if (p->socket.lock)
ast_mutex_lock(p->socket.lock);
if (p->socket.type & SIP_TRANSPORT_UDP)
res = sendto(p->socket.fd, data, len, 0, (const struct sockaddr *)dst, sizeof(struct sockaddr_in));
else {
if (p->socket.ser->f)
res = server_write(p->socket.ser, data, len);
else
ast_debug(1, "No p->socket.ser->f len=%d\n", len);
}
if (p->socket.lock)
ast_mutex_unlock(p->socket.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: /* Interface down */
Olle Johansson
committed
case ENETUNREACH: /* Network failure */
res = XMIT_ERROR; /* Don't bother with trying to transmit again */
Olle Johansson
committed
}
}
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) & SIP_NAT_RFC3581 ? ";rport" : "";
/* z9hG4bK is a magic cookie. See RFC 3261 section 8.1.1.7 */
ast_string_field_build(p, via, "SIP/2.0/%s %s:%d;branch=z9hG4bK%08x%s",
get_transport(p->socket.type),
ast_inet_ntoa(p->ourip.sin_addr),
ntohs(p->ourip.sin_port), 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 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 &&
(!global_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));
} else if (bindaddr.sin_addr.s_addr) {
/* no remapping, but we bind to a specific address, so use it. */
*us = bindaddr;
}
/*! \brief Append to SIP dialog history with arg list */
Olle Johansson
committed
static 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),
ntohs(dst->sin_port), pkt->data);
Olle Johansson
committed
append_history(pkt->owner, "ReTx", "%d %s", reschedule, pkt->data);
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)\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) \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.\n", pkt->owner->callid);
ast_queue_hangup(pkt->owner->owner);
} 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) {
pkt->owner->needdestroy = 1;
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.");
pkt->owner->needdestroy = 1;
/* 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);
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
*/
Olle Johansson
committed
static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, char *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 */
/* 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(dialog_ref(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
return AST_SUCCESS;
}
if (!(pkt = ast_calloc(1, sizeof(*pkt) + len + 1)))
Olle Johansson
committed
return AST_FAILURE;
/* copy data, add a terminator and save length */
/* copy other parameters from the caller */
pkt->method = sipmethod;
pkt->is_resp = resp;
pkt->is_fatal = fatal;
pkt->owner = dialog_ref(p);
p->packets = pkt; /* Add it to the queue */
pkt->timer_t1 = p->timer_t1; /* Set SIP timer T1 */
if (pkt->timer_t1)
siptimer_a = pkt->timer_t1 * 2;
/* Schedule retransmission */
pkt->retransid = 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_sched_del(sched, pkt->retransid); /* No more retransmission */
Olle Johansson
committed
pkt->retransid = -1;
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");
return 10000;
Olle Johansson
committed
if (p->subscribed == MWI_NOTIFICATION)
if (p->relatedpeer)
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);
Mark Spencer
committed
ast_queue_hangup(p->owner);
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_destroy(p); /* Go ahead and destroy dialog. All attempts to recover is done */
/* sip_destroy also absorbs the reference */
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);
Olle Johansson
committed
append_history(p, "SchedDestroy", "%d ms", ms);
p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p));
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.
*/
Olle Johansson
committed
static void sip_cancel_destroy(struct sip_pvt *p)
if (p->autokillid > -1) {
append_history(p, "CancelDestroy", "");
p->autokillid = -1;
/*! \brief Acknowledges receipt of a packet and stops retransmission */
Olle Johansson
committed
static void __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
sip_pvt_lock(p);
/* 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)
p->outboundproxy = 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);
ast_sched_del(sched, cur->retransid);
UNLINK(cur, p->packets, prev);
dialog_unref(cur->owner);
ast_free(cur);
sip_pvt_unlock(p);
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
* maybe the lock on p is not strictly necessary but there might be a race */
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;
method = (cur->method) ? cur->method : find_sip_method(cur->data);
__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 &&
(cur->is_resp || method_match(sipmethod, cur->data))) {
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 ? "Not Found" : "Found");
/*! \brief Copy SIP request, parse it */