Add support for SIP over WebSocket.

This allows SIP traffic to be exchanged over a WebSocket connection which is useful for rtcweb.

Review: https://reviewboard.asterisk.org/r/2008


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@370072 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Joshua Colp
2012-07-16 12:35:04 +00:00
parent f9c3585d73
commit e938737570
8 changed files with 263 additions and 139 deletions

View File

@@ -105,6 +105,8 @@ SIP Changes
* Add support for lightweight NAT keepalive. If enabled a blank packet will * Add support for lightweight NAT keepalive. If enabled a blank packet will
be sent to the remote host at a given interval to keep the NAT mapping open. be sent to the remote host at a given interval to keep the NAT mapping open.
This can be enabled using the keepalive configuration option. This can be enabled using the keepalive configuration option.
* Add support for WebSocket transport. This can be configured using 'ws' or 'wss'
as the transport.
Chan_local changes Chan_local changes
------------------ ------------------

View File

@@ -1167,6 +1167,12 @@ static void temp_pvt_cleanup(void *);
/*! \brief A per-thread temporary pvt structure */ /*! \brief A per-thread temporary pvt structure */
AST_THREADSTORAGE_CUSTOM(ts_temp_pvt, temp_pvt_init, temp_pvt_cleanup); AST_THREADSTORAGE_CUSTOM(ts_temp_pvt, temp_pvt_init, temp_pvt_cleanup);
/*! \brief A per-thread buffer for transport to string conversion */
AST_THREADSTORAGE(sip_transport_str_buf);
/*! \brief Size of the SIP transport buffer */
#define SIP_TRANSPORT_STR_BUFSIZE 128
/*! \brief Authentication container for realm authentication */ /*! \brief Authentication container for realm authentication */
static struct sip_auth_container *authl = NULL; static struct sip_auth_container *authl = NULL;
/*! \brief Global authentication container protection while adjusting the references. */ /*! \brief Global authentication container protection while adjusting the references. */
@@ -2525,6 +2531,54 @@ static void *sip_tcp_worker_fn(void *data)
return _sip_tcp_helper_thread(tcptls_session); return _sip_tcp_helper_thread(tcptls_session);
} }
/*! \brief SIP WebSocket connection handler */
static void sip_websocket_callback(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers)
{
int res;
if (ast_websocket_set_nonblock(session)) {
goto end;
}
while ((res = ast_wait_for_input(ast_websocket_fd(session), -1)) > 0) {
char *payload;
uint64_t payload_len;
enum ast_websocket_opcode opcode;
int fragmented;
if (ast_websocket_read(session, &payload, &payload_len, &opcode, &fragmented)) {
/* We err on the side of caution and terminate the session if any error occurs */
break;
}
if (opcode == AST_WEBSOCKET_OPCODE_TEXT || opcode == AST_WEBSOCKET_OPCODE_BINARY) {
struct sip_request req = { 0, };
if (!(req.data = ast_str_create(payload_len))) {
goto end;
}
if (ast_str_set(&req.data, -1, "%s", payload) == AST_DYNSTR_BUILD_FAILED) {
deinit_req(&req);
goto end;
}
req.socket.fd = ast_websocket_fd(session);
set_socket_transport(&req.socket, ast_websocket_is_secure(session) ? SIP_TRANSPORT_WSS : SIP_TRANSPORT_WS);
req.socket.ws_session = session;
handle_request_do(&req, ast_websocket_remote_address(session));
deinit_req(&req);
} else if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
break;
}
}
end:
ast_websocket_unref(session);
}
/*! \brief Check if the authtimeout has expired. /*! \brief Check if the authtimeout has expired.
* \param start the time when the session started * \param start the time when the session started
* *
@@ -2800,6 +2854,7 @@ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_s
we receive is not the same - we should generate an error */ we receive is not the same - we should generate an error */
req.socket.tcptls_session = tcptls_session; req.socket.tcptls_session = tcptls_session;
req.socket.ws_session = NULL;
handle_request_do(&req, &tcptls_session->remote_address); handle_request_do(&req, &tcptls_session->remote_address);
} }
@@ -3306,29 +3361,53 @@ static int get_transport_str2enum(const char *transport)
if (!strcasecmp(transport, "tls")) { if (!strcasecmp(transport, "tls")) {
res |= SIP_TRANSPORT_TLS; res |= SIP_TRANSPORT_TLS;
} }
if (!strcasecmp(transport, "ws")) {
res |= SIP_TRANSPORT_WS;
}
if (!strcasecmp(transport, "wss")) {
res |= SIP_TRANSPORT_WSS;
}
return res; return res;
} }
/*! \brief Return configuration of transports for a device */ /*! \brief Return configuration of transports for a device */
static inline const char *get_transport_list(unsigned int transports) { static inline const char *get_transport_list(unsigned int transports)
switch (transports) { {
case SIP_TRANSPORT_UDP: char *buf;
return "UDP";
case SIP_TRANSPORT_TCP: if (!transports) {
return "TCP"; return "UNKNOWN";
case SIP_TRANSPORT_TLS:
return "TLS";
case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TCP:
return "TCP,UDP";
case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TLS:
return "TLS,UDP";
case SIP_TRANSPORT_TCP | SIP_TRANSPORT_TLS:
return "TLS,TCP";
default:
return transports ?
"TLS,TCP,UDP" : "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 */ /*! \brief Return transport as string */
@@ -3341,6 +3420,9 @@ const char *sip_get_transport(enum sip_transport t)
return "TCP"; return "TCP";
case SIP_TRANSPORT_TLS: case SIP_TRANSPORT_TLS:
return "TLS"; return "TLS";
case SIP_TRANSPORT_WS:
case SIP_TRANSPORT_WSS:
return "WS";
} }
return "UNKNOWN"; return "UNKNOWN";
@@ -3352,9 +3434,13 @@ static inline const char *get_srv_protocol(enum sip_transport t)
switch (t) { switch (t) {
case SIP_TRANSPORT_UDP: case SIP_TRANSPORT_UDP:
return "udp"; return "udp";
case SIP_TRANSPORT_WS:
return "ws";
case SIP_TRANSPORT_TLS: case SIP_TRANSPORT_TLS:
case SIP_TRANSPORT_TCP: case SIP_TRANSPORT_TCP:
return "tcp"; return "tcp";
case SIP_TRANSPORT_WSS:
return "wss";
} }
return "udp"; return "udp";
@@ -3366,8 +3452,10 @@ static inline const char *get_srv_service(enum sip_transport t)
switch (t) { switch (t) {
case SIP_TRANSPORT_TCP: case SIP_TRANSPORT_TCP:
case SIP_TRANSPORT_UDP: case SIP_TRANSPORT_UDP:
case SIP_TRANSPORT_WS:
return "sip"; return "sip";
case SIP_TRANSPORT_TLS: case SIP_TRANSPORT_TLS:
case SIP_TRANSPORT_WSS:
return "sips"; return "sips";
} }
return "sip"; return "sip";
@@ -3414,6 +3502,11 @@ static int __sip_xmit(struct sip_pvt *p, struct ast_str *data)
res = ast_sendto(p->socket.fd, data->str, ast_str_strlen(data), 0, dst); res = ast_sendto(p->socket.fd, data->str, ast_str_strlen(data), 0, dst);
} else if (p->socket.tcptls_session) { } else if (p->socket.tcptls_session) {
res = sip_tcptls_write(p->socket.tcptls_session, data->str, ast_str_strlen(data)); res = sip_tcptls_write(p->socket.tcptls_session, data->str, ast_str_strlen(data));
} else if (p->socket.ws_session) {
if (!(res = ast_websocket_write(p->socket.ws_session, AST_WEBSOCKET_OPCODE_TEXT, data->str, 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 { } else {
ast_debug(2, "Socket type is TCP but no tcptls_session is present to write to\n"); ast_debug(2, "Socket type is TCP but no tcptls_session is present to write to\n");
return XMIT_ERROR; return XMIT_ERROR;
@@ -4730,6 +4823,9 @@ static void sip_destroy_peer(struct sip_peer *peer)
if (peer->socket.tcptls_session) { if (peer->socket.tcptls_session) {
ao2_ref(peer->socket.tcptls_session, -1); ao2_ref(peer->socket.tcptls_session, -1);
peer->socket.tcptls_session = NULL; peer->socket.tcptls_session = NULL;
} else if (peer->socket.ws_session) {
ast_websocket_unref(peer->socket.ws_session);
peer->socket.ws_session = NULL;
} }
ast_cc_config_params_destroy(peer->cc_params); ast_cc_config_params_destroy(peer->cc_params);
@@ -5298,10 +5394,15 @@ static void copy_socket_data(struct sip_socket *to_sock, const struct sip_socket
if (to_sock->tcptls_session) { if (to_sock->tcptls_session) {
ao2_ref(to_sock->tcptls_session, -1); ao2_ref(to_sock->tcptls_session, -1);
to_sock->tcptls_session = NULL; to_sock->tcptls_session = NULL;
} else if (to_sock->ws_session) {
ast_websocket_unref(to_sock->ws_session);
to_sock->ws_session = NULL;
} }
if (from_sock->tcptls_session) { if (from_sock->tcptls_session) {
ao2_ref(from_sock->tcptls_session, +1); ao2_ref(from_sock->tcptls_session, +1);
} else if (from_sock->ws_session) {
ast_websocket_ref(from_sock->ws_session);
} }
*to_sock = *from_sock; *to_sock = *from_sock;
@@ -6012,6 +6113,9 @@ void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist)
if (p->socket.tcptls_session) { if (p->socket.tcptls_session) {
ao2_ref(p->socket.tcptls_session, -1); ao2_ref(p->socket.tcptls_session, -1);
p->socket.tcptls_session = NULL; p->socket.tcptls_session = NULL;
} else if (p->socket.ws_session) {
ast_websocket_unref(p->socket.ws_session);
p->socket.ws_session = NULL;
} }
if (p->peerauth) { if (p->peerauth) {
@@ -9334,7 +9438,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
int image = FALSE; int image = FALSE;
int text = FALSE; int text = FALSE;
int processed_crypto = FALSE; int processed_crypto = FALSE;
char protocol[5] = {0,}; char protocol[6] = {0,};
int x; int x;
numberofports = 0; numberofports = 0;
@@ -9354,8 +9458,8 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
/* Check for 'audio' media offer */ /* Check for 'audio' media offer */
if (strncmp(m, "audio ", 6) == 0) { if (strncmp(m, "audio ", 6) == 0) {
if ((sscanf(m, "audio %30u/%30u RTP/%4s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || if ((sscanf(m, "audio %30u/%30u RTP/%5s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) ||
(sscanf(m, "audio %30u RTP/%4s %n", &x, protocol, &len) == 2 && len > 0)) { (sscanf(m, "audio %30u RTP/%5s %n", &x, protocol, &len) == 2 && len > 0)) {
codecs = m + len; codecs = m + len;
/* produce zero-port m-line since it may be needed later /* produce zero-port m-line since it may be needed later
* length is "m=audio 0 RTP/" + protocol + " " + codecs + "\0" */ * length is "m=audio 0 RTP/" + protocol + " " + codecs + "\0" */
@@ -9377,9 +9481,21 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
ast_log(LOG_WARNING, "%d ports offered for audio media, not supported by Asterisk. Will try anyway...\n", numberofports); ast_log(LOG_WARNING, "%d ports offered for audio media, not supported by Asterisk. Will try anyway...\n", numberofports);
} }
if (!strcmp(protocol, "SAVP")) { if (!strcmp(protocol, "SAVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) {
ast_log(LOG_WARNING, "Received SAVPF profle in audio offer but AVPF is not enabled: %s\n", m);
continue;
} else if (!strcmp(protocol, "SAVP") && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) {
ast_log(LOG_WARNING, "Received SAVP profile in audio offer but AVPF is enabled: %s\n", m);
continue;
} else if (!strcmp(protocol, "SAVP") || !strcmp(protocol, "SAVPF")) {
secure_audio = 1; secure_audio = 1;
} else if (strcmp(protocol, "AVP")) { } else if (!strcmp(protocol, "AVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) {
ast_log(LOG_WARNING, "Received AVPF profile in audio offer but AVPF is not enabled: %s\n", m);
continue;
} else if (!strcmp(protocol, "AVP") && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) {
ast_log(LOG_WARNING, "Received AVP profile in audio offer but AVPF is enabled: %s\n", m);
continue;
} else if (strcmp(protocol, "AVP") && strcmp(protocol, "AVPF")) {
ast_log(LOG_WARNING, "Unknown RTP profile in audio offer: %s\n", m); ast_log(LOG_WARNING, "Unknown RTP profile in audio offer: %s\n", m);
continue; continue;
} }
@@ -9414,8 +9530,8 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
} }
/* Check for 'video' media offer */ /* Check for 'video' media offer */
else if (strncmp(m, "video ", 6) == 0) { else if (strncmp(m, "video ", 6) == 0) {
if ((sscanf(m, "video %30u/%30u RTP/%4s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || if ((sscanf(m, "video %30u/%30u RTP/%5s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) ||
(sscanf(m, "video %30u RTP/%4s %n", &x, protocol, &len) == 2 && len > 0)) { (sscanf(m, "video %30u RTP/%5s %n", &x, protocol, &len) == 2 && len > 0)) {
codecs = m + len; codecs = m + len;
/* produce zero-port m-line since it may be needed later /* produce zero-port m-line since it may be needed later
* length is "m=video 0 RTP/" + protocol + " " + codecs + "\0" */ * length is "m=video 0 RTP/" + protocol + " " + codecs + "\0" */
@@ -9437,9 +9553,21 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
ast_log(LOG_WARNING, "%d ports offered for video stream, not supported by Asterisk. Will try anyway...\n", numberofports); ast_log(LOG_WARNING, "%d ports offered for video stream, not supported by Asterisk. Will try anyway...\n", numberofports);
} }
if (!strcmp(protocol, "SAVP")) { if (!strcmp(protocol, "SAVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) {
ast_log(LOG_WARNING, "Received SAVPF profle in video offer but AVPF is not enabled: %s\n", m);
continue;
} else if (!strcmp(protocol, "SAVP") && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) {
ast_log(LOG_WARNING, "Received SAVP profile in video offer but AVPF is enabled: %s\n", m);
continue;
} else if (!strcmp(protocol, "SAVP") || !strcmp(protocol, "SAVPF")) {
secure_video = 1; secure_video = 1;
} else if (strcmp(protocol, "AVP")) { } else if (!strcmp(protocol, "AVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) {
ast_log(LOG_WARNING, "Received AVPF profile in video offer but AVPF is not enabled: %s\n", m);
continue;
} else if (!strcmp(protocol, "AVP") && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) {
ast_log(LOG_WARNING, "Received AVP profile in video offer but AVPF is enabled: %s\n", m);
continue;
} else if (strcmp(protocol, "AVP") && strcmp(protocol, "AVPF")) {
ast_log(LOG_WARNING, "Unknown RTP profile in video offer: %s\n", m); ast_log(LOG_WARNING, "Unknown RTP profile in video offer: %s\n", m);
continue; continue;
} }
@@ -9474,18 +9602,18 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
} }
/* Check for 'text' media offer */ /* Check for 'text' media offer */
else if (strncmp(m, "text ", 5) == 0) { else if (strncmp(m, "text ", 5) == 0) {
if ((sscanf(m, "text %30u/%30u RTP/AVP %n", &x, &numberofports, &len) == 2 && len > 0) || if ((sscanf(m, "text %30u/%30u RTP/%s %n", &x, &numberofports, protocol, &len) == 2 && len > 0) ||
(sscanf(m, "text %30u RTP/AVP %n", &x, &len) == 1 && len > 0)) { (sscanf(m, "text %30u RTP/%s %n", &x, protocol, &len) == 1 && len > 0)) {
codecs = m + len; codecs = m + len;
/* produce zero-port m-line since it may be needed later /* produce zero-port m-line since it may be needed later
* length is "m=text 0 RTP/AVP " + codecs + "\0" */ * length is "m=text 0 RTP/" + protocol + " " + codecs + "\0" */
if (!(offer->decline_m_line = ast_malloc(17 + strlen(codecs) + 1))) { if (!(offer->decline_m_line = ast_malloc(13 + strlen(protocol) + 1 + strlen(codecs) + 1))) {
ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n");
res = -1; res = -1;
goto process_sdp_cleanup; goto process_sdp_cleanup;
} }
/* guaranteed to be exactly the right length */ /* guaranteed to be exactly the right length */
sprintf(offer->decline_m_line, "m=text 0 RTP/AVP %s", codecs); sprintf(offer->decline_m_line, "m=text 0 RTP/%s %s", protocol, codecs);
if (x == 0) { if (x == 0) {
ast_log(LOG_WARNING, "Ignoring text stream offer because port number is zero\n"); ast_log(LOG_WARNING, "Ignoring text stream offer because port number is zero\n");
@@ -9497,6 +9625,17 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
ast_log(LOG_WARNING, "%d ports offered for text stream, not supported by Asterisk. Will try anyway...\n", numberofports); ast_log(LOG_WARNING, "%d ports offered for text stream, not supported by Asterisk. Will try anyway...\n", numberofports);
} }
if (!strcmp(protocol, "AVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) {
ast_log(LOG_WARNING, "Received AVPF profile in text offer but AVPF is not enabled: %s\n", m);
continue;
} else if (!strcmp(protocol, "AVP") && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) {
ast_log(LOG_WARNING, "Received AVP profile in text offer but AVPF is enabled: %s\n", m);
continue;
} else if (strcmp(protocol, "AVP") && strcmp(protocol, "AVPF")) {
ast_log(LOG_WARNING, "Unknown RTP profile in text offer: %s\n", m);
continue;
}
if (has_media_stream(p, SDP_TEXT)) { if (has_media_stream(p, SDP_TEXT)) {
ast_log(LOG_WARNING, "Declining non-primary text stream: %s\n", m); ast_log(LOG_WARNING, "Declining non-primary text stream: %s\n", m);
continue; continue;
@@ -10692,7 +10831,7 @@ static void add_route(struct sip_request *req, struct sip_route *route)
*/ */
static void set_destination(struct sip_pvt *p, char *uri) static void set_destination(struct sip_pvt *p, char *uri)
{ {
char *h, *maddr, hostname[256]; char *trans, *h, *maddr, hostname[256];
int hn; int hn;
int debug=sip_debug_test_pvt(p); int debug=sip_debug_test_pvt(p);
int tls_on = FALSE; int tls_on = FALSE;
@@ -10700,6 +10839,16 @@ static void set_destination(struct sip_pvt *p, char *uri)
if (debug) if (debug)
ast_verbose("set_destination: Parsing <%s> for address/port to send to\n", uri); ast_verbose("set_destination: Parsing <%s> for address/port to send to\n", uri);
if ((trans = strcasestr(uri, ";transport="))) {
trans += strlen(";transport=");
if (!strncasecmp(trans, "ws", 2)) {
if (debug)
ast_verbose("set_destination: URI is for WebSocket, we can't set destination\n");
return;
}
}
/* Find and parse hostname */ /* Find and parse hostname */
h = strchr(uri, '@'); h = strchr(uri, '@');
if (h) if (h)
@@ -12026,6 +12175,15 @@ static void get_crypto_attrib(struct sip_pvt *p, struct sip_srtp *srtp, const ch
} }
} }
static char *get_sdp_rtp_profile(const struct sip_pvt *p, unsigned int secure)
{
if (ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) {
return secure ? "SAVPF" : "AVPF";
} else {
return secure ? "SAVP" : "AVP";
}
}
/*! \brief Add Session Description Protocol message /*! \brief Add Session Description Protocol message
If oldsdp is TRUE, then the SDP version number is not incremented. This mechanism If oldsdp is TRUE, then the SDP version number is not incremented. This mechanism
@@ -12186,7 +12344,7 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
if (needvideo) { if (needvideo) {
get_crypto_attrib(p, p->vsrtp, &v_a_crypto); get_crypto_attrib(p, p->vsrtp, &v_a_crypto);
ast_str_append(&m_video, 0, "m=video %d RTP/%s", ast_sockaddr_port(&vdest), ast_str_append(&m_video, 0, "m=video %d RTP/%s", ast_sockaddr_port(&vdest),
v_a_crypto ? "SAVP" : "AVP"); get_sdp_rtp_profile(p, a_crypto ? 1 : 0));
/* Build max bitrate string */ /* Build max bitrate string */
if (p->maxcallbitrate) if (p->maxcallbitrate)
@@ -12207,7 +12365,7 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
ast_verbose("Lets set up the text sdp\n"); ast_verbose("Lets set up the text sdp\n");
get_crypto_attrib(p, p->tsrtp, &t_a_crypto); get_crypto_attrib(p, p->tsrtp, &t_a_crypto);
ast_str_append(&m_text, 0, "m=text %d RTP/%s", ast_sockaddr_port(&tdest), ast_str_append(&m_text, 0, "m=text %d RTP/%s", ast_sockaddr_port(&tdest),
t_a_crypto ? "SAVP" : "AVP"); get_sdp_rtp_profile(p, a_crypto ? 1 : 0));
if (debug) { /* XXX should I use tdest below ? */ if (debug) { /* XXX should I use tdest below ? */
ast_verbose("Text is at %s\n", ast_sockaddr_stringify(&taddr)); ast_verbose("Text is at %s\n", ast_sockaddr_stringify(&taddr));
} }
@@ -12224,7 +12382,7 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
get_crypto_attrib(p, p->srtp, &a_crypto); get_crypto_attrib(p, p->srtp, &a_crypto);
ast_str_append(&m_audio, 0, "m=audio %d RTP/%s", ast_sockaddr_port(&dest), ast_str_append(&m_audio, 0, "m=audio %d RTP/%s", ast_sockaddr_port(&dest),
a_crypto ? "SAVP" : "AVP"); get_sdp_rtp_profile(p, a_crypto ? 1 : 0));
/* Now, start adding audio codecs. These are added in this order: /* Now, start adding audio codecs. These are added in this order:
- First what was requested by the calling channel - First what was requested by the calling channel
@@ -14639,6 +14797,9 @@ static void set_socket_transport(struct sip_socket *socket, int transport)
if (socket->tcptls_session) { if (socket->tcptls_session) {
ao2_ref(socket->tcptls_session, -1); ao2_ref(socket->tcptls_session, -1);
socket->tcptls_session = NULL; socket->tcptls_session = NULL;
} else if (socket->ws_session) {
ast_websocket_unref(socket->ws_session);
socket->ws_session = NULL;
} }
} }
} }
@@ -14661,6 +14822,9 @@ static int expire_register(const void *data)
if (peer->socket.tcptls_session) { if (peer->socket.tcptls_session) {
ao2_ref(peer->socket.tcptls_session, -1); ao2_ref(peer->socket.tcptls_session, -1);
peer->socket.tcptls_session = NULL; peer->socket.tcptls_session = NULL;
} else if (peer->socket.ws_session) {
ast_websocket_unref(peer->socket.ws_session);
peer->socket.ws_session = NULL;
} }
manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", peer->name); manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", peer->name);
@@ -16841,6 +17005,11 @@ static void check_via(struct sip_pvt *p, struct sip_request *req)
ast_copy_string(via, sip_get_header(req, "Via"), sizeof(via)); ast_copy_string(via, sip_get_header(req, "Via"), sizeof(via));
/* If this is via WebSocket we don't use the Via header contents at all */
if (!strncasecmp(via, "SIP/2.0/WS", 10)) {
return;
}
/* Work on the leftmost value of the topmost Via header */ /* Work on the leftmost value of the topmost Via header */
c = strchr(via, ','); c = strchr(via, ',');
if (c) if (c)
@@ -20984,6 +21153,9 @@ static void parse_moved_contact(struct sip_pvt *p, struct sip_request *req, char
if (p->socket.tcptls_session) { if (p->socket.tcptls_session) {
ao2_ref(p->socket.tcptls_session, -1); ao2_ref(p->socket.tcptls_session, -1);
p->socket.tcptls_session = NULL; p->socket.tcptls_session = NULL;
} else if (p->socket.ws_session) {
ast_websocket_unref(p->socket.ws_session);
p->socket.ws_session = NULL;
} }
set_socket_transport(&p->socket, transport); set_socket_transport(&p->socket, transport);
@@ -27196,6 +27368,9 @@ static int sip_prepare_socket(struct sip_pvt *p)
(s->tcptls_session->fd != -1)) { (s->tcptls_session->fd != -1)) {
return s->tcptls_session->fd; return s->tcptls_session->fd;
} }
if ((s->type & (SIP_TRANSPORT_WS | SIP_TRANSPORT_WSS))) {
return s->ws_session ? ast_websocket_fd(s->ws_session) : -1;
}
/*! \todo Check this... This might be wrong, depending on the proxy configuration /*! \todo Check this... This might be wrong, depending on the proxy configuration
If proxy is in "force" mode its correct. If proxy is in "force" mode its correct.
@@ -29188,6 +29363,10 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
if (!strncasecmp(trans, "udp", 3)) { if (!strncasecmp(trans, "udp", 3)) {
peer->transports |= SIP_TRANSPORT_UDP; peer->transports |= SIP_TRANSPORT_UDP;
} else if (!strncasecmp(trans, "wss", 3)) {
peer->transports |= SIP_TRANSPORT_WSS;
} else if (!strncasecmp(trans, "ws", 2)) {
peer->transports |= SIP_TRANSPORT_WS;
} else if (sip_cfg.tcp_enabled && !strncasecmp(trans, "tcp", 3)) { } else if (sip_cfg.tcp_enabled && !strncasecmp(trans, "tcp", 3)) {
peer->transports |= SIP_TRANSPORT_TCP; peer->transports |= SIP_TRANSPORT_TCP;
} else if (default_tls_cfg.enabled && !strncasecmp(trans, "tls", 3)) { } else if (default_tls_cfg.enabled && !strncasecmp(trans, "tls", 3)) {
@@ -29538,6 +29717,8 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
ast_set2_flag(&peer->flags[2], !strcasecmp(v->value, "32"), SIP_PAGE3_SRTP_TAG_32); ast_set2_flag(&peer->flags[2], !strcasecmp(v->value, "32"), SIP_PAGE3_SRTP_TAG_32);
} else if (!strcasecmp(v->name, "snom_aoc_enabled")) { } else if (!strcasecmp(v->name, "snom_aoc_enabled")) {
ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_SNOM_AOC); ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_SNOM_AOC);
} else if (!strcasecmp(v->name, "avpf")) {
ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_USE_AVPF);
} }
} }
@@ -29651,7 +29832,6 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
* 3. The socket.type is not set yet. */ * 3. The socket.type is not set yet. */
if (((peer->socket.type != peer->default_outbound_transport) && (peer->expire == -1)) || if (((peer->socket.type != peer->default_outbound_transport) && (peer->expire == -1)) ||
!(peer->socket.type & peer->transports) || !(peer->socket.type)) { !(peer->socket.type & peer->transports) || !(peer->socket.type)) {
set_socket_transport(&peer->socket, peer->default_outbound_transport); set_socket_transport(&peer->socket, peer->default_outbound_transport);
} }
@@ -30189,6 +30369,10 @@ static int reload_config(enum channelreloadreason reason)
default_transports |= SIP_TRANSPORT_TCP; default_transports |= SIP_TRANSPORT_TCP;
} else if (!strncasecmp(trans, "tls", 3)) { } else if (!strncasecmp(trans, "tls", 3)) {
default_transports |= SIP_TRANSPORT_TLS; default_transports |= SIP_TRANSPORT_TLS;
} else if (!strncasecmp(trans, "wss", 3)) {
default_transports |= SIP_TRANSPORT_WSS;
} else if (!strncasecmp(trans, "ws", 2)) {
default_transports |= SIP_TRANSPORT_WS;
} else { } else {
ast_log(LOG_NOTICE, "'%s' is not a valid transport type. if no other is specified, udp will be used.\n", trans); ast_log(LOG_NOTICE, "'%s' is not a valid transport type. if no other is specified, udp will be used.\n", trans);
} }
@@ -32598,6 +32782,8 @@ static int load_module(void)
sip_register_tests(); sip_register_tests();
network_change_event_subscribe(); network_change_event_subscribe();
ast_websocket_add_protocol("sip", sip_websocket_callback);
return AST_MODULE_LOAD_SUCCESS; return AST_MODULE_LOAD_SUCCESS;
} }
@@ -32610,6 +32796,8 @@ static int unload_module(void)
struct ao2_iterator i; struct ao2_iterator i;
int wait_count; int wait_count;
ast_websocket_remove_protocol("sip", sip_websocket_callback);
network_change_event_unsubscribe(); network_change_event_unsubscribe();
acl_change_event_unsubscribe(); acl_change_event_unsubscribe();

View File

@@ -35,6 +35,7 @@
#include "asterisk/indications.h" #include "asterisk/indications.h"
#include "asterisk/security_events.h" #include "asterisk/security_events.h"
#include "asterisk/features.h" #include "asterisk/features.h"
#include "asterisk/http_websocket.h"
#ifndef FALSE #ifndef FALSE
#define FALSE 0 #define FALSE 0
@@ -369,10 +370,11 @@
#define SIP_PAGE3_NAT_AUTO_RPORT (1 << 2) /*!< DGP: Set SIP_NAT_FORCE_RPORT when NAT is detected */ #define SIP_PAGE3_NAT_AUTO_RPORT (1 << 2) /*!< DGP: Set SIP_NAT_FORCE_RPORT when NAT is detected */
#define SIP_PAGE3_NAT_AUTO_COMEDIA (1 << 3) /*!< DGP: Set SIP_PAGE2_SYMMETRICRTP when NAT is detected */ #define SIP_PAGE3_NAT_AUTO_COMEDIA (1 << 3) /*!< DGP: Set SIP_PAGE2_SYMMETRICRTP when NAT is detected */
#define SIP_PAGE3_DIRECT_MEDIA_OUTGOING (1 << 4) /*!< DP: Only send direct media reinvites on outgoing calls */ #define SIP_PAGE3_DIRECT_MEDIA_OUTGOING (1 << 4) /*!< DP: Only send direct media reinvites on outgoing calls */
#define SIP_PAGE3_USE_AVPF (1 << 5) /*!< DGP: Support a minimal AVPF-compatible profile */
#define SIP_PAGE3_FLAGS_TO_COPY \ #define SIP_PAGE3_FLAGS_TO_COPY \
(SIP_PAGE3_SNOM_AOC | SIP_PAGE3_SRTP_TAG_32 | SIP_PAGE3_NAT_AUTO_RPORT | SIP_PAGE3_NAT_AUTO_COMEDIA | \ (SIP_PAGE3_SNOM_AOC | SIP_PAGE3_SRTP_TAG_32 | SIP_PAGE3_NAT_AUTO_RPORT | SIP_PAGE3_NAT_AUTO_COMEDIA | \
SIP_PAGE3_DIRECT_MEDIA_OUTGOING) SIP_PAGE3_DIRECT_MEDIA_OUTGOING | SIP_PAGE3_USE_AVPF)
#define CHECK_AUTH_BUF_INITLEN 256 #define CHECK_AUTH_BUF_INITLEN 256
@@ -564,6 +566,8 @@ enum sip_transport {
SIP_TRANSPORT_UDP = 1, /*!< Unreliable transport for SIP, needs retransmissions */ SIP_TRANSPORT_UDP = 1, /*!< Unreliable transport for SIP, needs retransmissions */
SIP_TRANSPORT_TCP = 1 << 1, /*!< Reliable, but unsecure */ SIP_TRANSPORT_TCP = 1 << 1, /*!< Reliable, but unsecure */
SIP_TRANSPORT_TLS = 1 << 2, /*!< TCP/TLS - reliable and secure transport for signalling */ SIP_TRANSPORT_TLS = 1 << 2, /*!< TCP/TLS - reliable and secure transport for signalling */
SIP_TRANSPORT_WS = 1 << 3, /*!< WebSocket, unsecure */
SIP_TRANSPORT_WSS = 1 << 4, /*!< WebSocket, secure */
}; };
/*! \brief Automatic peer registration behavior /*! \brief Automatic peer registration behavior
@@ -769,6 +773,7 @@ struct sip_socket {
int fd; /*!< Filed descriptor, the actual socket */ int fd; /*!< Filed descriptor, the actual socket */
uint16_t port; uint16_t port;
struct ast_tcptls_session_instance *tcptls_session; /* If tcp or tls, a socket manager */ struct ast_tcptls_session_instance *tcptls_session; /* If tcp or tls, a socket manager */
struct ast_websocket *ws_session; /*! If ws or wss, a WebSocket session */
}; };
/*! \brief sip_request: The data grabbed from the UDP socket /*! \brief sip_request: The data grabbed from the UDP socket
@@ -1284,7 +1289,7 @@ struct sip_peer {
enum sip_transport default_outbound_transport; /*!< Peer Registration may change the default outbound transport. enum sip_transport default_outbound_transport; /*!< Peer Registration may change the default outbound transport.
If register expires, default should be reset. to this value */ If register expires, default should be reset. to this value */
/* things that don't belong in flags */ /* things that don't belong in flags */
unsigned short transports:3; /*!< Transports (enum sip_transport) that are acceptable for this peer */ unsigned short transports:5; /*!< Transports (enum sip_transport) that are acceptable for this peer */
unsigned short is_realtime:1; /*!< this is a 'realtime' peer */ unsigned short is_realtime:1; /*!< this is a 'realtime' peer */
unsigned short rt_fromcontact:1;/*!< copy fromcontact from realtime */ unsigned short rt_fromcontact:1;/*!< copy fromcontact from realtime */
unsigned short host_dynamic:1; /*!< Dynamic Peers register with Asterisk */ unsigned short host_dynamic:1; /*!< Dynamic Peers register with Asterisk */

View File

@@ -218,7 +218,7 @@ int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_in
return -1; return -1;
} }
if (session_params) { if (!ast_strlen_zero(session_params)) {
ast_log(LOG_WARNING, "Unsupported crypto parameters: %s", session_params); ast_log(LOG_WARNING, "Unsupported crypto parameters: %s", session_params);
return -1; return -1;
} }

View File

@@ -45,8 +45,10 @@ static enum ast_security_event_transport_type security_event_get_transport(const
case SIP_TRANSPORT_UDP: case SIP_TRANSPORT_UDP:
return AST_SECURITY_EVENT_TRANSPORT_UDP; return AST_SECURITY_EVENT_TRANSPORT_UDP;
case SIP_TRANSPORT_TCP: case SIP_TRANSPORT_TCP:
case SIP_TRANSPORT_WS:
return AST_SECURITY_EVENT_TRANSPORT_TCP; return AST_SECURITY_EVENT_TRANSPORT_TCP;
case SIP_TRANSPORT_TLS: case SIP_TRANSPORT_TLS:
case SIP_TRANSPORT_WSS:
return AST_SECURITY_EVENT_TRANSPORT_TLS; return AST_SECURITY_EVENT_TRANSPORT_TLS;
} }

View File

@@ -745,7 +745,7 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls
; ;
;register => tls://username:xxxxxx@sip-tls-proxy.example.org ;register => tls://username:xxxxxx@sip-tls-proxy.example.org
; ;
; The 'transport' part defaults to 'udp' but may also be 'tcp' or 'tls'. ; The 'transport' part defaults to 'udp' but may also be 'tcp', 'tls', 'ws', or 'wss'.
; Using 'udp://' explicitly is also useful in case the username part ; Using 'udp://' explicitly is also useful in case the username part
; contains a '/' ('user/name'). ; contains a '/' ('user/name').
@@ -977,7 +977,10 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls
; on outgoing calls to a peer. Calls will fail with HANGUPCAUSE=58 if ; on outgoing calls to a peer. Calls will fail with HANGUPCAUSE=58 if
; the peer does not support SRTP. Defaults to no. ; the peer does not support SRTP. Defaults to no.
;encryption_taglen=80 ; Set the auth tag length offered in the INVITE either 32/80 default 80 ;encryption_taglen=80 ; Set the auth tag length offered in the INVITE either 32/80 default 80
;
;avpf=yes ; Enable inter-operability with media streams using the AVPF RTP profile.
; This will cause all offers and answers to use AVPF (or SAVPF). This
; option may be specified at the global or peer scope.
;----------------------------------------- REALTIME SUPPORT ------------------------ ;----------------------------------------- REALTIME SUPPORT ------------------------
; For additional information on ARA, the Asterisk Realtime Architecture, ; For additional information on ARA, the Asterisk Realtime Architecture,
; please read https://wiki.asterisk.org/wiki/display/AST/Realtime+Database+Configuration ; please read https://wiki.asterisk.org/wiki/display/AST/Realtime+Database+Configuration

View File

@@ -176,105 +176,12 @@ struct ast_sockaddr *ast_websocket_remote_address(struct ast_websocket *session)
*/ */
int ast_websocket_is_secure(struct ast_websocket *session); int ast_websocket_is_secure(struct ast_websocket *session);
#endif /* _ASTERISK_HTTP_WEBSOCKET_H */
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2012, Digium, Inc.
*
* Joshua Colp <jcolp@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
#ifndef _ASTERISK_HTTP_WEBSOCKET_H
#define _ASTERISK_HTTP_WEBSOCKET_H
#include "asterisk/module.h"
/*! /*!
* \file http_websocket.h * \brief Set the socket of a WebSocket session to be non-blocking.
* \brief Support for WebSocket connections within the Asterisk HTTP server.
*
* \author Joshua Colp <jcolp@digium.com>
* *
* \retval 0 on success
* \retval -1 on failure
*/ */
int ast_websocket_set_nonblock(struct ast_websocket *session);
/*! \brief WebSocket operation codes */ #endif
enum ast_websocket_opcode {
AST_WEBSOCKET_OPCODE_TEXT = 0x1, /*!< Text frame */
AST_WEBSOCKET_OPCODE_BINARY = 0x2, /*!< Binary frame */
AST_WEBSOCKET_OPCODE_PING = 0x9, /*!< Request that the other side respond with a pong */
AST_WEBSOCKET_OPCODE_PONG = 0xA, /*!< Response to a ping */
AST_WEBSOCKET_OPCODE_CLOSE = 0x8, /*!< Connection is being closed */
AST_WEBSOCKET_OPCODE_CONTINUATION = 0x0, /*!< Continuation of a previous frame */
};
/*!
* \brief Callback for when a new connection for a sub-protocol is established
*
* \param f Pointer to the file instance for the session
* \param fd File descriptor for the session
* \param remote_address The address of the remote party
*
* \note Once called the ownership of the session is transferred to the sub-protocol handler. It
* is responsible for closing and cleaning up.
*
*/
typedef void (*ast_websocket_callback)(FILE *f, int fd, struct ast_sockaddr *remote_address);
/*!
* \brief Add a sub-protocol handler to the server
*
* \param name Name of the sub-protocol to register
* \param callback Callback called when a new connection requesting the sub-protocol is established
*
* \retval 0 success
* \retval -1 if sub-protocol handler could not be registered
*/
int ast_websocket_add_protocol(char *name, ast_websocket_callback callback);
/*!
* \brief Remove a sub-protocol handler from the server
*
* \param name Name of the sub-protocol to unregister
* \param callback Callback that was previously registered with the sub-protocol
*
* \retval 0 success
* \retval -1 if sub-protocol was not found or if callback did not match
*/
int ast_websocket_remove_protocol(char *name, ast_websocket_callback callback);
/*!
* \brief Read a WebSocket frame and handle it
*
* \param f Pointer to the file stream, used to respond to certain frames
* \param buf Pointer to the buffer containing the frame
* \param buflen Size of the buffer
* \param payload_len Pointer to a uint64_t which will be populated with the length of the payload if present
* \param opcode Pointer to an int which will be populated with the opcode of the frame
*
* \retval NULL if no payload is present
* \retval non-NULL if payload is present, returned pointer points to beginning of payload
*/
char *ast_websocket_read(FILE *f, char *buf, size_t buflen, uint64_t *payload_len, int *opcode);
/*!
* \brief Construct and transmit a WebSocket frame
*
* \param f Pointer to the file stream which the frame will be sent on
* \param opcode WebSocket operation code to place in the frame
* \param payload Optional pointer to a payload to add to the frame
* \param actual_length Length of the payload (0 if no payload)
*/
void ast_websocket_write(FILE *f, int op_code, char *payload, uint64_t actual_length);
#endif /* _ASTERISK_HTTP_WEBSOCKET_H */

View File

@@ -267,6 +267,23 @@ int ast_websocket_is_secure(struct ast_websocket *session)
return session->secure; return session->secure;
} }
int ast_websocket_set_nonblock(struct ast_websocket *session)
{
int flags;
if ((flags = fcntl(session->fd, F_GETFL)) == -1) {
return -1;
}
flags |= O_NONBLOCK;
if ((flags = fcntl(session->fd, F_SETFL, flags)) == -1) {
return -1;
}
return 0;
}
int ast_websocket_read(struct ast_websocket *session, char **payload, uint64_t *payload_len, enum ast_websocket_opcode *opcode, int *fragmented) int ast_websocket_read(struct ast_websocket *session, char **payload, uint64_t *payload_len, enum ast_websocket_opcode *opcode, int *fragmented)
{ {
char buf[MAXIMUM_FRAME_SIZE] = ""; char buf[MAXIMUM_FRAME_SIZE] = "";