mirror of
https://github.com/asterisk/asterisk.git
synced 2025-11-20 08:40:16 +00:00
Do not use a FILE handle when doing SIP TCP reads.
This is used to solve an issue where a poll on a file descriptor does not necessarily correspond to the readiness of a FILE handle to be read. This change makes it so that for TCP connections, we do a recv() on the file descriptor instead. Because TCP does not guarantee that an entire message or even just one single message will arrive during a read, a loop has been introduced to ensure that we only attempt to handle a single message at a time. The tcptls_session_instance structure has also had an overflow buffer added to it so that if more than one TCP message arrives in one go, there is a place to throw the excess. Huge thanks goes out to Walter Doekes for doing extensive review on this change and finding edge cases where code could fail. (closes issue ASTERISK-20212) reported by Phil Ciccone Review: https://reviewboard.asterisk.org/r/2123 ........ Merged revisions 374905 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 374906 from http://svn.asterisk.org/svn/asterisk/branches/10 git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/11@374914 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
@@ -2643,12 +2643,363 @@ static int sip_check_authtimeout(time_t start)
|
|||||||
return timeout;
|
return timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Read a SIP request or response from a TLS connection
|
||||||
|
*
|
||||||
|
* Because TLS operations are hidden from view via a FILE handle, the
|
||||||
|
* logic for reading data is a bit complex, and we have to make periodic
|
||||||
|
* checks to be sure we aren't taking too long to perform the necessary
|
||||||
|
* action.
|
||||||
|
*
|
||||||
|
* \todo XXX This should be altered in the future not to use a FILE pointer
|
||||||
|
*
|
||||||
|
* \param req The request structure to fill in
|
||||||
|
* \param tcptls_session The TLS connection on which the data is being received
|
||||||
|
* \param authenticated A flag indicating whether authentication has occurred yet.
|
||||||
|
* This is only relevant in a server role.
|
||||||
|
* \param start The time at which we started attempting to read data. Used in
|
||||||
|
* determining if there has been a timeout.
|
||||||
|
* \param me Thread info. Used as a means of determining if the session needs to be stoppped.
|
||||||
|
* \retval -1 Failed to read data
|
||||||
|
* \retval 0 Succeeded in reading data
|
||||||
|
*/
|
||||||
|
static int sip_tls_read(struct sip_request *req, struct ast_tcptls_session_instance *tcptls_session, int authenticated, time_t start, struct sip_threadinfo *me)
|
||||||
|
{
|
||||||
|
int res, content_length, after_poll = 1, need_poll = 1;
|
||||||
|
struct sip_request reqcpy = { 0, };
|
||||||
|
char buf[1024] = "";
|
||||||
|
int timeout = -1;
|
||||||
|
|
||||||
|
/* Read in headers one line at a time */
|
||||||
|
while (ast_str_strlen(req->data) < 4 || strncmp(REQ_OFFSET_TO_STR(req, data->used - 4), "\r\n\r\n", 4)) {
|
||||||
|
if (!tcptls_session->client && !authenticated) {
|
||||||
|
if ((timeout = sip_check_authtimeout(start)) < 0) {
|
||||||
|
ast_debug(2, "SIP SSL server failed to determine authentication timeout\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout == 0) {
|
||||||
|
ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timeout = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* special polling behavior is required for TLS
|
||||||
|
* sockets because of the buffering done in the
|
||||||
|
* TLS layer */
|
||||||
|
if (need_poll) {
|
||||||
|
need_poll = 0;
|
||||||
|
after_poll = 1;
|
||||||
|
res = ast_wait_for_input(tcptls_session->fd, timeout);
|
||||||
|
if (res < 0) {
|
||||||
|
ast_debug(2, "SIP TCP server :: ast_wait_for_input returned %d\n", res);
|
||||||
|
return -1;
|
||||||
|
} else if (res == 0) {
|
||||||
|
/* timeout */
|
||||||
|
ast_debug(2, "SIP TCP server timed out\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ao2_lock(tcptls_session);
|
||||||
|
if (!fgets(buf, sizeof(buf), tcptls_session->f)) {
|
||||||
|
ao2_unlock(tcptls_session);
|
||||||
|
if (after_poll) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
need_poll = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ao2_unlock(tcptls_session);
|
||||||
|
after_poll = 0;
|
||||||
|
if (me->stop) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ast_str_append(&req->data, 0, "%s", buf);
|
||||||
|
}
|
||||||
|
copy_request(&reqcpy, req);
|
||||||
|
parse_request(&reqcpy);
|
||||||
|
/* In order to know how much to read, we need the content-length header */
|
||||||
|
if (sscanf(sip_get_header(&reqcpy, "Content-Length"), "%30d", &content_length)) {
|
||||||
|
while (content_length > 0) {
|
||||||
|
size_t bytes_read;
|
||||||
|
if (!tcptls_session->client && !authenticated) {
|
||||||
|
if ((timeout = sip_check_authtimeout(start)) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout == 0) {
|
||||||
|
ast_debug(2, "SIP SSL server timed out\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timeout = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (need_poll) {
|
||||||
|
need_poll = 0;
|
||||||
|
after_poll = 1;
|
||||||
|
res = ast_wait_for_input(tcptls_session->fd, timeout);
|
||||||
|
if (res < 0) {
|
||||||
|
ast_debug(2, "SIP TCP server :: ast_wait_for_input returned %d\n", res);
|
||||||
|
return -1;
|
||||||
|
} else if (res == 0) {
|
||||||
|
/* timeout */
|
||||||
|
ast_debug(2, "SIP TCP server timed out\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ao2_lock(tcptls_session);
|
||||||
|
if (!(bytes_read = fread(buf, 1, MIN(sizeof(buf) - 1, content_length), tcptls_session->f))) {
|
||||||
|
ao2_unlock(tcptls_session);
|
||||||
|
if (after_poll) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
need_poll = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf[bytes_read] = '\0';
|
||||||
|
ao2_unlock(tcptls_session);
|
||||||
|
after_poll = 0;
|
||||||
|
if (me->stop) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
content_length -= strlen(buf);
|
||||||
|
ast_str_append(&req->data, 0, "%s", buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*! \todo XXX If there's no Content-Length or if the content-length and what
|
||||||
|
we receive is not the same - we should generate an error */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Indication of a TCP message's integrity
|
||||||
|
*/
|
||||||
|
enum message_integrity {
|
||||||
|
/*!
|
||||||
|
* The message has an error in it with
|
||||||
|
* regards to its Content-Length header
|
||||||
|
*/
|
||||||
|
MESSAGE_INVALID,
|
||||||
|
/*!
|
||||||
|
* The message is incomplete
|
||||||
|
*/
|
||||||
|
MESSAGE_FRAGMENT,
|
||||||
|
/*!
|
||||||
|
* The data contains a complete message
|
||||||
|
* plus a fragment of another.
|
||||||
|
*/
|
||||||
|
MESSAGE_FRAGMENT_COMPLETE,
|
||||||
|
/*!
|
||||||
|
* The message is complete
|
||||||
|
*/
|
||||||
|
MESSAGE_COMPLETE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get the content length from an unparsed SIP message
|
||||||
|
*
|
||||||
|
* \param message The unparsed SIP message headers
|
||||||
|
* \return The value of the Content-Length header or -1 if message is invalid
|
||||||
|
*/
|
||||||
|
static int read_raw_content_length(const char *message)
|
||||||
|
{
|
||||||
|
char *end_of_line;
|
||||||
|
char *content_length_str;
|
||||||
|
char *l_str;
|
||||||
|
int content_length;
|
||||||
|
char *msg;
|
||||||
|
|
||||||
|
if (sip_cfg.pedanticsipchecking) {
|
||||||
|
struct ast_str *msg_copy = ast_str_create(strlen(message));
|
||||||
|
if (!msg_copy) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ast_str_set(&msg_copy, 0, "%s", message);
|
||||||
|
lws2sws(msg_copy);
|
||||||
|
msg = ast_strdupa(ast_str_buffer(msg_copy));
|
||||||
|
ast_free(msg_copy);
|
||||||
|
} else {
|
||||||
|
msg = ast_strdupa(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Let's find a Content-Length header */
|
||||||
|
content_length_str = strcasestr(msg, "\nContent-Length:");
|
||||||
|
if (!content_length_str && !(l_str = strcasestr(msg, "\nl:"))) {
|
||||||
|
/* RFC 3261 18.3
|
||||||
|
* "In the case of stream-oriented transports such as TCP, the Content-
|
||||||
|
* Length header field indicates the size of the body. The Content-
|
||||||
|
* Length header field MUST be used with stream oriented transports."
|
||||||
|
*/
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (content_length_str) {
|
||||||
|
content_length_str += sizeof("\nContent-Length:");
|
||||||
|
} else if (l_str) {
|
||||||
|
content_length_str = l_str + sizeof("\nl:");
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
end_of_line = strchr(content_length_str, '\n');
|
||||||
|
|
||||||
|
if (!end_of_line) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sscanf(content_length_str, "%30d", &content_length) == 1) {
|
||||||
|
return content_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Check that a message received over TCP is a full message
|
||||||
|
*
|
||||||
|
* This will take the information read in and then determine if
|
||||||
|
* 1) The message is a full SIP request
|
||||||
|
* 2) The message is a partial SIP request
|
||||||
|
* 3) The message contains a full SIP request along with another partial request
|
||||||
|
* \param data The unparsed incoming SIP message.
|
||||||
|
* \param request The resulting request with extra fragments removed.
|
||||||
|
* \param overflow If the message contains more than a full request, this is the remainder of the message
|
||||||
|
* \return The resulting integrity of the message
|
||||||
|
*/
|
||||||
|
static enum message_integrity check_message_integrity(struct ast_str **request, struct ast_str **overflow)
|
||||||
|
{
|
||||||
|
char *message = ast_strdupa(ast_str_buffer(*request));
|
||||||
|
char *body;
|
||||||
|
int content_length;
|
||||||
|
int body_len;
|
||||||
|
int message_len = strlen(message);
|
||||||
|
|
||||||
|
/* Important pieces to search for in a SIP request are \r\n\r\n. This
|
||||||
|
* marks either
|
||||||
|
* 1) The division between the headers and body
|
||||||
|
* 2) The end of the SIP request
|
||||||
|
*/
|
||||||
|
body = strstr(message, "\r\n\r\n");
|
||||||
|
if (!body) {
|
||||||
|
/* This is clearly a partial message since we haven't reached an end
|
||||||
|
* yet.
|
||||||
|
*/
|
||||||
|
return MESSAGE_FRAGMENT;
|
||||||
|
}
|
||||||
|
body += sizeof("\r\n\r\n") - 1;
|
||||||
|
body_len = strlen(body);
|
||||||
|
|
||||||
|
body[-1] = '\0';
|
||||||
|
content_length = read_raw_content_length(message);
|
||||||
|
body[-1] = '\n';
|
||||||
|
|
||||||
|
if (content_length < 0) {
|
||||||
|
return MESSAGE_INVALID;
|
||||||
|
} else if (content_length == 0) {
|
||||||
|
/* We've definitely received an entire message. We need
|
||||||
|
* to check if there's also a fragment of another message
|
||||||
|
* in addition.
|
||||||
|
*/
|
||||||
|
if (body_len == 0) {
|
||||||
|
return MESSAGE_COMPLETE;
|
||||||
|
} else {
|
||||||
|
ast_str_truncate(*request, message_len - body_len);
|
||||||
|
ast_str_append(overflow, 0, "%s", body);
|
||||||
|
return MESSAGE_FRAGMENT_COMPLETE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Positive content length. Let's see what sort of
|
||||||
|
* message body we're dealing with.
|
||||||
|
*/
|
||||||
|
if (body_len < content_length) {
|
||||||
|
/* We don't have the full message body yet */
|
||||||
|
return MESSAGE_FRAGMENT;
|
||||||
|
} else if (body_len > content_length) {
|
||||||
|
/* We have the full message plus a fragment of a further
|
||||||
|
* message
|
||||||
|
*/
|
||||||
|
ast_str_truncate(*request, message_len - (body_len - content_length));
|
||||||
|
ast_str_append(overflow, 0, "%s", body + content_length);
|
||||||
|
return MESSAGE_FRAGMENT_COMPLETE;
|
||||||
|
} else {
|
||||||
|
/* Yay! Full message with no extra content */
|
||||||
|
return MESSAGE_COMPLETE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Read SIP request or response from a TCP connection
|
||||||
|
*
|
||||||
|
* \param req The request structure to be filled in
|
||||||
|
* \param tcptls_session The TCP connection from which to read
|
||||||
|
* \retval -1 Failed to read data
|
||||||
|
* \retval 0 Successfully read data
|
||||||
|
*/
|
||||||
|
static int sip_tcp_read(struct sip_request *req, struct ast_tcptls_session_instance *tcptls_session,
|
||||||
|
int authenticated, time_t start)
|
||||||
|
{
|
||||||
|
enum message_integrity message_integrity = MESSAGE_FRAGMENT;
|
||||||
|
|
||||||
|
while (message_integrity == MESSAGE_FRAGMENT) {
|
||||||
|
if (ast_str_strlen(tcptls_session->overflow_buf) == 0) {
|
||||||
|
char readbuf[4097];
|
||||||
|
int timeout;
|
||||||
|
int res;
|
||||||
|
if (!tcptls_session->client && !authenticated) {
|
||||||
|
if ((timeout = sip_check_authtimeout(start)) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout == 0) {
|
||||||
|
ast_debug(2, "SIP TCP server timed out\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timeout = -1;
|
||||||
|
}
|
||||||
|
res = ast_wait_for_input(tcptls_session->fd, timeout);
|
||||||
|
if (res < 0) {
|
||||||
|
ast_debug(2, "SIP TCP server :: ast_wait_for_input returned %d\n", res);
|
||||||
|
return -1;
|
||||||
|
} else if (res == 0) {
|
||||||
|
ast_debug(2, "SIP TCP server timed out\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
message_integrity = check_message_integrity(&req->data, &tcptls_session->overflow_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*! \brief SIP TCP thread management function
|
/*! \brief SIP TCP thread management function
|
||||||
This function reads from the socket, parses the packet into a request
|
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)
|
static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_session)
|
||||||
{
|
{
|
||||||
int res, cl, timeout = -1, authenticated = 0, flags, after_poll = 0, need_poll = 1;
|
int res, timeout = -1, authenticated = 0, flags;
|
||||||
time_t start;
|
time_t start;
|
||||||
struct sip_request req = { 0, } , reqcpy = { 0, };
|
struct sip_request req = { 0, } , reqcpy = { 0, };
|
||||||
struct sip_threadinfo *me = NULL;
|
struct sip_threadinfo *me = NULL;
|
||||||
@@ -2748,6 +3099,7 @@ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_s
|
|||||||
timeout = -1;
|
timeout = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ast_str_strlen(tcptls_session->overflow_buf) == 0) {
|
||||||
res = ast_poll(fds, 2, timeout); /* polls for both socket and alert_pipe */
|
res = ast_poll(fds, 2, timeout); /* polls for both socket and alert_pipe */
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res);
|
ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res);
|
||||||
@@ -2757,12 +3109,13 @@ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_s
|
|||||||
ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
|
ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* handle the socket event, check for both reads from the socket fd,
|
/*
|
||||||
* and writes from alert_pipe fd */
|
* handle the socket event, check for both reads from the socket fd or TCP overflow buffer,
|
||||||
if (fds[0].revents) { /* there is data on the socket to be read */
|
* and writes from alert_pipe fd.
|
||||||
after_poll = 1;
|
*/
|
||||||
|
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;
|
fds[0].revents = 0;
|
||||||
|
|
||||||
/* clear request structure */
|
/* clear request structure */
|
||||||
@@ -2786,111 +3139,15 @@ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_s
|
|||||||
req.socket.port = htons(ourport_tcp);
|
req.socket.port = htons(ourport_tcp);
|
||||||
}
|
}
|
||||||
req.socket.fd = tcptls_session->fd;
|
req.socket.fd = tcptls_session->fd;
|
||||||
|
if (tcptls_session->ssl) {
|
||||||
/* Read in headers one line at a time */
|
res = sip_tls_read(&req, tcptls_session, authenticated, start, me);
|
||||||
while (ast_str_strlen(req.data) < 4 || strncmp(REQ_OFFSET_TO_STR(&req, data->used - 4), "\r\n\r\n", 4)) {
|
|
||||||
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 ? "SSL": "TCP");
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
timeout = -1;
|
res = sip_tcp_read(&req, tcptls_session, authenticated, start);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* special polling behavior is required for TLS
|
|
||||||
* sockets because of the buffering done in the
|
|
||||||
* TLS layer */
|
|
||||||
if (!tcptls_session->ssl || need_poll) {
|
|
||||||
need_poll = 0;
|
|
||||||
after_poll = 1;
|
|
||||||
res = ast_wait_for_input(tcptls_session->fd, timeout);
|
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
ast_debug(2, "SIP TCP server :: ast_wait_for_input returned %d\n", res);
|
|
||||||
goto cleanup;
|
|
||||||
} else if (res == 0) {
|
|
||||||
/* timeout */
|
|
||||||
ast_debug(2, "SIP TCP server timed out\n");
|
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ao2_lock(tcptls_session);
|
|
||||||
if (!fgets(buf, sizeof(buf), tcptls_session->f)) {
|
|
||||||
ao2_unlock(tcptls_session);
|
|
||||||
if (after_poll) {
|
|
||||||
goto cleanup;
|
|
||||||
} else {
|
|
||||||
need_poll = 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ao2_unlock(tcptls_session);
|
|
||||||
after_poll = 0;
|
|
||||||
if (me->stop) {
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
ast_str_append(&req.data, 0, "%s", buf);
|
|
||||||
}
|
|
||||||
copy_request(&reqcpy, &req);
|
|
||||||
parse_request(&reqcpy);
|
|
||||||
/* In order to know how much to read, we need the content-length header */
|
|
||||||
if (sscanf(sip_get_header(&reqcpy, "Content-Length"), "%30d", &cl)) {
|
|
||||||
while (cl > 0) {
|
|
||||||
size_t bytes_read;
|
|
||||||
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 ? "SSL": "TCP");
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
timeout = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tcptls_session->ssl || need_poll) {
|
|
||||||
need_poll = 0;
|
|
||||||
after_poll = 1;
|
|
||||||
res = ast_wait_for_input(tcptls_session->fd, timeout);
|
|
||||||
if (res < 0) {
|
|
||||||
ast_debug(2, "SIP TCP server :: ast_wait_for_input returned %d\n", res);
|
|
||||||
goto cleanup;
|
|
||||||
} else if (res == 0) {
|
|
||||||
/* timeout */
|
|
||||||
ast_debug(2, "SIP TCP server timed out\n");
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ao2_lock(tcptls_session);
|
|
||||||
if (!(bytes_read = fread(buf, 1, MIN(sizeof(buf) - 1, cl), tcptls_session->f))) {
|
|
||||||
ao2_unlock(tcptls_session);
|
|
||||||
if (after_poll) {
|
|
||||||
goto cleanup;
|
|
||||||
} else {
|
|
||||||
need_poll = 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf[bytes_read] = '\0';
|
|
||||||
ao2_unlock(tcptls_session);
|
|
||||||
after_poll = 0;
|
|
||||||
if (me->stop) {
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
cl -= strlen(buf);
|
|
||||||
ast_str_append(&req.data, 0, "%s", buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*! \todo XXX If there's no Content-Length or if the content-length and what
|
|
||||||
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;
|
req.socket.ws_session = NULL;
|
||||||
@@ -33008,6 +33265,482 @@ AST_TEST_DEFINE(test_sip_peers_get)
|
|||||||
return AST_TEST_PASS;
|
return AST_TEST_PASS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Imitation TCP reception loop
|
||||||
|
*
|
||||||
|
* This imitates the logic used by SIP's TCP code. Its purpose
|
||||||
|
* is to either
|
||||||
|
* 1) Combine fragments into a single message
|
||||||
|
* 2) Break up combined messages into single messages
|
||||||
|
*
|
||||||
|
* \param fragments The message fragments. This simulates the data received on a TCP socket.
|
||||||
|
* \param num_fragments This indicates the number of fragments to receive
|
||||||
|
* \param overflow This is a place to stash extra data if more than one message is received
|
||||||
|
* in a single fragment
|
||||||
|
* \param[out] messages The parsed messages are placed in this array
|
||||||
|
* \param[out] num_messages The number of messages that were parsed
|
||||||
|
* \param test Used for printing messages
|
||||||
|
* \retval 0 Success
|
||||||
|
* \retval -1 Failure
|
||||||
|
*/
|
||||||
|
static int mock_tcp_loop(char *fragments[], size_t num_fragments,
|
||||||
|
struct ast_str **overflow, char **messages, int *num_messages, struct ast_test* test)
|
||||||
|
{
|
||||||
|
struct ast_str *req_data;
|
||||||
|
int i = 0;
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
req_data = ast_str_create(128);
|
||||||
|
ast_str_reset(*overflow);
|
||||||
|
|
||||||
|
while (i < num_fragments || ast_str_strlen(*overflow) > 0) {
|
||||||
|
enum message_integrity message_integrity = MESSAGE_FRAGMENT;
|
||||||
|
ast_str_reset(req_data);
|
||||||
|
while (message_integrity == MESSAGE_FRAGMENT) {
|
||||||
|
if (ast_str_strlen(*overflow) > 0) {
|
||||||
|
ast_str_append(&req_data, 0, "%s", ast_str_buffer(*overflow));
|
||||||
|
ast_str_reset(*overflow);
|
||||||
|
} else {
|
||||||
|
ast_str_append(&req_data, 0, "%s", fragments[i++]);
|
||||||
|
}
|
||||||
|
message_integrity = check_message_integrity(&req_data, overflow);
|
||||||
|
}
|
||||||
|
if (strcmp(ast_str_buffer(req_data), messages[*num_messages])) {
|
||||||
|
ast_test_status_update(test, "Mismatch in SIP messages.\n");
|
||||||
|
ast_test_status_update(test, "Expected message:\n%s", messages[*num_messages]);
|
||||||
|
ast_test_status_update(test, "Parsed message:\n%s", ast_str_buffer(req_data));
|
||||||
|
res = -1;
|
||||||
|
goto end;
|
||||||
|
} else {
|
||||||
|
ast_test_status_update(test, "Successfully read message:\n%s", ast_str_buffer(req_data));
|
||||||
|
}
|
||||||
|
(*num_messages)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
ast_free(req_data);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
AST_TEST_DEFINE(test_tcp_message_fragmentation)
|
||||||
|
{
|
||||||
|
/* Normal single message in one fragment */
|
||||||
|
char *normal[] = {
|
||||||
|
"INVITE sip:bob@example.org SIP/2.0\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
"Contact: sip:127.0.0.1:5061\r\n"
|
||||||
|
"Max-Forwards: 70\r\n"
|
||||||
|
"Content-Type: application/sdp\r\n"
|
||||||
|
"Content-Length: 130\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"v=0\r\n"
|
||||||
|
"o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
|
||||||
|
"s=-\r\n"
|
||||||
|
"c=IN IP4 127.0.0.1\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"m=audio 10000 RTP/AVP 0\r\n"
|
||||||
|
"a=rtpmap:0 PCMU/8000\r\n"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Single message in two fragments.
|
||||||
|
* Fragments combine to make "normal"
|
||||||
|
*/
|
||||||
|
char *fragmented[] = {
|
||||||
|
"INVITE sip:bob@example.org SIP/2.0\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
"Contact: sip:127.0.0.1:5061\r\n"
|
||||||
|
"Max-Forwards: ",
|
||||||
|
|
||||||
|
"70\r\n"
|
||||||
|
"Content-Type: application/sdp\r\n"
|
||||||
|
"Content-Length: 130\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"v=0\r\n"
|
||||||
|
"o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
|
||||||
|
"s=-\r\n"
|
||||||
|
"c=IN IP4 127.0.0.1\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"m=audio 10000 RTP/AVP 0\r\n"
|
||||||
|
"a=rtpmap:0 PCMU/8000\r\n"
|
||||||
|
};
|
||||||
|
/* Single message in two fragments, divided precisely at the body
|
||||||
|
* Fragments combine to make "normal"
|
||||||
|
*/
|
||||||
|
char *fragmented_body[] = {
|
||||||
|
"INVITE sip:bob@example.org SIP/2.0\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
"Contact: sip:127.0.0.1:5061\r\n"
|
||||||
|
"Max-Forwards: 70\r\n"
|
||||||
|
"Content-Type: application/sdp\r\n"
|
||||||
|
"Content-Length: 130\r\n"
|
||||||
|
"\r\n",
|
||||||
|
|
||||||
|
"v=0\r\n"
|
||||||
|
"o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
|
||||||
|
"s=-\r\n"
|
||||||
|
"c=IN IP4 127.0.0.1\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"m=audio 10000 RTP/AVP 0\r\n"
|
||||||
|
"a=rtpmap:0 PCMU/8000\r\n"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Single message in three fragments
|
||||||
|
* Fragments combine to make "normal"
|
||||||
|
*/
|
||||||
|
char *multi_fragment[] = {
|
||||||
|
"INVITE sip:bob@example.org SIP/2.0\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n",
|
||||||
|
|
||||||
|
"Contact: sip:127.0.0.1:5061\r\n"
|
||||||
|
"Max-Forwards: 70\r\n"
|
||||||
|
"Content-Type: application/sdp\r\n"
|
||||||
|
"Content-Length: 130\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"v=0\r\n"
|
||||||
|
"o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
|
||||||
|
"s=-\r\n"
|
||||||
|
"c=IN IP4",
|
||||||
|
|
||||||
|
" 127.0.0.1\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"m=audio 10000 RTP/AVP 0\r\n"
|
||||||
|
"a=rtpmap:0 PCMU/8000\r\n"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Two messages in a single fragment
|
||||||
|
* Fragments split into "multi_message_divided"
|
||||||
|
*/
|
||||||
|
char *multi_message[] = {
|
||||||
|
"SIP/2.0 100 Trying\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
"Contact: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Content-Length: 0\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"SIP/2.0 180 Ringing\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
"Contact: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Content-Length: 0\r\n"
|
||||||
|
"\r\n"
|
||||||
|
};
|
||||||
|
char *multi_message_divided[] = {
|
||||||
|
"SIP/2.0 100 Trying\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
"Contact: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Content-Length: 0\r\n"
|
||||||
|
"\r\n",
|
||||||
|
|
||||||
|
"SIP/2.0 180 Ringing\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
"Contact: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Content-Length: 0\r\n"
|
||||||
|
"\r\n"
|
||||||
|
};
|
||||||
|
/* Two messages with bodies combined into one fragment
|
||||||
|
* Fragments split into "multi_message_body_divided"
|
||||||
|
*/
|
||||||
|
char *multi_message_body[] = {
|
||||||
|
"INVITE sip:bob@example.org SIP/2.0\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
"Contact: sip:127.0.0.1:5061\r\n"
|
||||||
|
"Max-Forwards: 70\r\n"
|
||||||
|
"Content-Type: application/sdp\r\n"
|
||||||
|
"Content-Length: 130\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"v=0\r\n"
|
||||||
|
"o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
|
||||||
|
"s=-\r\n"
|
||||||
|
"c=IN IP4 127.0.0.1\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"m=audio 10000 RTP/AVP 0\r\n"
|
||||||
|
"a=rtpmap:0 PCMU/8000\r\n"
|
||||||
|
"INVITE sip:bob@example.org SIP/2.0\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 2 INVITE\r\n"
|
||||||
|
"Contact: sip:127.0.0.1:5061\r\n"
|
||||||
|
"Max-Forwards: 70\r\n"
|
||||||
|
"Content-Type: application/sdp\r\n"
|
||||||
|
"Content-Length: 130\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"v=0\r\n"
|
||||||
|
"o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
|
||||||
|
"s=-\r\n"
|
||||||
|
"c=IN IP4 127.0.0.1\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"m=audio 10000 RTP/AVP 0\r\n"
|
||||||
|
"a=rtpmap:0 PCMU/8000\r\n"
|
||||||
|
};
|
||||||
|
char *multi_message_body_divided[] = {
|
||||||
|
"INVITE sip:bob@example.org SIP/2.0\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
"Contact: sip:127.0.0.1:5061\r\n"
|
||||||
|
"Max-Forwards: 70\r\n"
|
||||||
|
"Content-Type: application/sdp\r\n"
|
||||||
|
"Content-Length: 130\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"v=0\r\n"
|
||||||
|
"o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
|
||||||
|
"s=-\r\n"
|
||||||
|
"c=IN IP4 127.0.0.1\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"m=audio 10000 RTP/AVP 0\r\n"
|
||||||
|
"a=rtpmap:0 PCMU/8000\r\n",
|
||||||
|
|
||||||
|
"INVITE sip:bob@example.org SIP/2.0\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 2 INVITE\r\n"
|
||||||
|
"Contact: sip:127.0.0.1:5061\r\n"
|
||||||
|
"Max-Forwards: 70\r\n"
|
||||||
|
"Content-Type: application/sdp\r\n"
|
||||||
|
"Content-Length: 130\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"v=0\r\n"
|
||||||
|
"o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
|
||||||
|
"s=-\r\n"
|
||||||
|
"c=IN IP4 127.0.0.1\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"m=audio 10000 RTP/AVP 0\r\n"
|
||||||
|
"a=rtpmap:0 PCMU/8000\r\n"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Two messages that appear in two fragments. Fragment
|
||||||
|
* boundaries do not align with message boundaries.
|
||||||
|
* Fragments combine to make "multi_message_divided"
|
||||||
|
*/
|
||||||
|
char *multi_message_in_fragments[] = {
|
||||||
|
"SIP/2.0 100 Trying\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVI",
|
||||||
|
|
||||||
|
"TE\r\n"
|
||||||
|
"Contact: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Content-Length: 0\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"SIP/2.0 180 Ringing\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
"Contact: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Content-Length: 0\r\n"
|
||||||
|
"\r\n"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Message with compact content-length header
|
||||||
|
* Same as "normal" but with compact content-length header
|
||||||
|
*/
|
||||||
|
char *compact[] = {
|
||||||
|
"INVITE sip:bob@example.org SIP/2.0\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
"Contact: sip:127.0.0.1:5061\r\n"
|
||||||
|
"Max-Forwards: 70\r\n"
|
||||||
|
"Content-Type: application/sdp\r\n"
|
||||||
|
"l: 130\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"v=0\r\n"
|
||||||
|
"o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
|
||||||
|
"s=-\r\n"
|
||||||
|
"c=IN IP4 127.0.0.1\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"m=audio 10000 RTP/AVP 0\r\n"
|
||||||
|
"a=rtpmap:0 PCMU/8000\r\n"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Message with faux content-length headers
|
||||||
|
* Same as "normal" but with extra fake content-length headers
|
||||||
|
*/
|
||||||
|
char *faux[] = {
|
||||||
|
"INVITE sip:bob@example.org SIP/2.0\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
"Contact: sip:127.0.0.1:5061\r\n"
|
||||||
|
"Max-Forwards: 70\r\n"
|
||||||
|
"Content-Type: application/sdp\r\n"
|
||||||
|
"DisContent-Length: 0\r\n"
|
||||||
|
"MalContent-Length: 60\r\n"
|
||||||
|
"Content-Length: 130\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"v=0\r\n"
|
||||||
|
"o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
|
||||||
|
"s=-\r\n"
|
||||||
|
"c=IN IP4 127.0.0.1\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"m=audio 10000 RTP/AVP 0\r\n"
|
||||||
|
"a=rtpmap:0 PCMU/8000\r\n"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Message with folded Content-Length header
|
||||||
|
* Message is "normal" with Content-Length spread across three lines
|
||||||
|
*
|
||||||
|
* This is the test that requires pedantic=yes in order to pass
|
||||||
|
*/
|
||||||
|
char *folded[] = {
|
||||||
|
"INVITE sip:bob@example.org SIP/2.0\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
"Contact: sip:127.0.0.1:5061\r\n"
|
||||||
|
"Max-Forwards: 70\r\n"
|
||||||
|
"Content-Type: application/sdp\r\n"
|
||||||
|
"Content-Length: \t\r\n"
|
||||||
|
"\t \r\n"
|
||||||
|
" 130\t \r\n"
|
||||||
|
"\r\n"
|
||||||
|
"v=0\r\n"
|
||||||
|
"o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
|
||||||
|
"s=-\r\n"
|
||||||
|
"c=IN IP4 127.0.0.1\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"m=audio 10000 RTP/AVP 0\r\n"
|
||||||
|
"a=rtpmap:0 PCMU/8000\r\n"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Message with compact Content-length header in message and
|
||||||
|
* full Content-Length header in the body. Ensure that the header
|
||||||
|
* in the message is read and that the one in the body is ignored
|
||||||
|
*/
|
||||||
|
char *cl_in_body[] = {
|
||||||
|
"INVITE sip:bob@example.org SIP/2.0\r\n"
|
||||||
|
"Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n"
|
||||||
|
"From: sipp <sip:127.0.0.1:5061>;tag=12345\r\n"
|
||||||
|
"To: <sip:bob@example.org:5060>\r\n"
|
||||||
|
"Call-ID: 12345\r\n"
|
||||||
|
"CSeq: 1 INVITE\r\n"
|
||||||
|
"Contact: sip:127.0.0.1:5061\r\n"
|
||||||
|
"Max-Forwards: 70\r\n"
|
||||||
|
"Content-Type: application/sdp\r\n"
|
||||||
|
"l: 149\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"v=0\r\n"
|
||||||
|
"Content-Length: 0\r\n"
|
||||||
|
"o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n"
|
||||||
|
"s=-\r\n"
|
||||||
|
"c=IN IP4 127.0.0.1\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"m=audio 10000 RTP/AVP 0\r\n"
|
||||||
|
"a=rtpmap:0 PCMU/8000\r\n"
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ast_str *overflow;
|
||||||
|
struct {
|
||||||
|
char **fragments;
|
||||||
|
char **expected;
|
||||||
|
int num_expected;
|
||||||
|
const char *description;
|
||||||
|
} tests[] = {
|
||||||
|
{ normal, normal, 1, "normal" },
|
||||||
|
{ fragmented, normal, 1, "fragmented" },
|
||||||
|
{ fragmented_body, normal, 1, "fragmented_body" },
|
||||||
|
{ multi_fragment, normal, 1, "multi_fragment" },
|
||||||
|
{ multi_message, multi_message_divided, 2, "multi_message" },
|
||||||
|
{ multi_message_body, multi_message_body_divided, 2, "multi_message_body" },
|
||||||
|
{ multi_message_in_fragments, multi_message_divided, 2, "multi_message_in_fragments" },
|
||||||
|
{ compact, compact, 1, "compact" },
|
||||||
|
{ faux, faux, 1, "faux" },
|
||||||
|
{ folded, folded, 1, "folded" },
|
||||||
|
{ cl_in_body, cl_in_body, 1, "cl_in_body" },
|
||||||
|
};
|
||||||
|
int i;
|
||||||
|
enum ast_test_result_state res = AST_TEST_PASS;
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case TEST_INIT:
|
||||||
|
info->name = "sip_tcp_message_fragmentation";
|
||||||
|
info->category = "/main/sip/transport";
|
||||||
|
info->summary = "SIP TCP message fragmentation test";
|
||||||
|
info->description =
|
||||||
|
"Tests reception of different TCP messages that have been fragmented or"
|
||||||
|
"run together. This test mimicks the code that TCP reception uses.";
|
||||||
|
return AST_TEST_NOT_RUN;
|
||||||
|
case TEST_EXECUTE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!sip_cfg.pedanticsipchecking) {
|
||||||
|
ast_log(LOG_WARNING, "Not running test. Pedantic SIP checking is not enabled, so it is guaranteed to fail\n");
|
||||||
|
return AST_TEST_NOT_RUN;
|
||||||
|
}
|
||||||
|
|
||||||
|
overflow = ast_str_create(128);
|
||||||
|
if (!overflow) {
|
||||||
|
return AST_TEST_FAIL;
|
||||||
|
}
|
||||||
|
for (i = 0; i < ARRAY_LEN(tests); ++i) {
|
||||||
|
int num_messages = 0;
|
||||||
|
if (mock_tcp_loop(tests[i].fragments, ARRAY_LEN(tests[i].fragments),
|
||||||
|
&overflow, tests[i].expected, &num_messages, test)) {
|
||||||
|
ast_test_status_update(test, "Failed to parse message '%s'\n", tests[i].description);
|
||||||
|
res = AST_TEST_FAIL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (num_messages != tests[i].num_expected) {
|
||||||
|
ast_test_status_update(test, "Did not receive the expected number of messages. "
|
||||||
|
"Expected %d but received %d\n", tests[i].num_expected, num_messages);
|
||||||
|
res = AST_TEST_FAIL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast_free(overflow);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define DATA_EXPORT_SIP_PEER(MEMBER) \
|
#define DATA_EXPORT_SIP_PEER(MEMBER) \
|
||||||
@@ -33257,6 +33990,7 @@ static int load_module(void)
|
|||||||
#ifdef TEST_FRAMEWORK
|
#ifdef TEST_FRAMEWORK
|
||||||
AST_TEST_REGISTER(test_sip_peers_get);
|
AST_TEST_REGISTER(test_sip_peers_get);
|
||||||
AST_TEST_REGISTER(test_sip_mwi_subscribe_parse);
|
AST_TEST_REGISTER(test_sip_mwi_subscribe_parse);
|
||||||
|
AST_TEST_REGISTER(test_tcp_message_fragmentation);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Register AstData providers */
|
/* Register AstData providers */
|
||||||
@@ -33384,6 +34118,7 @@ static int unload_module(void)
|
|||||||
|
|
||||||
AST_TEST_UNREGISTER(test_sip_peers_get);
|
AST_TEST_UNREGISTER(test_sip_peers_get);
|
||||||
AST_TEST_UNREGISTER(test_sip_mwi_subscribe_parse);
|
AST_TEST_UNREGISTER(test_sip_mwi_subscribe_parse);
|
||||||
|
AST_TEST_UNREGISTER(test_tcp_message_fragmentation);
|
||||||
#endif
|
#endif
|
||||||
/* Unregister all the AstData providers */
|
/* Unregister all the AstData providers */
|
||||||
ast_data_unregister(NULL);
|
ast_data_unregister(NULL);
|
||||||
|
|||||||
@@ -155,6 +155,12 @@ struct ast_tcptls_session_instance {
|
|||||||
int client;
|
int client;
|
||||||
struct ast_sockaddr remote_address;
|
struct ast_sockaddr remote_address;
|
||||||
struct ast_tcptls_session_args *parent;
|
struct ast_tcptls_session_args *parent;
|
||||||
|
/* Sometimes, when an entity reads TCP data, multiple
|
||||||
|
* logical messages might be read at the same time. In such
|
||||||
|
* a circumstance, there needs to be a place to stash the
|
||||||
|
* extra data.
|
||||||
|
*/
|
||||||
|
struct ast_str *overflow_buf;
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(HAVE_FUNOPEN)
|
#if defined(HAVE_FUNOPEN)
|
||||||
|
|||||||
@@ -142,6 +142,12 @@ HOOK_T ast_tcptls_server_write(struct ast_tcptls_session_instance *tcptls_sessio
|
|||||||
return write(tcptls_session->fd, buf, count);
|
return write(tcptls_session->fd, buf, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void session_instance_destructor(void *obj)
|
||||||
|
{
|
||||||
|
struct ast_tcptls_session_instance *i = obj;
|
||||||
|
ast_free(i->overflow_buf);
|
||||||
|
}
|
||||||
|
|
||||||
/*! \brief
|
/*! \brief
|
||||||
* creates a FILE * from the fd passed by the accept thread.
|
* creates a FILE * from the fd passed by the accept thread.
|
||||||
* This operation is potentially expensive (certificate verification),
|
* This operation is potentially expensive (certificate verification),
|
||||||
@@ -290,7 +296,7 @@ void *ast_tcptls_server_root(void *data)
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tcptls_session = ao2_alloc(sizeof(*tcptls_session), NULL);
|
tcptls_session = ao2_alloc(sizeof(*tcptls_session), session_instance_destructor);
|
||||||
if (!tcptls_session) {
|
if (!tcptls_session) {
|
||||||
ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
|
ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
|
||||||
if (close(fd)) {
|
if (close(fd)) {
|
||||||
@@ -299,6 +305,7 @@ void *ast_tcptls_server_root(void *data)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tcptls_session->overflow_buf = ast_str_create(128);
|
||||||
flags = fcntl(fd, F_GETFL);
|
flags = fcntl(fd, F_GETFL);
|
||||||
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
|
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
|
||||||
tcptls_session->fd = fd;
|
tcptls_session->fd = fd;
|
||||||
@@ -498,10 +505,11 @@ struct ast_tcptls_session_instance *ast_tcptls_client_create(struct ast_tcptls_s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(tcptls_session = ao2_alloc(sizeof(*tcptls_session), NULL))) {
|
if (!(tcptls_session = ao2_alloc(sizeof(*tcptls_session), session_instance_destructor))) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tcptls_session->overflow_buf = ast_str_create(128);
|
||||||
tcptls_session->client = 1;
|
tcptls_session->client = 1;
|
||||||
tcptls_session->fd = desc->accept_fd;
|
tcptls_session->fd = desc->accept_fd;
|
||||||
tcptls_session->parent = desc;
|
tcptls_session->parent = desc;
|
||||||
|
|||||||
Reference in New Issue
Block a user