Newer
Older
ast_log(LOG_ERROR, "error executing time(): %s\n", strerror(errno));
goto cleanup;
}
/*
* 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
}
struct sip_peer *_ref_peer(struct sip_peer *peer, char *tag, char *file, int line, const char *func)
if (peer)
__ao2_ref_debug(peer, 1, tag, file, line, func);
else
ast_log(LOG_ERROR, "Attempt to Ref a null peer pointer\n");
return peer;
void *_unref_peer(struct sip_peer *peer, char *tag, char *file, int line, const char *func)
if (peer)
__ao2_ref_debug(peer, -1, tag, file, line, func);
return NULL;
/*!
* 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.
*/
void *sip_unref_peer(struct sip_peer *peer, char *tag)
ao2_t_ref(peer, -1, tag);
return NULL;
}
struct sip_peer *sip_ref_peer(struct sip_peer *peer, char *tag)
{
ao2_t_ref(peer, 1, tag);
return peer;
}
#endif /* REF_DEBUG */
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)
{
ao2_t_callback(peers, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE,
match_and_cleanup_peer_sched, &flag, "initiating callback to remove marked peers");
ao2_t_callback(peers_by_ip, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE,
match_and_cleanup_peer_sched, &flag, "initiating callback to remove marked peers_by_ip");
}
/* \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);
}
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
/*! \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);
}
}
/*!
* \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");
/* 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");
if (cp->data) {
ast_free(cp->data);
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"));
Olle Johansson
committed
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"));
Matthew Jordan
committed
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"));
AST_SCHED_DEL_UNREF(sched, dialog->provisional_keepalive_sched_id, dialog_unref(dialog, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
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"));
if (dialog->stimer) {
stop_session_timer(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;
}
3425
3426
3427
3428
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
/*! \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(p->socket.ws_session, AST_WEBSOCKET_OPCODE_TEXT, ast_str_buffer(data), ast_str_strlen(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 (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(&externaddr, externhost, 0)) {
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++;
}
/*! \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 (Called from scheduler) */
static int retrans_pkt(const void *data)
Joshua Colp
committed
{
struct sip_pkt *pkt = (struct sip_pkt *)data, *prev, *cur = NULL;
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,
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;
}