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:
Mark Michelson
2012-10-12 16:20:15 +00:00
parent 963f94e99f
commit ccf01fbfdc
3 changed files with 867 additions and 118 deletions

View File

@@ -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);

View File

@@ -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)

View File

@@ -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;