diff --git a/common/turbotunnel/queuepacketconn.go b/common/turbotunnel/queuepacketconn.go
index 14a98331f36fdedcc2d1386e18626148a3c4941d..e11f8d8ea1f1be0c1d5d7b0ef42fa720e7d62bf8 100644
--- a/common/turbotunnel/queuepacketconn.go
+++ b/common/turbotunnel/queuepacketconn.go
@@ -42,8 +42,9 @@ func NewQueuePacketConn(localAddr net.Addr, timeout time.Duration) *QueuePacketC
 	}
 }
 
-// QueueIncoming queues and incoming packet and its source address, to be
-// returned in a future call to ReadFrom.
+// QueueIncoming queues an incoming packet and its source address, to be
+// returned in a future call to ReadFrom. This function takes ownership of p and
+// the caller must not reuse it.
 func (c *QueuePacketConn) QueueIncoming(p []byte, addr net.Addr) {
 	select {
 	case <-c.closed:
@@ -51,11 +52,8 @@ func (c *QueuePacketConn) QueueIncoming(p []byte, addr net.Addr) {
 		return
 	default:
 	}
-	// Copy the slice so that the caller may reuse it.
-	buf := make([]byte, len(p))
-	copy(buf, p)
 	select {
-	case c.recvQueue <- taggedPacket{buf, addr}:
+	case c.recvQueue <- taggedPacket{p, addr}:
 	default:
 		// Drop the incoming packet if the receive queue is full.
 	}
@@ -84,22 +82,20 @@ func (c *QueuePacketConn) ReadFrom(p []byte) (int, net.Addr, error) {
 }
 
 // WriteTo queues an outgoing packet for the given address. The queue can later
-// be retrieved using the OutgoingQueue method.
+// be retrieved using the OutgoingQueue method. This function takes ownership of
+// p and the caller must not reuse it.
 func (c *QueuePacketConn) WriteTo(p []byte, addr net.Addr) (int, error) {
 	select {
 	case <-c.closed:
 		return 0, &net.OpError{Op: "write", Net: c.LocalAddr().Network(), Addr: c.LocalAddr(), Err: c.err.Load().(error)}
 	default:
 	}
-	// Copy the slice so that the caller may reuse it.
-	buf := make([]byte, len(p))
-	copy(buf, p)
 	select {
-	case c.clients.SendQueue(addr) <- buf:
-		return len(buf), nil
+	case c.clients.SendQueue(addr) <- p:
+		return len(p), nil
 	default:
 		// Drop the outgoing packet if the send queue is full.
-		return len(buf), nil
+		return len(p), nil
 	}
 }
 
diff --git a/common/turbotunnel/queuepacketconn_test.go b/common/turbotunnel/queuepacketconn_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..49ab6aaecdacd34baf83422cfd8d600721557d87
--- /dev/null
+++ b/common/turbotunnel/queuepacketconn_test.go
@@ -0,0 +1,43 @@
+package turbotunnel
+
+import (
+	"testing"
+	"time"
+)
+
+type emptyAddr struct{}
+
+func (_ emptyAddr) Network() string { return "empty" }
+func (_ emptyAddr) String() string  { return "empty" }
+
+// Run with -benchmem to see memory allocations.
+func BenchmarkQueueIncoming(b *testing.B) {
+	conn := NewQueuePacketConn(emptyAddr{}, 1*time.Hour)
+	defer conn.Close()
+
+	b.ResetTimer()
+	s := 500
+	for i := 0; i < b.N; i++ {
+		// Use a variable for the length to stop the compiler from
+		// optimizing out the allocation.
+		p := make([]byte, s)
+		conn.QueueIncoming(p, emptyAddr{})
+	}
+	b.StopTimer()
+}
+
+// BenchmarkWriteTo benchmarks the QueuePacketConn.WriteTo function.
+func BenchmarkWriteTo(b *testing.B) {
+	conn := NewQueuePacketConn(emptyAddr{}, 1*time.Hour)
+	defer conn.Close()
+
+	b.ResetTimer()
+	s := 500
+	for i := 0; i < b.N; i++ {
+		// Use a variable for the length to stop the compiler from
+		// optimizing out the allocation.
+		p := make([]byte, s)
+		conn.WriteTo(p, emptyAddr{})
+	}
+	b.StopTimer()
+}