Skip to content
Snippets Groups Projects
chan_sip.c 1.16 MiB
Newer Older
  • Learn to ignore specific revisions
  • 		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);
    
    
    	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;
    
    			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");
    
    		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");
    
    		 * 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 */
    
    			fds[0].revents = 0;
    
    			/* 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, 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;
    
    			req.socket.ws_session = NULL;
    
    			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;
    
    			fds[1].revents = 0;
    
    			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");
    
    					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");
    				}
    
    				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");
    
    	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");
    	}
    
    	if (tcptls_session) {
    
    		ast_tcptls_close_session_file(tcptls_session);
    
    		tcptls_session->parent = NULL;
    
    		ao2_ref(tcptls_session, -1);
    		tcptls_session = NULL;
    	}
    	return NULL;
    
    #ifdef REF_DEBUG
    
    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);
    }
    
    
    /*! \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 sip_pkt *cp;
    
    	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");
    
    	ao2_t_unlink(dialogs_needdestroy, dialog, "unlinking dialog_needdestroy via ao2_unlink");
    	ao2_t_unlink(dialogs_rtpcheck, dialog, "unlinking dialog_rtpcheck via ao2_unlink");
    
    	/* 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);
    
    	if (dialog->registry) {
    
    		if (dialog->registry->call == dialog) {
    
    			dialog->registry->call = dialog_unref(dialog->registry->call, "nulling out the registry's call dialog field in unlink_all");
    
    		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"));
    
    	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");
    
    static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
    	__attribute__((format(printf, 2, 3)));
    
    /*! \brief Convert transfer status to string */
    static const char *referstatus2str(enum referstatus rstatus)
    {
    	return map_x_s(referstatusstrings, rstatus, "");
    }
    
    static inline void pvt_set_needdestroy(struct sip_pvt *pvt, const char *reason)
    {
    
    	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;
    
    		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)
    
    	if (p->initreq.headers) {
    
    		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);
    
    	if (req->debug) {
    
    		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 */
    
    Mark Michelson's avatar
    Mark Michelson committed
    	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) {
    
    Mark Michelson's avatar
    Mark Michelson committed
    				ast_log(LOG_WARNING, "Unable to locate host '%s'\n", proxy->name);
    				return FALSE;
    
    Mark Michelson's avatar
    Mark Michelson committed
    
    	ast_sockaddr_set_port(&proxy->ip, proxy->port);
    
    
    	proxy->last_dnsupdate = time(NULL);
    	return TRUE;
    }
    
    /*! \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's avatar
    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)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	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)) {
    
    			res = sip_methods[i].id;
    
    Mark Spencer's avatar
    Mark Spencer committed
    }
    
    
    /*! \brief See if we pass debug IP filter */
    
    Mark Michelson's avatar
    Mark Michelson committed
    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 */
    
    Mark Michelson's avatar
    Mark Michelson committed
    static const struct ast_sockaddr *sip_real_dst(const struct sip_pvt *p)
    
    	if (p->outboundproxy) {
    
    		return &p->outboundproxy->ip;
    
    
    	return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT) ? &p->recv : &p->sa;
    }
    
    Mark Spencer's avatar
    Mark Spencer committed
    
    
    /*! \brief Display SIP nat mode */
    static const char *sip_nat_mode(const struct sip_pvt *p)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) ? "NAT" : "no NAT";
    
    Mark Spencer's avatar
    Mark Spencer committed
    }
    
    /*! \brief Test PVT for debugging output */
    static inline int sip_debug_test_pvt(struct sip_pvt *p)
    
    	if (!sipdebug) {
    
    	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 |= AST_TRANSPORT_UDP;
    
    	}
    	if (!strcasecmp(transport, "tcp")) {
    
    		res |= AST_TRANSPORT_TCP;
    
    	}
    	if (!strcasecmp(transport, "tls")) {
    
    		res |= AST_TRANSPORT_TLS;
    
    	if (!strcasecmp(transport, "ws")) {
    
    		res |= AST_TRANSPORT_WS;
    
    	}
    	if (!strcasecmp(transport, "wss")) {
    
    		res |= AST_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_UDP:
    
    	case AST_TRANSPORT_TCP:
    
    	case AST_TRANSPORT_TLS:
    
    	case AST_TRANSPORT_WS:
    	case AST_TRANSPORT_WSS:
    
    		return "WS";
    
    	return "UNKNOWN";
    
    /*! \brief Return protocol string for srv dns query */
    
    static inline const char *get_srv_protocol(enum ast_transport t)
    
    	case AST_TRANSPORT_UDP:
    
    	case AST_TRANSPORT_WS:
    
    		return "ws";
    
    	case AST_TRANSPORT_TLS:
    	case AST_TRANSPORT_TCP:
    
    	case AST_TRANSPORT_WSS:
    
    		return "wss";
    
    	}
    
    	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:
    
    /*! \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)
    
    Mark Michelson's avatar
    Mark Michelson committed
    	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) {
    
    		return XMIT_ERROR;
    
    	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));
    
    	} else if (p->socket.ws_session) {
    
    		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;
    
    	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));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    /*! \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 */
    
    Mark Michelson's avatar
    Mark Michelson committed
    	snprintf(p->via, sizeof(p->via), "SIP/2.0/%s %s;branch=z9hG4bK%08x%s",
    
    		 get_transport_pvt(p),
    
    		 ast_sockaddr_stringify_remote(&p->ourip),
    
    		 (unsigned)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
    
     * externaddr or can get away with our internal bindaddr
    
     * 'us' is always overwritten.
     */
    
    Mark Michelson's avatar
    Mark Michelson committed
    static void ast_sip_ouraddrfor(const struct ast_sockaddr *them, struct ast_sockaddr *us, struct sip_pvt *p)
    
    Mark Michelson's avatar
    Mark Michelson committed
    	struct ast_sockaddr 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. 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.
    	 */
    
    Mark Michelson's avatar
    Mark Michelson committed
    	int want_remap = 0;
    
    Mark Michelson's avatar
    Mark Michelson committed
    	ast_sockaddr_copy(us, &internip); /* starting guess for the internal address */
    
    	/* now ask the system what would it use to talk to 'them' */
    
    Mark Michelson's avatar
    Mark Michelson committed
    	ast_ouraddrfor(them, us);
    	ast_sockaddr_copy(&theirs, them);
    
    Mark Michelson's avatar
    Mark Michelson committed
    	if (ast_sockaddr_is_ipv6(&theirs)) {
    
    		if (localaddr && !ast_sockaddr_isnull(&externaddr) && !ast_sockaddr_is_any(&bindaddr)) {
    
    Mark Michelson's avatar
    Mark Michelson committed
    			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");
    
    Mark Michelson's avatar
    Mark Michelson committed
    		}
    	} else {
    		want_remap = localaddr &&
    
    			!ast_sockaddr_isnull(&externaddr) &&
    
    Mark Michelson's avatar
    Mark Michelson committed
    			ast_apply_ha(localaddr, &theirs) == AST_SENSE_ALLOW ;
    
    	if (want_remap &&
    
    	    (!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 AST_TRANSPORT_TCP:
    
    				if (!externtcpport && ast_sockaddr_port(&externaddr)) {
    					/* for consistency, default to the externaddr port */
    					externtcpport = ast_sockaddr_port(&externaddr);
    
    Mark Michelson's avatar
    Mark Michelson committed
    				}
    				ast_sockaddr_set_port(us, externtcpport);
    
    			case AST_TRANSPORT_TLS:
    
    Mark Michelson's avatar
    Mark Michelson committed
    				ast_sockaddr_set_port(us, externtlsport);
    
    			case AST_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",
    
    Mark Michelson's avatar
    Mark Michelson committed
    			  ast_sockaddr_stringify(them));
    
    		/* no remapping, but we bind to a specific address, so use it. */
    		switch (p->socket.type) {
    
    		case AST_TRANSPORT_TCP:
    
    Mark Michelson's avatar
    Mark Michelson committed
    			if (!ast_sockaddr_is_any(&sip_tcp_desc.local_address)) {
    				ast_sockaddr_copy(us,
    						  &sip_tcp_desc.local_address);
    
    Mark Michelson's avatar
    Mark Michelson committed
    				ast_sockaddr_set_port(us,
    						      ast_sockaddr_port(&sip_tcp_desc.local_address));
    
    		case AST_TRANSPORT_TLS:
    
    Mark Michelson's avatar
    Mark Michelson committed
    			if (!ast_sockaddr_is_any(&sip_tls_desc.local_address)) {
    				ast_sockaddr_copy(us,
    						  &sip_tls_desc.local_address);
    
    Mark Michelson's avatar
    Mark Michelson committed
    				ast_sockaddr_set_port(us,
    						      ast_sockaddr_port(&sip_tls_desc.local_address));
    
    Mark Michelson's avatar
    Mark Michelson committed
    			break;
    
    		case AST_TRANSPORT_UDP:
    
    			/* fall through on purpose */
    		default:
    
    Mark Michelson's avatar
    Mark Michelson committed
    			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);
    
    	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)
    
    	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);
    
    	if (!pkt->retrans_stop) {
    
    		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);
    
    			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;
    			}