Просмотр исходного кода

overlay/darwin: test tun framing and zero-alloc Read/Write (#1906)

Caleb Jasik 3 дней назад
Родитель
Сommit
7daacc6a48
1 измененных файлов с 121 добавлено и 0 удалено
  1. 121 0
      overlay/tun_darwin_test.go

+ 121 - 0
overlay/tun_darwin_test.go

@@ -0,0 +1,121 @@
+//go:build !ios && !e2e_testing
+
+package overlay
+
+import (
+	"bytes"
+	"os"
+	"syscall"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestTun_WriteFraming(t *testing.T) {
+	r, w, err := os.Pipe()
+	require.NoError(t, err)
+	defer r.Close()
+	defer w.Close()
+	tn := &tun{f: w}
+
+	for _, tc := range []struct {
+		name string
+		pkt  []byte
+		af   byte
+	}{
+		// Literal families rather than syscall.AF_*, so a wrong constant in Write can't also fix the expectation.
+		{"ipv4", []byte{0x45, 1, 2, 3, 4, 5}, 2},
+		{"ipv6", []byte{0x60, 1, 2, 3, 4, 5}, 30},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			n, err := tn.Write(tc.pkt)
+			require.NoError(t, err)
+			require.Equal(t, len(tc.pkt), n)
+
+			got := make([]byte, 64)
+			n, err = r.Read(got)
+			require.NoError(t, err)
+			require.Equal(t, append([]byte{0, 0, 0, tc.af}, tc.pkt...), got[:n])
+		})
+	}
+
+	_, err = tn.Write([]byte{0x10, 1, 2, 3})
+	require.Error(t, err)
+	_, err = tn.Write([]byte{})
+	require.ErrorIs(t, err, syscall.EIO)
+
+	// The rejected writes must not have put anything on the fd, so the next read sees only this packet.
+	pkt := []byte{0x45, 9, 9}
+	_, err = tn.Write(pkt)
+	require.NoError(t, err)
+	got := make([]byte, 64)
+	n, err := r.Read(got)
+	require.NoError(t, err)
+	require.Equal(t, append([]byte{0, 0, 0, 2}, pkt...), got[:n])
+}
+
+func TestTun_ReadStripsHeader(t *testing.T) {
+	r, w, err := os.Pipe()
+	require.NoError(t, err)
+	defer r.Close()
+	defer w.Close()
+	tn := &tun{f: r}
+
+	pkt := []byte{0x45, 1, 2, 3, 4, 5}
+	_, err = w.Write(append([]byte{0, 0, 0, syscall.AF_INET}, pkt...))
+	require.NoError(t, err)
+
+	got := make([]byte, 64)
+	n, err := tn.Read(got)
+	require.NoError(t, err)
+	require.Equal(t, pkt, got[:n])
+}
+
+func TestTun_ReadShort(t *testing.T) {
+	r, w, err := os.Pipe()
+	require.NoError(t, err)
+	defer r.Close()
+	defer w.Close()
+	tn := &tun{f: r}
+
+	// Fewer bytes than the protocol header is not a packet.
+	_, err = w.Write([]byte{0, 0})
+	require.NoError(t, err)
+	n, err := tn.Read(make([]byte, 64))
+	require.NoError(t, err)
+	require.Zero(t, n)
+
+	// The next packet reads cleanly only if the short read consumed exactly what was written.
+	pkt := []byte{0x45, 1, 2, 3}
+	_, err = w.Write(append([]byte{0, 0, 0, syscall.AF_INET}, pkt...))
+	require.NoError(t, err)
+	got := make([]byte, 64)
+	n, err = tn.Read(got)
+	require.NoError(t, err)
+	require.Equal(t, pkt, got[:n])
+}
+
+// A heap allocation per packet rarely shows in a short throughput run, only across GC pauses, so
+// this pins Read and Write to zero allocations directly.
+func TestTun_ReadWriteDoNotAllocate(t *testing.T) {
+	r, w, err := os.Pipe()
+	require.NoError(t, err)
+	defer r.Close()
+	defer w.Close()
+	tw := &tun{f: w}
+	tr := &tun{f: r}
+
+	pkts := [][]byte{bytes.Repeat([]byte{0x45}, 1300), bytes.Repeat([]byte{0x60}, 1300)}
+	buf := make([]byte, 1500)
+	i := 0
+	allocs := testing.AllocsPerRun(1000, func() {
+		i++
+		if _, err := tw.Write(pkts[i%len(pkts)]); err != nil {
+			t.Fatal(err)
+		}
+		if _, err := tr.Read(buf); err != nil {
+			t.Fatal(err)
+		}
+	})
+	require.Zero(t, allocs)
+}