Newer
Older
/*
* We cannot let the stream exclusively wait for data to arrive.
* We have to wake up the task to send outgoing messages.
*/
ast_tcptls_stream_set_exclusive_input(tcptls_session->stream_cookie, 0);
Richard Mudgett
committed
ast_tcptls_stream_set_timeout_sequence(tcptls_session->stream_cookie, ast_tvnow(),
tcptls_session->client ? -1 : (authtimeout * 1000));
for (;;) {
struct ast_str *str_save;
if (!tcptls_session->client && req.authenticated && !authenticated) {
authenticated = 1;
Richard Mudgett
committed
ast_tcptls_stream_set_timeout_disable(tcptls_session->stream_cookie);
ast_atomic_fetchadd_int(&unauth_sessions, -1);
}
/* calculate the timeout for unauthenticated server sessions */
if (!tcptls_session->client && !authenticated ) {
if ((timeout = sip_check_authtimeout(start)) < 0) {
goto cleanup;
}
if (timeout == 0) {
ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "TLS": "TCP");
goto cleanup;
}
} else {
timeout = -1;
}
if (ast_str_strlen(tcptls_session->overflow_buf) == 0) {
res = ast_poll(fds, 2, timeout); /* polls for both socket and alert_pipe */
if (res < 0) {
ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "TLS": "TCP", res);
goto cleanup;
} else if (res == 0) {
/* timeout */
ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "TLS": "TCP");
goto cleanup;
}
Brett Bryant
committed
Matthew Jordan
committed
/*
* handle the socket event, check for both reads from the socket fd or TCP overflow buffer,
* and writes from alert_pipe fd.
*/
if (fds[0].revents || (ast_str_strlen(tcptls_session->overflow_buf) > 0)) { /* there is data on the socket to be read */
/* clear request structure */
str_save = req.data;
memset(&req, 0, sizeof(req));
req.data = str_save;
ast_str_reset(req.data);
str_save = reqcpy.data;
memset(&reqcpy, 0, sizeof(reqcpy));
reqcpy.data = str_save;
ast_str_reset(reqcpy.data);
memset(buf, 0, sizeof(buf));
set_socket_transport(&req.socket, AST_TRANSPORT_TLS);
req.socket.port = htons(ourport_tls);
} else {
set_socket_transport(&req.socket, AST_TRANSPORT_TCP);
req.socket.port = htons(ourport_tcp);
}
req.socket.fd = tcptls_session->fd;
res = sip_tcptls_read(&req, tcptls_session, authenticated, start);
if (res < 0) {
goto cleanup;
req.socket.tcptls_session = tcptls_session;
handle_request_do(&req, &tcptls_session->remote_address);
}
if (fds[1].revents) { /* alert_pipe indicates there is data in the send queue to be sent */
enum sip_tcptls_alert alert;
struct tcptls_packet *packet;
Joshua Colp
committed
if (read(me->alert_pipe[0], &alert, sizeof(alert)) == -1) {
ast_log(LOG_ERROR, "read() failed: %s\n", strerror(errno));
continue;
}
switch (alert) {
case TCPTLS_ALERT_STOP:
goto cleanup;
case TCPTLS_ALERT_DATA:
ao2_lock(me);
if (!(packet = AST_LIST_REMOVE_HEAD(&me->packet_q, entry))) {
ast_log(LOG_WARNING, "TCPTLS thread alert_pipe indicated packet should be sent, but frame_q is empty\n");
Jason Parker
committed
if (ast_tcptls_server_write(tcptls_session, ast_str_buffer(packet->data), packet->len) == -1) {
ast_log(LOG_WARNING, "Failure to write to tcp/tls socket\n");
}
ao2_t_ref(packet, -1, "tcptls packet sent, this is no longer needed");
}
break;
default:
ast_log(LOG_ERROR, "Unknown tcptls thread alert '%u'\n", alert);
ast_debug(2, "Shutting down thread for %s server\n", tcptls_session->ssl ? "TLS" : "TCP");
if (tcptls_session && !tcptls_session->client && !authenticated) {
ast_atomic_fetchadd_int(&unauth_sessions, -1);
}
if (me) {
ao2_t_unlink(threadt, me, "Removing tcptls helper thread, thread is closing");
ao2_t_ref(me, -1, "Removing tcp_helper_threads threadinfo ref");
Mark Spencer
committed
}
deinit_req(&reqcpy);
deinit_req(&req);
/* if client, we own the parent session arguments and must decrement ref */
if (ca) {
ao2_t_ref(ca, -1, "closing tcptls thread, getting rid of client tcptls_session arguments");
}
Richard Mudgett
committed
ao2_lock(tcptls_session);
ast_tcptls_close_session_file(tcptls_session);
tcptls_session->parent = NULL;
Richard Mudgett
committed
ao2_unlock(tcptls_session);
Olle Johansson
committed
ao2_ref(tcptls_session, -1);
tcptls_session = NULL;
}
return NULL;
Mark Spencer
committed
}
static void peer_sched_cleanup(struct sip_peer *peer)
{
if (peer->pokeexpire != -1) {
AST_SCHED_DEL_UNREF(sched, peer->pokeexpire,
sip_unref_peer(peer, "removing poke peer ref"));
}
if (peer->expire != -1) {
AST_SCHED_DEL_UNREF(sched, peer->expire,
sip_unref_peer(peer, "remove register expire ref"));
if (peer->keepalivesend != -1) {
AST_SCHED_DEL_UNREF(sched, peer->keepalivesend,
sip_unref_peer(peer, "remove keepalive peer ref"));
}
}
typedef enum {
SIP_PEERS_MARKED,
SIP_PEERS_ALL,
} peer_unlink_flag_t;
/* this func is used with ao2_callback to unlink/delete all marked or linked
peers, depending on arg */
static int match_and_cleanup_peer_sched(void *peerobj, void *arg, int flags)
{
struct sip_peer *peer = peerobj;
peer_unlink_flag_t which = *(peer_unlink_flag_t *)arg;
if (which == SIP_PEERS_ALL || peer->the_mark) {
peer_sched_cleanup(peer);
if (peer->dnsmgr) {
ast_dnsmgr_release(peer->dnsmgr);
peer->dnsmgr = NULL;
sip_unref_peer(peer, "Release peer from dnsmgr");
}
return CMP_MATCH;
}
return 0;
}
static void unlink_peers_from_tables(peer_unlink_flag_t flag)
{
struct ao2_iterator *peers_iter;
/*
* We must remove the ref outside of the peers container to prevent
* a deadlock condition when unsubscribing from stasis while it is
* invoking a subscription event callback.
*/
peers_iter = ao2_t_callback(peers, OBJ_UNLINK | OBJ_MULTIPLE,
match_and_cleanup_peer_sched, &flag, "initiating callback to remove marked peers");
if (peers_iter) {
ao2_iterator_destroy(peers_iter);
}
peers_iter = ao2_t_callback(peers_by_ip, OBJ_UNLINK | OBJ_MULTIPLE,
match_and_cleanup_peer_sched, &flag, "initiating callback to remove marked peers_by_ip");
if (peers_iter) {
ao2_iterator_destroy(peers_iter);
}
}
/* \brief Unlink all marked peers from ao2 containers */
static void unlink_marked_peers_from_tables(void)
{
unlink_peers_from_tables(SIP_PEERS_MARKED);
}
static void unlink_all_peers_from_tables(void)
{
unlink_peers_from_tables(SIP_PEERS_ALL);
}
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
/*! \brief maintain proper refcounts for a sip_pvt's outboundproxy
*
* 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;
/* The sip_cfg.outboundproxy is statically allocated, and so
* we don't ever need to adjust refcounts for it
*/
if (proxy && proxy != &sip_cfg.outboundproxy) {
ao2_ref(proxy, +1);
}
pvt->outboundproxy = proxy;
if (old_obproxy && old_obproxy != &sip_cfg.outboundproxy) {
ao2_ref(old_obproxy, -1);
}
}
static void do_dialog_unlink_sched_items(struct sip_pvt *dialog)
{
struct sip_pkt *cp;
/* remove all current packets in this dialog */
sip_pvt_lock(dialog);
while ((cp = dialog->packets)) {
/* Unlink and destroy the packet object. */
dialog->packets = dialog->packets->next;
AST_SCHED_DEL_UNREF(sched, cp->retransid,
ao2_t_ref(cp, -1, "Stop scheduled packet retransmission"));
ao2_t_ref(cp, -1, "Packet retransmission list");
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
}
sip_pvt_unlock(dialog);
AST_SCHED_DEL_UNREF(sched, dialog->waitid,
dialog_unref(dialog, "Stop scheduled waitid"));
AST_SCHED_DEL_UNREF(sched, dialog->initid,
dialog_unref(dialog, "Stop scheduled initid"));
AST_SCHED_DEL_UNREF(sched, dialog->reinviteid,
dialog_unref(dialog, "Stop scheduled reinviteid"));
AST_SCHED_DEL_UNREF(sched, dialog->autokillid,
dialog_unref(dialog, "Stop scheduled autokillid"));
AST_SCHED_DEL_UNREF(sched, dialog->request_queue_sched_id,
dialog_unref(dialog, "Stop scheduled request_queue_sched_id"));
AST_SCHED_DEL_UNREF(sched, dialog->provisional_keepalive_sched_id,
dialog_unref(dialog, "Stop scheduled provisional keepalive"));
AST_SCHED_DEL_UNREF(sched, dialog->t38id,
dialog_unref(dialog, "Stop scheduled t38id"));
if (dialog->stimer) {
dialog->stimer->st_active = FALSE;
do_stop_session_timer(dialog);
}
}
/* Run by the sched thread. */
static int __dialog_unlink_sched_items(const void *data)
{
struct sip_pvt *dialog = (void *) data;
do_dialog_unlink_sched_items(dialog);
dialog_unref(dialog, "Stop scheduled items for unlink action");
return 0;
}
/*!
* \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.
*/
void dialog_unlink_all(struct sip_pvt *dialog)
struct ast_channel *owner;
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");
Stefan Schmidt
committed
ao2_t_unlink(dialogs_needdestroy, dialog, "unlinking dialog_needdestroy via ao2_unlink");
ao2_t_unlink(dialogs_rtpcheck, dialog, "unlinking dialog_rtpcheck via ao2_unlink");
Olle Johansson
committed
/* Unlink us from the owner (channel) if we have one */
owner = sip_pvt_lock_full(dialog);
if (owner) {
ast_debug(1, "Detaching from channel %s\n", ast_channel_name(owner));
ast_channel_tech_pvt_set(owner, dialog_unref(ast_channel_tech_pvt(owner), "resetting channel dialog ptr in unlink_all"));
ast_channel_unlock(owner);
ast_channel_unref(owner);
sip_set_owner(dialog, NULL);
sip_pvt_unlock(dialog);
if (dialog->registry->call == dialog) {
dialog->registry->call = dialog_unref(dialog->registry->call, "nulling out the registry's call dialog field in unlink_all");
ao2_t_replace(dialog->registry, NULL, "delete dialog->registry");
if (dialog->stateid != -1) {
ast_extension_state_del(dialog->stateid, cb_extensionstate);
dialog->stateid = -1;
}
/* 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");
dialog_ref(dialog, "Stop scheduled items for unlink action");
if (ast_sched_add(sched, 0, __dialog_unlink_sched_items, dialog) < 0) {
/*
* Uh Oh. Fall back to unscheduling things immediately
* despite the potential deadlock risk.
*/
dialog_unref(dialog, "Failed to schedule stop scheduled items for unlink action");
do_dialog_unlink_sched_items(dialog);
dialog_unref(dialog, "Let's unbump the count in the unlink so the poor pvt can disappear if it is time");
static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
Olle Johansson
committed
/*! \brief Convert transfer status to string */
static const char *referstatus2str(enum referstatus rstatus)
{
return map_x_s(referstatusstrings, rstatus, "");
}
Olle Johansson
committed
static inline void pvt_set_needdestroy(struct sip_pvt *pvt, const char *reason)
{
if (pvt->final_destruction_scheduled) {
return; /* This is already scheduled for final destruction, let the scheduler take care of it. */
}
append_history(pvt, "NeedDestroy", "Setting needdestroy because %s", reason);
if (!pvt->needdestroy) {
pvt->needdestroy = 1;
Stefan Schmidt
committed
ao2_t_link(dialogs_needdestroy, pvt, "link pvt into dialogs_needdestroy container");
}
/*! \brief Initialize the initital request packet in the pvt structure.
This packet is used for creating replies and future requests in
a dialog */
static void initialize_initreq(struct sip_pvt *p, struct sip_request *req)
ast_debug(1, "Initializing already initialized SIP dialog %s (presumably reinvite)\n", p->callid);
ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
/* Use this as the basis */
copy_request(&p->initreq, req);
parse_request(&p->initreq);
ast_verbose("Initreq: %d headers, %d lines\n", p->initreq.headers, p->initreq.lines);
/*! \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 (!ast_sockaddr_parse(&proxy->ip, proxy->name, 0)) {
/* 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 */
proxy->ip.ss.ss_family = get_address_family_filter(AST_TRANSPORT_UDP); /* Filter address family */
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;
ast_sockaddr_set_port(&proxy->ip, proxy->port);
proxy->last_dnsupdate = time(NULL);
return TRUE;
}
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
/*! \brief Parse proxy string and return an ao2_alloc'd proxy. If dest is
* non-NULL, no allocation is performed and dest is used instead.
* On error NULL is returned. */
static struct sip_proxy *proxy_from_config(const char *proxy, int sipconf_lineno, struct sip_proxy *dest)
{
char *mutable_proxy, *sep, *name;
int allocated = 0;
if (!dest) {
dest = ao2_alloc(sizeof(struct sip_proxy), NULL);
if (!dest) {
ast_log(LOG_WARNING, "Unable to allocate config storage for proxy\n");
return NULL;
}
allocated = 1;
}
/* Format is: [transport://]name[:port][,force] */
mutable_proxy = ast_skip_blanks(ast_strdupa(proxy));
sep = strchr(mutable_proxy, ',');
if (sep) {
*sep++ = '\0';
dest->force = !strncasecmp(ast_skip_blanks(sep), "force", 5);
} else {
dest->force = FALSE;
}
sip_parse_host(mutable_proxy, sipconf_lineno, &name, &dest->port, &dest->transport);
/* Check that there is a name at all */
if (ast_strlen_zero(name)) {
if (allocated) {
ao2_ref(dest, -1);
} else {
dest->name[0] = '\0';
}
return NULL;
}
ast_copy_string(dest->name, name, sizeof(dest->name));
/* Resolve host immediately */
proxy_update(dest);
return dest;
}
/*! \brief converts ascii port to int representation. If no
* pt buffer is provided or the pt has errors when being converted
* to an int value, the port provided as the standard is used.
*/
unsigned int port_str2int(const char *pt, unsigned int standard)
{
int port = standard;
if (ast_strlen_zero(pt) || (sscanf(pt, "%30d", &port) != 1) || (port < 1) || (port > 65535)) {
port = standard;
Olle Johansson
committed
/*! \brief Get default outbound proxy or global proxy */
static struct sip_proxy *obproxy_get(struct sip_pvt *dialog, struct sip_peer *peer)
{
if (dialog && dialog->options && dialog->options->outboundproxy) {
if (sipdebug) {
ast_debug(1, "OBPROXY: Applying dialplan set OBproxy to this call\n");
append_history(dialog, "OBproxy", "Using dialplan obproxy %s", dialog->options->outboundproxy->name);
return dialog->options->outboundproxy;
}
if (peer && peer->outboundproxy) {
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 (sip_cfg.outboundproxy.name[0]) {
ast_debug(1, "OBPROXY: Applying global OBproxy to this call\n");
append_history(dialog, "OBproxy", "Using global obproxy %s", sip_cfg.outboundproxy.name);
return &sip_cfg.outboundproxy;
}
ast_debug(1, "OBPROXY: Not applying OBproxy to this call\n");
/*! \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 && 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;
Matthew Jordan
committed
if (ast_strlen_zero(msg)) {
for (i = 1; i < ARRAY_LEN(sip_methods) && !res; i++) {
if (method_match(i, msg)) {
/*! \brief See if we pass debug IP filter */
static inline int sip_debug_test_addr(const struct ast_sockaddr *addr)
/* Can't debug if sipdebug is not enabled */
if (!sipdebug) {
return 0;
}
/* A null debug_addr means we'll debug any address */
if (ast_sockaddr_isnull(&debugaddr)) {
return 1;
}
/* If no port was specified for a debug address, just compare the
* addresses, otherwise compare the address and port
*/
if (ast_sockaddr_port(&debugaddr)) {
return !ast_sockaddr_cmp(&debugaddr, addr);
} else {
return !ast_sockaddr_cmp_addr(&debugaddr, addr);
}
/*! \brief The real destination address for a write */
static const struct ast_sockaddr *sip_real_dst(const struct sip_pvt *p)
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;
}
/*! \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)
return sip_debug_test_addr(sip_real_dst(p));
}
/*! \brief Return int representing a bit field of transport types found in const char *transport */
static int get_transport_str2enum(const char *transport)
if (ast_strlen_zero(transport)) {
return res;
if (!strcasecmp(transport, "udp")) {
}
if (!strcasecmp(transport, "tcp")) {
}
if (!strcasecmp(transport, "tls")) {
}
if (!strcasecmp(transport, "wss")) {
/*! \brief Return configuration of transports for a device */
static inline const char *get_transport_list(unsigned int transports)
{
char *buf;
if (!transports) {
return "UNKNOWN";
}
if (!(buf = ast_threadstorage_get(&sip_transport_str_buf, SIP_TRANSPORT_STR_BUFSIZE))) {
return "";
}
memset(buf, 0, SIP_TRANSPORT_STR_BUFSIZE);
if (transports & AST_TRANSPORT_UDP) {
strncat(buf, "UDP,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf));
}
if (transports & AST_TRANSPORT_TCP) {
strncat(buf, "TCP,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf));
}
if (transports & AST_TRANSPORT_TLS) {
strncat(buf, "TLS,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf));
if (transports & AST_TRANSPORT_WS) {
strncat(buf, "WS,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf));
}
if (transports & AST_TRANSPORT_WSS) {
strncat(buf, "WSS,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf));
}
/* Remove the trailing ',' if present */
if (strlen(buf)) {
buf[strlen(buf) - 1] = 0;
}
return buf;
/*! \brief Return transport as string */
const char *sip_get_transport(enum ast_transport t)
case AST_TRANSPORT_WS:
case AST_TRANSPORT_WSS:
/*! \brief Return protocol string for srv dns query */
static inline const char *get_srv_protocol(enum ast_transport t)
case AST_TRANSPORT_TLS:
case AST_TRANSPORT_TCP:
}
return "udp";
}
/*! \brief Return service string for srv dns query */
static inline const char *get_srv_service(enum ast_transport t)
case AST_TRANSPORT_TCP:
case AST_TRANSPORT_UDP:
case AST_TRANSPORT_WS:
case AST_TRANSPORT_TLS:
case AST_TRANSPORT_WSS:
return "sips";
}
return "sip";
}
/*! \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
*/
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);
}
return sip_get_transport(p->socket.type);
/*!
* \internal
* \brief Transmit SIP message
*
* \details
* Sends a SIP request or response on a given socket (in the pvt)
* \note
* Called by retrans_pkt, send_request, send_response and __sip_reliable_xmit
*
* \return length of transmitted message, XMIT_ERROR on known network failures -1 on other failures.
*/
static int __sip_xmit(struct sip_pvt *p, struct ast_str *data)
const struct ast_sockaddr *dst = sip_real_dst(p);
ast_debug(2, "Trying to put '%.11s' onto %s socket destined for %s\n", ast_str_buffer(data), get_transport_pvt(p), ast_sockaddr_stringify(dst));
if (sip_prepare_socket(p) < 0) {
if (p->socket.type == AST_TRANSPORT_UDP) {
res = ast_sendto(p->socket.fd, ast_str_buffer(data), ast_str_strlen(data), 0, dst);
} else if (p->socket.tcptls_session) {
res = sip_tcptls_write(p->socket.tcptls_session, ast_str_buffer(data), ast_str_strlen(data));
if (!(res = ast_websocket_write_string(p->socket.ws_session, ast_str_buffer(data)))) {
/* The WebSocket API just returns 0 on success and -1 on failure, while this code expects the payload length to be returned */
res = ast_str_strlen(data);
}
} else {
ast_debug(2, "Socket type is TCP but no tcptls_session is present to write to\n");
return XMIT_ERROR;
Mark Spencer
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 */
case ENETUNREACH: /* Network failure */
case ECONNREFUSED: /* ICMP port unreachable */
res = XMIT_ERROR; /* Don't bother with trying to transmit again */
}
if (res != ast_str_strlen(data)) {
ast_log(LOG_WARNING, "sip_xmit of %p (len %zu) to %s returned %d: %s\n", data, ast_str_strlen(data), ast_sockaddr_stringify(dst), 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;branch=z9hG4bK%08x%s",
ast_sockaddr_stringify_remote(&p->ourip),
/*! \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
* externaddr or can get away with our internal bindaddr
* 'us' is always overwritten.
*/
static void ast_sip_ouraddrfor(const struct ast_sockaddr *them, struct ast_sockaddr *us, struct sip_pvt *p)
/* 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. externaddr 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.
*/
ast_sockaddr_copy(us, &internip); /* starting guess for the internal address */
/* now ask the system what would it use to talk to 'them' */
ast_ouraddrfor(them, us);
ast_sockaddr_copy(&theirs, them);
if (ast_sockaddr_is_ipv6(&theirs) && !ast_sockaddr_is_ipv4_mapped(&theirs)) {
if (localaddr && !ast_sockaddr_isnull(&externaddr) && !ast_sockaddr_is_any(&bindaddr)) {
ast_log(LOG_WARNING, "Address remapping activated in sip.conf "
"but we're using IPv6, which doesn't need it. Please "
"remove \"localnet\" and/or \"externaddr\" settings.\n");
!ast_sockaddr_isnull(&externaddr) &&
ast_apply_ha(localaddr, &theirs) == AST_SENSE_ALLOW ;
(!sip_cfg.matchexternaddrlocally || !ast_apply_ha(localaddr, us)) ) {
/* if we used externhost, see if it is time to refresh the info */
if (externexpire && time(NULL) >= externexpire) {
if (ast_sockaddr_resolve_first_af(&externaddr, externhost, 0, AST_AF_INET)) {
ast_log(LOG_NOTICE, "Warning: Re-lookup of '%s' failed!\n", externhost);
}
externexpire = time(NULL) + externrefresh;
}
if (!ast_sockaddr_isnull(&externaddr)) {
ast_sockaddr_copy(us, &externaddr);
if (!externtcpport && ast_sockaddr_port(&externaddr)) {
/* for consistency, default to the externaddr port */
externtcpport = ast_sockaddr_port(&externaddr);
}
ast_sockaddr_set_port(us, externtcpport);
if (!ast_sockaddr_port(&externaddr)) {
ast_sockaddr_set_port(us, ast_sockaddr_port(&bindaddr));
}
break;
ast_debug(1, "Target address %s is not local, substituting externaddr\n",
/* no remapping, but we bind to a specific address, so use it. */
switch (p->socket.type) {
if (!ast_sockaddr_is_any(&sip_tcp_desc.local_address)) {
ast_sockaddr_copy(us,
&sip_tcp_desc.local_address);
ast_sockaddr_set_port(us,
ast_sockaddr_port(&sip_tcp_desc.local_address));
if (!ast_sockaddr_is_any(&sip_tls_desc.local_address)) {
ast_sockaddr_copy(us,
&sip_tls_desc.local_address);
ast_sockaddr_set_port(us,
ast_sockaddr_port(&sip_tls_desc.local_address));
/* fall through on purpose */
default:
if (!ast_sockaddr_is_any(&bindaddr)) {
ast_sockaddr_copy(us, &bindaddr);
if (!ast_sockaddr_port(us)) {
ast_sockaddr_set_port(us, ast_sockaddr_port(&bindaddr));
}
ast_debug(3, "Setting AST_TRANSPORT_%s with address %s\n", sip_get_transport(p->socket.type), ast_sockaddr_stringify(us));
/*! \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)
{
char buf[80], *c = buf; /* max history length */
struct sip_history *hist;
int l;
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))) {
if (!p->history && !(p->history = ast_calloc(1, sizeof(*p->history)))) {
ast_free(hist);
return;
}
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);
AST_LIST_INSERT_TAIL(p->history, hist, list);
p->history_entries++;
if (log_level != -1) {
ast_log_dynamic_level(log_level, "%s\n", buf);
}
}
/*! \brief Append to SIP dialog history with arg list */
static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
if (!p->do_history && !recordhistory && !dumphistory) {
va_start(ap, fmt);
append_history_va(p, fmt, ap);
va_end(ap);
}
/*!
* \brief Retransmit SIP message if no answer
*
* \note Run by the sched thread.
*/
static int retrans_pkt(const void *data)
Joshua Colp
committed
{
struct sip_pkt *pkt = (struct sip_pkt *) data;
struct sip_pkt *prev;
struct sip_pkt *cur;
struct ast_channel *owner_chan;
int reschedule = DEFAULT_RETRANS;
int xmitres = 0;
/* how many ms until retrans timeout is reached */
int64_t diff = pkt->retrans_stop_time - ast_tvdiff_ms(ast_tvnow(), pkt->time_sent);
/* Do not retransmit if time out is reached. This will be negative if the time between
* the first transmission and now is larger than our timeout period. This is a fail safe
* check in case the scheduler gets behind or the clock is changed. */
if ((diff <= 0) || (diff > pkt->retrans_stop_time)) {
pkt->retrans_stop = 1;
}
/* Lock channel PVT */
sip_pvt_lock(pkt->owner);
Joshua Colp
committed
if (!pkt->retrans_stop) {
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);
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,