libnice/tests/test-pseudotcp-fin.c

1278 lines
37 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* This file is part of the Nice GLib ICE library.
*
* © 2014, 2015 Collabora Ltd.
* Contact: Philip Withnall
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Nice GLib ICE library.
*
* The Initial Developers of the Original Code are Collabora Ltd and Nokia
* Corporation. All Rights Reserved.
*
* Contributors:
* Philip Withnall, Collabora Ltd.
*
* Alternatively, the contents of this file may be used under the terms of the
* the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
* case the provisions of LGPL are applicable instead of those above. If you
* wish to allow use of your version of this file only under the terms of the
* LGPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replace
* them with the notice and other provisions required by the LGPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the LGPL.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <locale.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>
#include "pseudotcp.h"
typedef struct {
PseudoTcpSocket *left; /* owned */
PseudoTcpSocket *right; /* owned */
guint32 left_current_time;
guint32 right_current_time;
/* Data sent and received by each socket. */
GQueue/*<owned GBytes>*/ *left_sent; /* owned */
GQueue/*<owned GBytes>*/ *right_sent; /* owned */
} Data;
/* NOTE: Must match the on-the-wire flag values from pseudotcp.c. */
typedef enum {
FLAG_NONE = 0,
FLAG_FIN = 1 << 0,
FLAG_SYN = 1 << 1,
FLAG_RST = 1 << 2,
} SegmentFlags;
typedef void (*TestFunc) (Data *data, const void *next_funcs);
static void
data_clear (Data *data)
{
if (data->left != NULL)
g_object_unref (data->left);
if (data->right != NULL)
g_object_unref (data->right);
if (data->left_sent != NULL)
g_queue_free_full (data->left_sent, (GDestroyNotify) g_bytes_unref);
if (data->right_sent != NULL)
g_queue_free_full (data->right_sent, (GDestroyNotify) g_bytes_unref);
}
static gchar *
segment_flags_to_string (SegmentFlags flags)
{
GString *str = g_string_new (NULL);
if (flags & FLAG_SYN)
g_string_append (str, "SYN,");
if (flags & FLAG_FIN)
g_string_append (str, "FIN,");
if (flags & FLAG_RST)
g_string_append (str, "RST,");
/* Strip the trailing comma. */
if (str->len > 0)
g_string_truncate (str, str->len - 1);
if (str->len == 0)
g_string_append (str, "0");
return g_string_free (str, FALSE);
}
static gchar *
segment_to_string (guint32 seq, guint32 ack, SegmentFlags flags)
{
gchar *ctl, *out;
ctl = segment_flags_to_string (flags);
out = g_strdup_printf ("<SEQ=%u><ACK=%u><CTL=%s>", seq, ack, ctl);
g_free (ctl);
return out;
}
static gchar *
segment_bytes_to_string (const guint8 *bytes)
{
union {
const guint8 *u8;
const guint32 *u32;
} b;
guint32 seq, ack;
guint8 flags;
b.u8 = bytes;
seq = ntohl (b.u32[1]);
ack = ntohl (b.u32[2]);
flags = b.u8[13];
return segment_to_string (seq, ack, flags);
}
static void
opened (PseudoTcpSocket *sock, gpointer data)
{
g_debug ("Socket %p opened", sock);
}
static void
readable (PseudoTcpSocket *sock, gpointer data)
{
g_debug ("Socket %p readable", sock);
}
static void
writable (PseudoTcpSocket *sock, gpointer data)
{
g_debug ("Socket %p writeable", sock);
}
static void
closed (PseudoTcpSocket *sock, guint32 err, gpointer data)
{
g_debug ("Socket %p closed: %s", sock, strerror (err));
}
static PseudoTcpWriteResult
write_packet (PseudoTcpSocket *sock, const gchar *buffer, guint32 len,
gpointer user_data)
{
Data *data = user_data;
gchar *str; /* owned */
GQueue/*<owned GBytes>*/ *queue; /* unowned */
GBytes *segment; /* owned */
/* Debug output. */
str = segment_bytes_to_string ((const guint8 *) buffer);
g_debug ("%p sent: %s", sock, str);
g_free (str);
/* One of the sockets has outputted a packet. */
if (sock == data->left)
queue = data->left_sent;
else if (sock == data->right)
queue = data->right_sent;
else
g_assert_not_reached ();
segment = g_bytes_new (buffer, len);
g_queue_push_tail (queue, segment);
return WR_SUCCESS;
}
static void
create_sockets (Data *data, gboolean support_fin_ack)
{
PseudoTcpCallbacks cbs = {
data, opened, readable, writable, closed, write_packet
};
data->left = g_object_new (PSEUDO_TCP_SOCKET_TYPE,
"conversation", 0,
"callbacks", &cbs,
"support-fin-ack", support_fin_ack,
NULL);
data->right = g_object_new (PSEUDO_TCP_SOCKET_TYPE,
"conversation", 0,
"callbacks", &cbs,
"support-fin-ack", support_fin_ack,
NULL);
g_debug ("Left: %p, right: %p", data->left, data->right);
/* Control the socket clocks precisely. */
pseudo_tcp_socket_set_time (data->left, 1);
pseudo_tcp_socket_set_time (data->right, 1);
/* Sanity check the socket state. */
g_assert_cmpint (pseudo_tcp_socket_send (data->left, "foo", 3), ==, -1);
g_assert_cmpint (pseudo_tcp_socket_get_error (data->left), ==, ENOTCONN);
g_assert_cmpint (pseudo_tcp_socket_send (data->right, "foo", 3), ==, -1);
g_assert_cmpint (pseudo_tcp_socket_get_error (data->right), ==, ENOTCONN);
data->left_sent = g_queue_new ();
data->right_sent = g_queue_new ();
}
static void
expect_segment (PseudoTcpSocket *socket, GQueue/*<owned GBytes>*/ *queue,
guint32 seq, guint32 ack, guint32 len, SegmentFlags flags)
{
GBytes *bytes; /* unowned */
union {
const guint8 *u8;
const guint32 *u32;
} b;
gsize size;
gchar *str;
str = segment_to_string (seq, ack, flags);
g_debug ("%p expect: %s", socket, str);
g_free (str);
/* Grab the segment. */
bytes = g_queue_peek_head (queue);
g_assert_true (bytes != NULL);
b.u8 = g_bytes_get_data (bytes, &size);
g_assert_cmpuint (size, >=, 24); /* minimum packet size */
g_assert_cmpuint (size - 24, ==, len);
/* Check the segments fields. */
g_assert_cmpuint (ntohl (b.u32[1]), ==, seq);
g_assert_cmpuint (ntohl (b.u32[2]), ==, ack);
g_assert_cmpuint (b.u8[13], ==, flags);
}
static void
expect_syn_sent (Data *data)
{
expect_segment (data->left, data->left_sent, 0, 0, 7, FLAG_SYN);
}
static void
expect_syn_received (Data *data)
{
expect_segment (data->right, data->right_sent, 0, 7, 7, FLAG_SYN);
}
static void
assert_empty_queues (Data *data)
{
g_assert_cmpuint (g_queue_get_length (data->left_sent), ==, 0);
g_assert_cmpuint (g_queue_get_length (data->right_sent), ==, 0);
}
/* Return whether the socket accepted the packet. */
static gboolean
forward_segment (GQueue/*<owned GBytes>*/ *from, PseudoTcpSocket *to)
{
GBytes *segment; /* owned */
const guint8 *b;
gsize size;
gboolean retval;
segment = g_queue_pop_head (from);
g_assert_true (segment != NULL);
b = g_bytes_get_data (segment, &size);
retval = pseudo_tcp_socket_notify_packet (to, (const gchar *) b, size);
g_bytes_unref (segment);
return retval;
}
static void
forward_segment_ltr (Data *data)
{
g_assert_true (forward_segment (data->left_sent, data->right));
}
static void
forward_segment_rtl (Data *data)
{
g_assert_true (forward_segment (data->right_sent, data->left));
}
static void
duplicate_segment (GQueue/*<owned GBytes>*/ *queue)
{
GBytes *segment; /* unowned */
segment = g_queue_peek_head (queue);
g_assert_true (segment != NULL);
g_queue_push_head (queue, g_bytes_ref (segment));
}
static void
drop_segment (PseudoTcpSocket *socket, GQueue/*<owned GBytes>*/ *queue)
{
GBytes *segment; /* owned */
gchar *str;
segment = g_queue_pop_head (queue);
g_assert_true (segment != NULL);
str = segment_bytes_to_string (g_bytes_get_data (segment, NULL));
g_debug ("%p drop: %s", socket, str);
g_free (str);
g_bytes_unref (segment);
}
/* Swap the order of the head-most two segments in the @queue. */
static void
reorder_segments (PseudoTcpSocket *socket, GQueue/*<owned GBytes>*/ *queue)
{
GBytes *segment1, *segment2; /* unowned */
gchar *str;
segment1 = g_queue_pop_head (queue);
g_assert_true (segment1 != NULL);
segment2 = g_queue_pop_head (queue);
g_assert_true (segment2 != NULL);
str = segment_bytes_to_string (g_bytes_get_data (segment1, NULL));
g_debug ("%p reorder: %s", socket, str);
g_free (str);
str = segment_bytes_to_string (g_bytes_get_data (segment2, NULL));
g_debug ("%p after: %s", socket, str);
g_free (str);
g_queue_push_head (queue, segment1);
g_queue_push_head (queue, segment2);
}
static void
expect_socket_state (PseudoTcpSocket *socket, PseudoTcpState expected_state)
{
PseudoTcpState state;
g_object_get (socket, "state", &state, NULL);
g_assert_cmpuint (state, ==, expected_state);
}
static void
expect_sockets_connected (Data *data)
{
expect_socket_state (data->left, PSEUDO_TCP_ESTABLISHED);
expect_socket_state (data->right, PSEUDO_TCP_ESTABLISHED);
}
static void
expect_sockets_closed (Data *data)
{
guint8 buf[100];
expect_socket_state (data->left, PSEUDO_TCP_CLOSED);
expect_socket_state (data->right, PSEUDO_TCP_CLOSED);
g_assert_cmpint (pseudo_tcp_socket_send (data->left, "foo", 3), ==, -1);
g_assert_cmpint (pseudo_tcp_socket_get_error (data->left), ==, EPIPE);
g_assert_cmpint (pseudo_tcp_socket_recv (data->left, (char *) buf, sizeof (buf)), ==, 0);
g_assert_cmpint (pseudo_tcp_socket_send (data->right, "foo", 3), ==, -1);
g_assert_cmpint (pseudo_tcp_socket_get_error (data->right), ==, EPIPE);
g_assert_cmpint (pseudo_tcp_socket_recv (data->right, (char *) buf, sizeof (buf)), ==, 0);
}
static void
increment_time (PseudoTcpSocket *socket, guint32 *counter, guint32 increment)
{
g_debug ("Incrementing %p time by %u from %u to %u", socket, increment,
*counter, *counter + increment);
*counter = *counter + increment;
pseudo_tcp_socket_set_time (socket, *counter);
pseudo_tcp_socket_notify_clock (socket);
}
static void
increment_time_both (Data *data, guint32 increment)
{
increment_time (data->left, &data->left_current_time, increment);
increment_time (data->right, &data->right_current_time, increment);
}
static void
expect_fin (PseudoTcpSocket *socket, GQueue/*<owned GBytes>*/ *queue,
guint32 seq, guint32 ack)
{
expect_segment (socket, queue, seq, ack, 0, FLAG_FIN);
}
static void
expect_rst (PseudoTcpSocket *socket, GQueue/*<owned GBytes>*/ *queue,
guint32 seq, guint32 ack)
{
expect_segment (socket, queue, seq, ack, 0, FLAG_RST);
}
static void
expect_ack (PseudoTcpSocket *socket, GQueue/*<owned GBytes>*/ *queue,
guint32 seq, guint32 ack)
{
expect_segment (socket, queue, seq, ack, 0, FLAG_NONE);
}
static void
expect_data (PseudoTcpSocket *socket, GQueue/*<owned GBytes>*/ *queue,
guint32 seq, guint32 ack, guint32 len)
{
expect_segment (socket, queue, seq, ack, len, FLAG_NONE);
}
static void
close_socket (PseudoTcpSocket *socket)
{
guint8 buf[100];
pseudo_tcp_socket_close (socket, FALSE);
g_assert_cmpint (pseudo_tcp_socket_send (socket, "foo", 3), ==, -1);
g_assert_cmpint (pseudo_tcp_socket_get_error (socket), ==, EPIPE);
g_assert_cmpint (pseudo_tcp_socket_recv (socket, (char *) buf, sizeof (buf)), ==, 0);
}
/* Helper to create a socket pair and perform the SYN handshake. */
static void
establish_connection (Data *data)
{
create_sockets (data, TRUE);
pseudo_tcp_socket_connect (data->left);
expect_syn_sent (data);
forward_segment_ltr (data);
expect_syn_received (data);
forward_segment_rtl (data);
increment_time_both (data, 110);
expect_ack (data->left, data->left_sent, 7, 7);
forward_segment_ltr (data);
expect_sockets_connected (data);
assert_empty_queues (data);
}
/* Helper to close the LHS of a socket pair which has not transmitted any
* data (i.e. perform the first half of the FIN handshake). */
static void
close_lhs (Data *data)
{
pseudo_tcp_socket_close (data->left, FALSE);
expect_fin (data->left, data->left_sent, 7, 7);
forward_segment_ltr (data);
expect_ack (data->right, data->right_sent, 7, 8);
forward_segment_rtl (data);
}
/* Helper to close the RHS of a socket pair which has not transmitted any
* data (i.e. perform the second half of the FIN handshake). */
static void
close_rhs (Data *data)
{
pseudo_tcp_socket_close (data->right, FALSE);
expect_fin (data->right, data->right_sent, 7, 8);
forward_segment_rtl (data);
increment_time_both (data, 10); /* TIME-WAIT */
expect_ack (data->left, data->left_sent, 8, 8);
forward_segment_ltr (data);
}
/* Check that establishing a connection then immediately closing it works, using
* normal handshakes (FIN, ACK, FIN, ACK). See: RFC 793, Figure 13. */
static void
pseudotcp_close_normal (void)
{
Data data = { 0, };
guint8 buf[100];
/* Establish a connection. */
establish_connection (&data);
/* Close it. Verify that sending fails. */
close_socket (data.left);
expect_fin (data.left, data.left_sent, 7, 7);
forward_segment_ltr (&data);
expect_ack (data.right, data.right_sent, 7, 8);
forward_segment_rtl (&data);
/* Check the RHS is closed. */
g_assert_cmpint (pseudo_tcp_socket_recv (data.right, (char *) buf, sizeof (buf)), ==, 0);
close_socket (data.right);
expect_fin (data.right, data.right_sent, 7, 8);
forward_segment_rtl (&data);
increment_time_both (&data, 10); /* TIME-WAIT */
expect_ack (data.left, data.left_sent, 8, 8);
forward_segment_ltr (&data);
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that establishing a connection then immediately closing it works, using
* simultaneous handshakes (FIN, FIN, ACK, ACK). See: RFC 793, Figure 14. */
static void
pseudotcp_close_simultaneous (void)
{
Data data = { 0, };
/* Establish a connection. */
establish_connection (&data);
/* Close both sides simultaneously. Verify that sending fails. */
close_socket (data.left);
close_socket (data.right);
expect_fin (data.left, data.left_sent, 7, 7);
expect_fin (data.right, data.right_sent, 7, 7);
forward_segment_ltr (&data);
forward_segment_rtl (&data);
expect_ack (data.left, data.left_sent, 8, 8);
expect_ack (data.right, data.right_sent, 8, 8);
forward_segment_ltr (&data);
forward_segment_rtl (&data);
increment_time_both (&data, 10); /* TIME-WAIT */
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that establishing a connection then immediately closing it works, using
* skewed handshakes. The segments are reordered so that the FIN and ACK from
* the LHS arrive at the RHS in reverse order. The RHS sees the ACK has a higher
* sequence number than the bytes its seen so far (as it hasnt seen the LHS
* FIN at that point) and thus emits two sequential ACKs: one from before
* receiving the FIN (fast retransmit), and one from after.
* See: RFC 793, Figure 14. */
static void
pseudotcp_close_skew1 (void)
{
Data data = { 0, };
/* Establish a connection. */
establish_connection (&data);
/* Close both sides simultaneously. Verify that sending fails. */
close_socket (data.left);
close_socket (data.right);
expect_fin (data.left, data.left_sent, 7, 7);
expect_fin (data.right, data.right_sent, 7, 7);
forward_segment_rtl (&data);
reorder_segments (data.left, data.left_sent);
expect_ack (data.left, data.left_sent, 8, 8);
forward_segment_ltr (&data);
forward_segment_ltr (&data);
expect_ack (data.right, data.right_sent, 8, 7);
forward_segment_rtl (&data);
expect_ack (data.right, data.right_sent, 8, 8);
forward_segment_rtl (&data);
increment_time_both (&data, 10); /* TIME-WAIT */
expect_sockets_closed (&data);
data_clear (&data);
}
/* Same as pseudotcp_close_skew1() but with the packets reordered in a
* different way. */
static void
pseudotcp_close_skew2 (void)
{
Data data = { 0, };
/* Establish a connection. */
establish_connection (&data);
/* Close both sides simultaneously. Verify that sending fails. */
close_socket (data.left);
close_socket (data.right);
expect_fin (data.right, data.right_sent, 7, 7);
expect_fin (data.left, data.left_sent, 7, 7);
forward_segment_ltr (&data);
reorder_segments (data.right, data.right_sent);
expect_ack (data.right, data.right_sent, 8, 8);
forward_segment_rtl (&data);
forward_segment_rtl (&data);
expect_ack (data.left, data.left_sent, 8, 7);
forward_segment_ltr (&data);
expect_ack (data.left, data.left_sent, 8, 8);
forward_segment_ltr (&data);
increment_time_both (&data, 10); /* TIME-WAIT */
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that closing a connection recovers from the initial FIN segment being
* dropped. Based on: RFC 793, Figure 13. */
static void
pseudotcp_close_normal_recovery1 (void)
{
Data data = { 0, };
/* Establish a connection. */
establish_connection (&data);
/* Close the LHS and drop the FIN segment. */
close_socket (data.left);
expect_fin (data.left, data.left_sent, 7, 7);
drop_segment (data.left, data.left_sent);
increment_time_both (&data, 1100); /* retransmit timeout */
expect_fin (data.left, data.left_sent, 7, 7);
forward_segment_ltr (&data);
expect_ack (data.right, data.right_sent, 7, 8);
forward_segment_rtl (&data);
/* Close the RHS. */
close_rhs (&data);
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that closing a connection recovers from the initial ACK segment being
* dropped. Based on: RFC 793, Figure 13. */
static void
pseudotcp_close_normal_recovery2 (void)
{
Data data = { 0, };
/* Establish a connection. */
establish_connection (&data);
/* Close the LHS and drop the ACK segment. The LHS should retransmit the
* FIN. */
close_socket (data.left);
expect_fin (data.left, data.left_sent, 7, 7);
forward_segment_ltr (&data);
expect_ack (data.right, data.right_sent, 7, 8);
drop_segment (data.right, data.right_sent);
increment_time_both (&data, 1100); /* retransmit timeout */
expect_fin (data.left, data.left_sent, 7, 7);
forward_segment_ltr (&data);
expect_ack (data.right, data.right_sent, 7, 8);
forward_segment_rtl (&data);
/* Close the RHS. */
close_rhs (&data);
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that closing a connection recovers from the second FIN segment being
* dropped. Based on: RFC 793, Figure 13. */
static void
pseudotcp_close_normal_recovery3 (void)
{
Data data = { 0, };
/* Establish a connection. */
establish_connection (&data);
/* Close the LHS. */
close_lhs (&data);
/* Close the RHS and drop the FIN segment. */
close_socket (data.right);
expect_fin (data.right, data.right_sent, 7, 8);
drop_segment (data.right, data.right_sent);
increment_time_both (&data, 300); /* retransmit timeout */
expect_fin (data.right, data.right_sent, 7, 8);
forward_segment_rtl (&data);
increment_time_both (&data, 10); /* TIME-WAIT */
expect_ack (data.left, data.left_sent, 8, 8);
forward_segment_ltr (&data);
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that closing a connection recovers from the second ACK segment being
* dropped. Based on: RFC 793, Figure 13. */
static void
pseudotcp_close_normal_recovery4 (void)
{
Data data = { 0, };
/* Establish a connection. */
establish_connection (&data);
/* Close the LHS. */
close_lhs (&data);
/* Close the RHS and drop the ACK segment. The RHS should retransmit the
* FIN. The timers for the two peers are manipulated separately so the LHS
* doesnt exceed its TIME-WAIT while waiting for the retransmit. */
close_socket (data.right);
expect_fin (data.right, data.right_sent, 7, 8);
forward_segment_rtl (&data);
expect_ack (data.left, data.left_sent, 8, 8);
drop_segment (data.left, data.left_sent);
increment_time (data.right, &data.right_current_time, 300); /* retransmit timeout */
expect_fin (data.right, data.right_sent, 7, 8);
forward_segment_rtl (&data);
increment_time (data.left, &data.left_current_time, 10); /* TIME-WAIT */
expect_ack (data.left, data.left_sent, 8, 8);
forward_segment_ltr (&data);
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that closing a connection recovers from a data segment being dropped
* immediately before the first FIN is sent. Based on: RFC 793, Figure 13. */
static void
pseudotcp_close_normal_recovery_data (void)
{
Data data = { 0, };
/* Establish a connection. */
establish_connection (&data);
/* Send some data from LHS to RHS, but drop the segment. */
g_assert_cmpint (pseudo_tcp_socket_send (data.left, "foo", 3), ==, 3);
expect_data (data.left, data.left_sent, 7, 7, 3);
drop_segment (data.left, data.left_sent);
assert_empty_queues(&data);
/* Close the LHS. */
g_assert_cmpint (pseudo_tcp_socket_get_available_bytes (data.left), ==, 0);
g_assert_cmpint (pseudo_tcp_socket_get_available_bytes (data.right), ==, 0);
close_socket (data.left);
expect_socket_state (data.left, PSEUDO_TCP_FIN_WAIT_1);
expect_fin (data.left, data.left_sent, 10, 7);
forward_segment_ltr (&data);
expect_socket_state (data.right, PSEUDO_TCP_ESTABLISHED);
expect_ack (data.right, data.right_sent, 7, 7);
forward_segment_rtl (&data);
expect_socket_state (data.left, PSEUDO_TCP_FIN_WAIT_1);
assert_empty_queues(&data);
/* Close the RHS. */
close_socket (data.right);
expect_socket_state (data.right, PSEUDO_TCP_FIN_WAIT_1);
expect_fin (data.right, data.right_sent, 7, 7);
forward_segment_rtl (&data);
expect_socket_state (data.left, PSEUDO_TCP_CLOSING);
expect_ack (data.left, data.left_sent, 11, 8);
forward_segment_ltr (&data);
expect_socket_state (data.right, PSEUDO_TCP_FIN_WAIT_2);
expect_data (data.right, data.right_sent, 8, 7, 0);
forward_segment_rtl (&data);
expect_socket_state (data.left, PSEUDO_TCP_CLOSING);
expect_data (data.left, data.left_sent, 7, 8, 3);
forward_segment_ltr (&data);
expect_socket_state (data.right, PSEUDO_TCP_TIME_WAIT);
increment_time_both (&data, 100); /* Delayed ACK */
expect_ack (data.right, data.right_sent, 8, 11);
forward_segment_rtl (&data);
expect_socket_state (data.left, PSEUDO_TCP_TIME_WAIT);
increment_time_both (&data, 10); /* TIME-WAIT */
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that if both FIN segments from a simultaneous FIN handshake are
* dropped, the handshake recovers and completes successfully.
* See: RFC 793, Figure 14. */
static void
pseudotcp_close_simultaneous_recovery1 (void)
{
Data data = { 0, };
/* Establish a connection. */
establish_connection (&data);
/* Close both sides simultaneously and drop the FINs. */
close_socket (data.left);
close_socket (data.right);
expect_fin (data.left, data.left_sent, 7, 7);
expect_fin (data.right, data.right_sent, 7, 7);
drop_segment (data.left, data.left_sent);
drop_segment (data.right, data.right_sent);
increment_time_both (&data, 1200); /* retransmit timeout */
expect_fin (data.left, data.left_sent, 7, 7);
expect_fin (data.right, data.right_sent, 7, 7);
forward_segment_ltr (&data);
forward_segment_rtl (&data);
expect_ack (data.left, data.left_sent, 8, 8);
expect_ack (data.right, data.right_sent, 8, 8);
forward_segment_ltr (&data);
forward_segment_rtl (&data);
increment_time_both (&data, 10); /* TIME-WAIT */
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that if both ACK segments from a simultaneous FIN handshake are
* dropped, the handshake recovers and completes successfully.
* See: RFC 793, Figure 14. */
static void
pseudotcp_close_simultaneous_recovery2 (void)
{
Data data = { 0, };
/* Establish a connection. */
establish_connection (&data);
/* Close both sides simultaneously and forward the FINs. */
close_socket (data.left);
close_socket (data.right);
expect_fin (data.left, data.left_sent, 7, 7);
expect_fin (data.right, data.right_sent, 7, 7);
forward_segment_ltr (&data);
forward_segment_rtl (&data);
/* Drop the ACKs. */
expect_ack (data.left, data.left_sent, 8, 8);
expect_ack (data.right, data.right_sent, 8, 8);
drop_segment (data.left, data.left_sent);
drop_segment (data.right, data.right_sent);
increment_time_both (&data, 1200); /* retransmit timeout */
expect_fin (data.left, data.left_sent, 7, 8);
expect_fin (data.right, data.right_sent, 7, 8);
forward_segment_ltr (&data);
forward_segment_rtl (&data);
expect_ack (data.left, data.left_sent, 8, 8);
expect_ack (data.right, data.right_sent, 8, 8);
forward_segment_ltr (&data);
forward_segment_rtl (&data);
increment_time_both (&data, 10); /* TIME-WAIT */
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that closing a connection ignores a duplicate FIN segment.
* Based on: RFC 793, Figure 13. */
static void
pseudotcp_close_duplicate_fin (void)
{
Data data = { 0, };
/* Establish a connection. */
establish_connection (&data);
/* Close the LHS. */
close_lhs (&data);
/* Close the RHS and duplicate the FIN segment. */
close_socket (data.right);
expect_fin (data.right, data.right_sent, 7, 8);
duplicate_segment (data.right_sent);
forward_segment_rtl (&data);
forward_segment_rtl (&data);
increment_time (data.left, &data.left_current_time, 10); /* TIME-WAIT */
expect_ack (data.left, data.left_sent, 8, 8);
forward_segment_ltr (&data);
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that closing a connection ignores a duplicate ACK segment.
* Based on: RFC 793, Figure 13. */
static void
pseudotcp_close_duplicate_ack (void)
{
Data data = { 0, };
/* Establish a connection. */
establish_connection (&data);
/* Close the LHS. */
close_lhs (&data);
/* Close the RHS and duplicate the ACK segment. The RHS should reject the
* duplicate with a RST segment. The LHS should then reject the RST. */
close_socket (data.right);
expect_fin (data.right, data.right_sent, 7, 8);
forward_segment_rtl (&data);
increment_time (data.left, &data.left_current_time, 10); /* TIME-WAIT */
expect_ack (data.left, data.left_sent, 8, 8);
duplicate_segment (data.left_sent);
forward_segment_ltr (&data);
g_assert_true (!forward_segment (data.left_sent, data.right));
expect_rst (data.right, data.right_sent, 8, 8);
g_assert_true (!forward_segment (data.right_sent, data.left));
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that forcefully closing a connection by sending a RST segment works.
* See: RFC 1122, §4.2.2.13. */
static void
pseudotcp_close_rst (void)
{
Data data = { 0, };
guint8 buf[100];
/* Establish a connection. */
establish_connection (&data);
/* Close the LHS. */
pseudo_tcp_socket_close (data.left, TRUE);
g_assert_cmpint (pseudo_tcp_socket_send (data.left, "foo", 3), ==, -1);
g_assert_cmpint (pseudo_tcp_socket_get_error (data.left), ==, EPIPE);
g_assert_cmpint (pseudo_tcp_socket_recv (data.left, (char *) buf, sizeof (buf)), ==, 0);
expect_rst (data.left, data.left_sent, 7, 7);
g_assert_true (!forward_segment (data.left_sent, data.right));
/* Check the RHS is closed. */
g_assert_cmpint (pseudo_tcp_socket_send (data.right, "foo", 3), ==, -1);
g_assert_cmpint (pseudo_tcp_socket_get_error (data.right), ==, EPIPE);
g_assert_cmpint (pseudo_tcp_socket_recv (data.right, (char *) buf, sizeof (buf)), ==, 0);
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that an RST is sent if a connection is closed with pending data in the
* local receive buffer. See: RFC 1122, §4.2.2.13. */
static void
pseudotcp_close_pending_received (void)
{
Data data = { 0, };
guint8 buf[100];
/* Establish a connection. */
establish_connection (&data);
/* Send some data from RHS to LHS. Do *not* read the data from the LHS receive
* buffer. */
g_assert_cmpint (pseudo_tcp_socket_send (data.right, "foo", 3), ==, 3);
expect_data (data.right, data.right_sent, 7, 7, 3);
forward_segment_rtl (&data);
/* Close the LHS. */
g_assert_cmpint (pseudo_tcp_socket_get_available_bytes (data.left), ==, 3);
close_socket (data.left);
expect_rst (data.left, data.left_sent, 7, 10);
g_assert_true (!forward_segment (data.left_sent, data.right));
/* Check the RHS is closed. */
g_assert_cmpint (pseudo_tcp_socket_send (data.right, "foo", 3), ==, -1);
g_assert_cmpint (pseudo_tcp_socket_get_error (data.right), ==, EPIPE);
g_assert_cmpint (pseudo_tcp_socket_recv (data.right, (char *) buf, sizeof (buf)), ==, 0);
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that an RST is sent if data is received on a socket after close() has
* been called. See: RFC 1122, §4.2.2.13. */
static void
pseudotcp_close_rst_afterwards (void)
{
Data data = { 0, };
guint8 buf[100];
/* Establish a connection. */
establish_connection (&data);
/* Close the LHS. */
g_assert_cmpint (pseudo_tcp_socket_get_available_bytes (data.left), ==, 0);
pseudo_tcp_socket_close (data.left, TRUE);
close_socket (data.left);
expect_rst (data.left, data.left_sent, 7, 7);
drop_segment (data.left, data.left_sent); /* just to get it out of the way */
assert_empty_queues(&data);
/* Send some data from RHS to LHS, which should result in an RST. */
g_assert_cmpint (pseudo_tcp_socket_send (data.right, "foo", 3), ==, 3);
expect_data (data.right, data.right_sent, 7, 7, 3);
g_assert_true (!forward_segment (data.right_sent, data.left));
expect_rst (data.left, data.left_sent, 7, 7);
g_assert_true (!forward_segment (data.left_sent, data.right));
/* Check the RHS is closed. */
g_assert_cmpint (pseudo_tcp_socket_send (data.right, "foo", 3), ==, -1);
g_assert_cmpint (pseudo_tcp_socket_get_error (data.right), ==, EPIPE);
g_assert_cmpint (pseudo_tcp_socket_recv (data.right, (char *) buf, sizeof (buf)), ==, 0);
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that two pseudo-TCP sockets interact correctly even if FINACK support
* is disabled on one of them. */
static void
pseudotcp_compatibility (void)
{
Data data = { 0, };
guint8 buf[100];
guint64 timeout;
/* Establish a connection. Note the sequence numbers should start at 4 this
* time, rather than the 7 in other tests, because the FINACK option should
* not be being sent. */
create_sockets (&data, FALSE);
pseudo_tcp_socket_connect (data.left);
expect_segment (data.left, data.left_sent, 0, 0, 4, FLAG_SYN);
forward_segment_ltr (&data);
expect_segment (data.right, data.right_sent, 0, 4, 4, FLAG_SYN);
forward_segment_rtl (&data);
increment_time_both (&data, 110);
expect_ack (data.left, data.left_sent, 4, 4);
forward_segment_ltr (&data);
expect_sockets_connected (&data);
/* Close it. Sending shouldnt fail. */
pseudo_tcp_socket_close (data.left, FALSE);
g_assert_true (!pseudo_tcp_socket_is_closed (data.left));
g_assert_cmpint (pseudo_tcp_socket_send (data.left, "foo", 3), ==, 3);
g_assert_cmpint (pseudo_tcp_socket_recv (data.left, (char *) buf, sizeof (buf)), ==, -1);
g_assert_cmpint (pseudo_tcp_socket_get_error (data.left), ==, EWOULDBLOCK);
expect_data (data.left, data.left_sent, 4, 4, 3);
forward_segment_ltr (&data);
increment_time_both (&data, 100);
expect_ack (data.right, data.right_sent, 4, 7);
forward_segment_rtl (&data);
/* Advance the timers; now the LHS should be closed, as the RHS has ACKed all
* outstanding data. */
increment_time_both (&data, 50);
g_assert_true (!pseudo_tcp_socket_get_next_clock (data.left, &timeout));
/* Check the RHS can be closed after receiving the data just sent. */
g_assert_cmpint (pseudo_tcp_socket_recv (data.right, (char *) buf, sizeof (buf)), ==, 3);
g_assert_cmpint (pseudo_tcp_socket_recv (data.right, (char *) buf, sizeof (buf)), ==, -1);
g_assert_cmpint (pseudo_tcp_socket_get_error (data.right), ==, EWOULDBLOCK);
pseudo_tcp_socket_close (data.right, FALSE);
g_assert_true (!pseudo_tcp_socket_get_next_clock (data.right, &timeout));
expect_sockets_closed (&data);
data_clear (&data);
}
/* Check that after receiving a FIN, queued data can still be read */
static void
pseudotcp_close_recv_queued (void)
{
Data data = { 0, };
guint8 buf[100];
/* Establish a connection. */
establish_connection (&data);
g_assert_cmpint (pseudo_tcp_socket_get_available_bytes (data.left), ==, 0);
g_assert_cmpint (pseudo_tcp_socket_get_available_bytes (data.right), ==, 0);
g_assert_cmpint (pseudo_tcp_socket_get_available_send_space (data.right), >,
0);
g_assert_cmpint (pseudo_tcp_socket_get_available_send_space (data.left), >,
0);
g_assert_cmpint (pseudo_tcp_socket_send (data.left, "foo", 3), ==, 3);
expect_data (data.left, data.left_sent, 7, 7, 3);
forward_segment_ltr (&data);
increment_time_both (&data, 100); /* Delayed ACK */
expect_ack (data.right, data.right_sent, 7, 10);
forward_segment_rtl (&data);
close_socket (data.left);
expect_fin (data.left, data.left_sent, 10, 7);
forward_segment_ltr (&data);
expect_socket_state (data.left, PSEUDO_TCP_FIN_WAIT_1);
expect_socket_state (data.right, PSEUDO_TCP_CLOSE_WAIT);
g_assert_cmpint (pseudo_tcp_socket_get_available_bytes (data.left), ==, 0);
g_assert_cmpint (pseudo_tcp_socket_get_available_send_space (data.left), ==,
0);
expect_ack (data.right, data.right_sent, 7, 11);
forward_segment_rtl (&data);
expect_socket_state (data.left, PSEUDO_TCP_FIN_WAIT_2);
g_assert_cmpint (pseudo_tcp_socket_get_available_bytes (data.right), ==, 3);
g_assert_cmpint (pseudo_tcp_socket_get_available_send_space (data.right), >,
0);
/* Check that the data can be read */
g_assert_cmpint (pseudo_tcp_socket_recv (data.right, (char *) buf, sizeof (buf)), ==, 3);
/* Now the socket should be empty */
g_assert_cmpint (pseudo_tcp_socket_recv (data.right, (char *) buf, sizeof (buf)), ==, 0);
data_clear (&data);
}
int
main (int argc, char *argv[])
{
setlocale (LC_ALL, "");
g_test_init (&argc, &argv, NULL);
pseudo_tcp_set_debug_level (PSEUDO_TCP_DEBUG_VERBOSE);
/* There are four possible scenarios for the FIN handshake, if the possibility
* of dropped or duplicated segments is ignored (but reordered segments are
* allowed: normal, simultaneous, and two types of skew.
*
* These can be generated by considering the events happening at a single peer
* during connection closure: sending the FIN (SF), receiving a FIN and
* sending a FIN-ACK (RF), receiving a FIN-ACK (RA). These have the following
* permutations:
* • SF, RF, RA
* • SF, RA, RF
* • RF, SF, RA
* Other permutations are disallowed because SF must come before RA.
*
* The permutations of one peers (1) behaviour with a second (2) can then be
* considered:
* • 1: SF, RF, RA; 2: SF, RF, RA (simultaneous)
* • 1: SF, RF, RA; 2: SF, RA, RF (skew 1)
* • 1: SF, RF, RA; 2: RF, SF, RA (skew 2)
* • 1: SF, RA, RF; 2: RF, SF, RA (normal)
* Other permutations are disallowed because SF on one peer must come before
* RF on the other; similarly RF on one must come before RA on the other.
*
* Thus, the following unit tests provably cover all possible scenarios where
* segments can be reordered but not dropped or duplicated. */
g_test_add_func ("/pseudotcp/close/normal",
pseudotcp_close_normal);
g_test_add_func ("/pseudotcp/close/simultaneous",
pseudotcp_close_simultaneous);
g_test_add_func ("/pseudotcp/close/skew1",
pseudotcp_close_skew1);
g_test_add_func ("/pseudotcp/close/skew2",
pseudotcp_close_skew2);
/* An arbitrary (less methodical) selection of unit tests for dropped and
* duplicated packets. */
g_test_add_func ("/pseudotcp/close/normal/recovery1",
pseudotcp_close_normal_recovery1);
g_test_add_func ("/pseudotcp/close/normal/recovery2",
pseudotcp_close_normal_recovery2);
g_test_add_func ("/pseudotcp/close/normal/recovery3",
pseudotcp_close_normal_recovery3);
g_test_add_func ("/pseudotcp/close/normal/recovery4",
pseudotcp_close_normal_recovery4);
g_test_add_func ("/pseudotcp/close/normal/recovery-data",
pseudotcp_close_normal_recovery_data);
g_test_add_func ("/pseudotcp/close/simultaneous/recovery1",
pseudotcp_close_simultaneous_recovery1);
g_test_add_func ("/pseudotcp/close/simultaneous/recovery2",
pseudotcp_close_simultaneous_recovery2);
g_test_add_func ("/pseudotcp/close/duplicate-fin",
pseudotcp_close_duplicate_fin);
g_test_add_func ("/pseudotcp/close/duplicate-ack",
pseudotcp_close_duplicate_ack);
g_test_add_func ("/pseudotcp/close/rst",
pseudotcp_close_rst);
g_test_add_func ("/pseudotcp/close/pending-received",
pseudotcp_close_pending_received);
g_test_add_func ("/pseudotcp/close/rst-afterwards",
pseudotcp_close_rst_afterwards);
g_test_add_func ("/pseudotcp/close/recv-queued",
pseudotcp_close_recv_queued);
g_test_add_func ("/pseudotcp/compatibility",
pseudotcp_compatibility);
g_test_run ();
return 0;
}