Newer
Older
res = recv(tcptls_session->fd, readbuf, sizeof(readbuf) - 1, 0);
if (res < 0) {
ast_debug(2, "SIP TCP server error when receiving data\n");
return -1;
} else if (res == 0) {
ast_debug(2, "SIP TCP server has shut down\n");
return -1;
}
readbuf[res] = '\0';
ast_str_append(&req->data, 0, "%s", readbuf);
} else {
ast_str_append(&req->data, 0, "%s", ast_str_buffer(tcptls_session->overflow_buf));
ast_str_reset(tcptls_session->overflow_buf);
}
datalen = ast_str_strlen(req->data);
if (datalen > SIP_MAX_PACKET_SIZE) {
ast_log(LOG_WARNING, "Rejecting TCP packet from '%s' because way too large: %zu\n",
ast_sockaddr_stringify(&tcptls_session->remote_address), datalen);
return -1;
}
message_integrity = check_message_integrity(&req->data, &tcptls_session->overflow_buf);
}
return 0;
}
/*! \brief SIP TCP thread management function
This function reads from the socket, parses the packet into a request
*/
static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_session)
int res, timeout = -1, authenticated = 0, flags;
struct sip_request req = { 0, } , reqcpy = { 0, };
struct sip_threadinfo *me = NULL;
char buf[1024] = "";
struct pollfd fds[2] = { { 0 }, { 0 }, };
struct ast_tcptls_session_args *ca = NULL;
/* If this is a server session, then the connection has already been
* setup. Check if the authlimit has been reached and if not create the
* threadinfo object so we can access this thread for writing.
*
* if this is a client connection more work must be done.
* 1. We own the parent session args for a client connection. This pointer needs
* to be held on to so we can decrement it's ref count on thread destruction.
* 2. The threadinfo object was created before this thread was launched, however
* it must be found within the threadt table.
* 3. Last, the tcptls_session must be started.
*/
if (!tcptls_session->client) {
if (ast_atomic_fetchadd_int(&unauth_sessions, +1) >= authlimit) {
/* unauth_sessions is decremented in the cleanup code */
goto cleanup;
}
if ((flags = fcntl(tcptls_session->fd, F_GETFL)) == -1) {
ast_log(LOG_ERROR, "error setting socket to non blocking mode, fcntl() failed: %s\n", strerror(errno));
goto cleanup;
}
flags |= O_NONBLOCK;
if (fcntl(tcptls_session->fd, F_SETFL, flags) == -1) {
ast_log(LOG_ERROR, "error setting socket to non blocking mode, fcntl() failed: %s\n", strerror(errno));
goto cleanup;
}
if (!(me = sip_threadinfo_create(tcptls_session, tcptls_session->ssl ? SIP_TRANSPORT_TLS : SIP_TRANSPORT_TCP))) {
goto cleanup;
}
ao2_t_ref(me, +1, "Adding threadinfo ref for tcp_helper_thread");
} else {
struct sip_threadinfo tmp = {
.tcptls_session = tcptls_session,
};
if ((!(ca = tcptls_session->parent)) ||
(!(me = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread"))) ||
(!(tcptls_session = ast_tcptls_client_start(tcptls_session)))) {
goto cleanup;
}
flags = 1;
if (setsockopt(tcptls_session->fd, SOL_SOCKET, SO_KEEPALIVE, &flags, sizeof(flags))) {
ast_log(LOG_ERROR, "error enabling TCP keep-alives on sip socket: %s\n", strerror(errno));
goto cleanup;
}
me->threadid = pthread_self();
ast_debug(2, "Starting thread for %s server\n", tcptls_session->ssl ? "TLS" : "TCP");
/* set up pollfd to watch for reads on both the socket and the alert_pipe */
fds[0].fd = tcptls_session->fd;
fds[1].fd = me->alert_pipe[0];
fds[0].events = fds[1].events = POLLIN | POLLPRI;
Brett Bryant
committed
if (!(req.data = ast_str_create(SIP_MIN_PACKET))) {
}
if (!(reqcpy.data = ast_str_create(SIP_MIN_PACKET))) {
if(time(&start) == -1) {
ast_log(LOG_ERROR, "error executing time(): %s\n", strerror(errno));
goto cleanup;
}
for (;;) {
struct ast_str *str_save;
if (!tcptls_session->client && req.authenticated && !authenticated) {
authenticated = 1;
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
/*
* 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));
if (tcptls_session->ssl) {
set_socket_transport(&req.socket, SIP_TRANSPORT_TLS);
req.socket.port = htons(ourport_tls);
} else {
set_socket_transport(&req.socket, SIP_TRANSPORT_TCP);
req.socket.port = htons(ourport_tcp);
}
req.socket.fd = tcptls_session->fd;
if (tcptls_session->ssl) {
res = sip_tls_read(&req, &reqcpy, tcptls_session, authenticated, start, me);
} else {
res = sip_tcp_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 '%d'\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");
}
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
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");
}
/* \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);
}
/* \brief Unlink single peer from all ao2 containers */
static void unlink_peer_from_tables(struct sip_peer *peer)
{
ao2_t_unlink(peers, peer, "ao2_unlink of peer from peers table");
if (!ast_sockaddr_isnull(&peer->addr)) {
ao2_t_unlink(peers_by_ip, peer, "ao2_unlink of peer from peers_by_ip table");
}
}
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
/*! \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);
dialog->owner = 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");
dialog->registry = registry_unref(dialog->registry, "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"));
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");
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);
return NULL;
}
/*! \brief Add object reference to SIP registry */
static struct sip_registry *registry_addref(struct sip_registry *reg, char *tag)
{
ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount + 1);
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)));
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(SIP_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;
}
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
/*! \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;
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")) {
res |= SIP_TRANSPORT_UDP;
}
if (!strcasecmp(transport, "tcp")) {
res |= SIP_TRANSPORT_TCP;
}
if (!strcasecmp(transport, "tls")) {
res |= SIP_TRANSPORT_TLS;
if (!strcasecmp(transport, "ws")) {
res |= SIP_TRANSPORT_WS;
}
if (!strcasecmp(transport, "wss")) {
res |= SIP_TRANSPORT_WSS;
}
/*! \brief Return configuration of transports for a device */
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
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 & SIP_TRANSPORT_UDP) {
strncat(buf, "UDP,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf));
}
if (transports & SIP_TRANSPORT_TCP) {
strncat(buf, "TCP,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf));
}
if (transports & SIP_TRANSPORT_TLS) {
strncat(buf, "TLS,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf));
if (transports & SIP_TRANSPORT_WS) {
strncat(buf, "WS,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf));
}
if (transports & SIP_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 sip_transport t)
{
switch (t) {
case SIP_TRANSPORT_UDP:
return "UDP";
case SIP_TRANSPORT_TCP:
return "TCP";
case SIP_TRANSPORT_TLS:
return "TLS";
case SIP_TRANSPORT_WS:
case SIP_TRANSPORT_WSS:
return "WS";
/*! \brief Return protocol string for srv dns query */
static inline const char *get_srv_protocol(enum sip_transport t)
{
switch (t) {
case SIP_TRANSPORT_UDP:
return "udp";
case SIP_TRANSPORT_WS:
return "ws";
case SIP_TRANSPORT_TLS:
case SIP_TRANSPORT_TCP:
return "tcp";
case SIP_TRANSPORT_WSS:
return "wss";
}
return "udp";
}
/*! \brief Return service string for srv dns query */
static inline const char *get_srv_service(enum sip_transport t)
{
switch (t) {
case SIP_TRANSPORT_TCP:
case SIP_TRANSPORT_UDP:
return "sip";
case SIP_TRANSPORT_TLS:
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 == SIP_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);
switch (p->socket.type) {
case SIP_TRANSPORT_TCP:
if (!externtcpport && ast_sockaddr_port(&externaddr)) {
/* for consistency, default to the externaddr port */
externtcpport = ast_sockaddr_port(&externaddr);
}
ast_sockaddr_set_port(us, externtcpport);
break;
case SIP_TRANSPORT_TLS:
break;
case SIP_TRANSPORT_UDP:
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. */