diff --git a/gtests/net/packetdrill/netdev.c b/gtests/net/packetdrill/netdev.c
index 98a3f006e147147d78442e27aa48fca5cb4c5030..64ac611ebdf2e86d545d2b41281cf5c755d38cd4 100644
--- a/gtests/net/packetdrill/netdev.c
+++ b/gtests/net/packetdrill/netdev.c
@@ -370,21 +370,28 @@ static int local_netdev_receive(struct netdev *a_netdev,
 
 	DEBUGP("local_netdev_receive\n");
 
+	return netdev_receive_loop(netdev->psock, PACKET_LAYER_3_IP,
+				   packet, error);
+}
+
+int netdev_receive_loop(struct packet_socket *psock,
+			enum packet_layer_t layer,
+			struct packet **packet,
+			char **error)
+{
 	assert(*packet == NULL);	/* should be no packet yet */
 	*packet = packet_new(PACKET_READ_BYTES);
 
 	while (1) {
 		int in_bytes = 0;
+		enum packet_parse_result_t result;
 
 		/* Sniff the next outbound packet from the kernel under test. */
-		if (packet_socket_receive(netdev->psock,
-					  DIRECTION_OUTBOUND,
+		if (packet_socket_receive(psock, DIRECTION_OUTBOUND,
 					  *packet, &in_bytes))
 			continue;
 
-		enum packet_parse_result_t result =
-			parse_packet(*packet, in_bytes,
-					     PACKET_LAYER_3_IP, error);
+		result = parse_packet(*packet, in_bytes, layer, error);
 		if (result == PACKET_OK) {
 			return STATUS_OK;
 		} else if (result == PACKET_BAD) {
@@ -392,7 +399,8 @@ static int local_netdev_receive(struct netdev *a_netdev,
 			*packet = NULL;
 			return STATUS_ERR;
 		} else {
-			DEBUGP("error parsing packet: %s\n", *error);
+			DEBUGP("parse_result:%d; error parsing packet: %s\n",
+			       result, *error);
 		}
 	}
 
diff --git a/gtests/net/packetdrill/netdev.h b/gtests/net/packetdrill/netdev.h
index 1eba6d0b97fd51ec72623111398df4bb346eeef9..84e87a0af5771ef6e3fbf6ce885986bad66812f1 100644
--- a/gtests/net/packetdrill/netdev.h
+++ b/gtests/net/packetdrill/netdev.h
@@ -30,6 +30,8 @@
 
 #include "config.h"
 #include "packet.h"
+#include "packet_parser.h"
+#include "packet_socket.h"
 
 struct netdev_ops;
 
@@ -79,6 +81,16 @@ static inline int netdev_receive(struct netdev *netdev,
 	return netdev->ops->receive(netdev, packet, error);
 }
 
+
+/* Keep sniffing packets leaving the kernel until we see one we know
+ * about and can parse. Return a pointer to the newly-allocated
+ * packet. Caller must free the packet with packet_free().
+ */
+extern int netdev_receive_loop(struct packet_socket *psock,
+			       enum packet_layer_t layer,
+			       struct packet **packet,
+			       char **error);
+
 /* Allocate and return a new netdev for purely local tests. */
 extern struct netdev *local_netdev_new(struct config *config);
 
diff --git a/gtests/net/packetdrill/wire_server_netdev.c b/gtests/net/packetdrill/wire_server_netdev.c
index fe57ce4663ad59fca9536df7910e1e93eaddce39..066566f251d5e87674357a904986bfec5cb3d59c 100644
--- a/gtests/net/packetdrill/wire_server_netdev.c
+++ b/gtests/net/packetdrill/wire_server_netdev.c
@@ -191,36 +191,8 @@ static int wire_server_netdev_receive(struct netdev *a_netdev,
 
 	DEBUGP("wire_server_netdev_receive\n");
 
-	assert(*packet == NULL);	/* should be no packet yet */
-	*packet = packet_new(PACKET_READ_BYTES);
-
-	while (1) {
-		int in_bytes = 0;
-		enum packet_parse_result_t result;
-
-		/* Sniff the next inbound packet from the kernel under test. */
-		if (packet_socket_receive(netdev->psock,
-					  DIRECTION_INBOUND,
-					  *packet, &in_bytes))
-			continue;
-
-		result = parse_packet(*packet, in_bytes,
-					      PACKET_LAYER_2_ETHERNET,
-					      error);
-		if (result == PACKET_OK) {
-			return STATUS_OK;
-		} else if (result == PACKET_BAD) {
-			packet_free(*packet);
-			*packet = NULL;
-			return STATUS_ERR;
-		} else {
-			/* TODO: print these packets in verbose mode? */
-			DEBUGP("parse_result:%d; error parsing packet: %s\n",
-			       result, *error);
-		}
-	}
-
-	return STATUS_ERR;
+	return netdev_receive_loop(netdev->psock, PACKET_LAYER_2_ETHERNET,
+				   packet, error);
 }
 
 struct netdev_ops wire_server_netdev_ops = {