From d799c219acfa4721bc43b2cdf328c5a96751c401 Mon Sep 17 00:00:00 2001 From: Neal Cardwell <ncardwell@google.com> Date: Thu, 1 May 2014 11:29:47 -0400 Subject: [PATCH] net-test: packetdrill: read outbound packets from tun device In local mode, read the outbound packets from the tun device. A recent Linux patch "tcp: avoid retransmits of TCP packets hanging in host queues" means that TCP behavior now depends on whether and when the network device (tun device here) consumes the packets. We now read all these packets so that the kernel can exercise its normal code paths for packet transmit completion. An alternative approach would be to set the tun txqueuelen to 0, but this would impact qdisc behavior. It is more realistic, and has fewer side-effects, to make sure the network device has a non-zero queue length, but packets are still consumed. Change-Id: Ia922994d4eeea12e61d7876b4c4ef34bc9cef34e --- gtests/net/packetdrill/netdev.c | 39 +++++++++++++++++++-- gtests/net/packetdrill/netdev.h | 1 + gtests/net/packetdrill/wire_server_netdev.c | 4 ++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/gtests/net/packetdrill/netdev.c b/gtests/net/packetdrill/netdev.c index 1d92da4d..aeca2da0 100644 --- a/gtests/net/packetdrill/netdev.c +++ b/gtests/net/packetdrill/netdev.c @@ -363,25 +363,59 @@ static int local_netdev_send(struct netdev *a_netdev, return STATUS_OK; } +/* Read the given number of packets out of the tun device. We read + * these packets so that the kernel can exercise its normal code paths + * for packet transmit completion, since this code path may feed back + * to TCP behavior; e.g., see the Linux patch "tcp: avoid retransmits + * of TCP packets hanging in host queues". We don't need to actually + * need the packet contents, but on Linux we need to read at least 1 + * byte of packet data to consume the packet. + */ +static void local_netdev_read_queue(struct local_netdev *netdev, + int num_packets) +{ + char buf[1]; + int i = 0, in_bytes = 0; + + for (i = 0; i < num_packets; ++i) { + in_bytes = read(netdev->tun_fd, buf, sizeof(buf)); + assert(in_bytes <= (int)sizeof(buf)); + + if (in_bytes < 0) { + if (errno == EINTR) + continue; + else + die_perror("tun read()"); + } + } +} + static int local_netdev_receive(struct netdev *a_netdev, struct packet **packet, char **error) { struct local_netdev *netdev = to_local_netdev(a_netdev); + int status = STATUS_ERR; + int num_packets = 0; DEBUGP("local_netdev_receive\n"); - return netdev_receive_loop(netdev->psock, PACKET_LAYER_3_IP, - DIRECTION_OUTBOUND, packet, error); + status = netdev_receive_loop(netdev->psock, PACKET_LAYER_3_IP, + DIRECTION_OUTBOUND, packet, &num_packets, + error); + local_netdev_read_queue(netdev, num_packets); + return status; } int netdev_receive_loop(struct packet_socket *psock, enum packet_layer_t layer, enum direction_t direction, struct packet **packet, + int *num_packets, char **error) { assert(*packet == NULL); /* should be no packet yet */ + *num_packets = 0; while (1) { int in_bytes = 0; enum packet_parse_result_t result; @@ -392,6 +426,7 @@ int netdev_receive_loop(struct packet_socket *psock, if (packet_socket_receive(psock, direction, *packet, &in_bytes)) continue; + ++*num_packets; result = parse_packet(*packet, in_bytes, layer, error); if (result == PACKET_OK) diff --git a/gtests/net/packetdrill/netdev.h b/gtests/net/packetdrill/netdev.h index d7444a86..c69c1384 100644 --- a/gtests/net/packetdrill/netdev.h +++ b/gtests/net/packetdrill/netdev.h @@ -90,6 +90,7 @@ extern int netdev_receive_loop(struct packet_socket *psock, enum packet_layer_t layer, enum direction_t direction, struct packet **packet, + int *num_packets, char **error); /* Allocate and return a new netdev for purely local tests. */ diff --git a/gtests/net/packetdrill/wire_server_netdev.c b/gtests/net/packetdrill/wire_server_netdev.c index 219f86c3..cee64e7a 100644 --- a/gtests/net/packetdrill/wire_server_netdev.c +++ b/gtests/net/packetdrill/wire_server_netdev.c @@ -188,11 +188,13 @@ static int wire_server_netdev_receive(struct netdev *a_netdev, struct packet **packet, char **error) { struct wire_server_netdev *netdev = to_server_netdev(a_netdev); + int num_packets = 0; DEBUGP("wire_server_netdev_receive\n"); return netdev_receive_loop(netdev->psock, PACKET_LAYER_2_ETHERNET, - DIRECTION_INBOUND, packet, error); + DIRECTION_INBOUND, packet, &num_packets, + error); } struct netdev_ops wire_server_netdev_ops = { -- GitLab