|
@@ -10,11 +10,15 @@ import (
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"net"
|
|
"net"
|
|
|
"net/netip"
|
|
"net/netip"
|
|
|
|
|
+ "path/filepath"
|
|
|
|
|
+ "runtime"
|
|
|
"strings"
|
|
"strings"
|
|
|
"testing"
|
|
"testing"
|
|
|
|
|
+ "time"
|
|
|
|
|
|
|
|
"github.com/google/gopacket"
|
|
"github.com/google/gopacket"
|
|
|
"github.com/google/gopacket/layers"
|
|
"github.com/google/gopacket/layers"
|
|
|
|
|
+ "tailscale.com/util/must"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
// TestPacketSideEffects tests that upon receiving certain
|
|
// TestPacketSideEffects tests that upon receiving certain
|
|
@@ -32,13 +36,7 @@ func TestPacketSideEffects(t *testing.T) {
|
|
|
}{
|
|
}{
|
|
|
{
|
|
{
|
|
|
netName: "basic",
|
|
netName: "basic",
|
|
|
- setup: func() (*Server, error) {
|
|
|
|
|
- var c Config
|
|
|
|
|
- nw := c.AddNetwork("192.168.0.1/24")
|
|
|
|
|
- c.AddNode(nw)
|
|
|
|
|
- c.AddNode(nw)
|
|
|
|
|
- return New(&c)
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ setup: newTwoNodesSameNetworkServer,
|
|
|
tests: []netTest{
|
|
tests: []netTest{
|
|
|
{
|
|
{
|
|
|
name: "drop-rando-ethertype",
|
|
name: "drop-rando-ethertype",
|
|
@@ -129,6 +127,14 @@ func mkEth(dst, src MAC, ethType layers.EthernetType, payload []byte) []byte {
|
|
|
return append(ret, payload...)
|
|
return append(ret, payload...)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// mkLenPrefixed prepends a uint32 length to the given packet.
|
|
|
|
|
+func mkLenPrefixed(pkt []byte) []byte {
|
|
|
|
|
+ ret := make([]byte, 4+len(pkt))
|
|
|
|
|
+ binary.BigEndian.PutUint32(ret, uint32(len(pkt)))
|
|
|
|
|
+ copy(ret[4:], pkt)
|
|
|
|
|
+ return ret
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// mkIPv6RouterSolicit makes a IPv6 router solicitation packet
|
|
// mkIPv6RouterSolicit makes a IPv6 router solicitation packet
|
|
|
// ethernet frame.
|
|
// ethernet frame.
|
|
|
func mkIPv6RouterSolicit(srcMAC MAC, srcIP netip.Addr) []byte {
|
|
func mkIPv6RouterSolicit(srcMAC MAC, srcIP netip.Addr) []byte {
|
|
@@ -230,3 +236,156 @@ func numPkts(want int) func(*sideEffects) error {
|
|
|
return fmt.Errorf("got %d packets, want %d. packets were:\n%s", len(se.got), want, pkts.Bytes())
|
|
return fmt.Errorf("got %d packets, want %d. packets were:\n%s", len(se.got), want, pkts.Bytes())
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+func newTwoNodesSameNetworkServer() (*Server, error) {
|
|
|
|
|
+ var c Config
|
|
|
|
|
+ nw := c.AddNetwork("192.168.0.1/24")
|
|
|
|
|
+ c.AddNode(nw)
|
|
|
|
|
+ c.AddNode(nw)
|
|
|
|
|
+ return New(&c)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// TestProtocolQEMU tests the protocol that qemu uses to connect to natlab's
|
|
|
|
|
+// vnet. (uint32-length prefixed ethernet frames over a unix stream socket)
|
|
|
|
|
+//
|
|
|
|
|
+// This test makes two clients (as qemu would act) and has one send an ethernet
|
|
|
|
|
+// packet to the other virtual LAN segment.
|
|
|
|
|
+func TestProtocolQEMU(t *testing.T) {
|
|
|
|
|
+ if runtime.GOOS == "windows" {
|
|
|
|
|
+ t.Skipf("skipping on %s", runtime.GOOS)
|
|
|
|
|
+ }
|
|
|
|
|
+ s := must.Get(newTwoNodesSameNetworkServer())
|
|
|
|
|
+ defer s.Close()
|
|
|
|
|
+ s.SetLoggerForTest(t.Logf)
|
|
|
|
|
+
|
|
|
|
|
+ td := t.TempDir()
|
|
|
|
|
+ serverSock := filepath.Join(td, "vnet.sock")
|
|
|
|
|
+
|
|
|
|
|
+ ln, err := net.Listen("unix", serverSock)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ defer ln.Close()
|
|
|
|
|
+
|
|
|
|
|
+ var clientc [2]*net.UnixConn
|
|
|
|
|
+ for i := range clientc {
|
|
|
|
|
+ c, err := net.Dial("unix", serverSock)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ defer c.Close()
|
|
|
|
|
+ clientc[i] = c.(*net.UnixConn)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for range clientc {
|
|
|
|
|
+ conn, err := ln.Accept()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ go s.ServeUnixConn(conn.(*net.UnixConn), ProtocolQEMU)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ sendBetweenClients(t, clientc, s, mkLenPrefixed)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// TestProtocolUnixDgram tests the protocol that macOS Virtualization.framework
|
|
|
|
|
+// uses to connect to vnet. (unix datagram sockets)
|
|
|
|
|
+//
|
|
|
|
|
+// It is similar to TestProtocolQEMU but uses unix datagram sockets instead of
|
|
|
|
|
+// streams.
|
|
|
|
|
+func TestProtocolUnixDgram(t *testing.T) {
|
|
|
|
|
+ if runtime.GOOS == "windows" {
|
|
|
|
|
+ t.Skipf("skipping on %s", runtime.GOOS)
|
|
|
|
|
+ }
|
|
|
|
|
+ s := must.Get(newTwoNodesSameNetworkServer())
|
|
|
|
|
+ defer s.Close()
|
|
|
|
|
+ s.SetLoggerForTest(t.Logf)
|
|
|
|
|
+
|
|
|
|
|
+ td := t.TempDir()
|
|
|
|
|
+ serverSock := filepath.Join(td, "vnet.sock")
|
|
|
|
|
+ serverAddr := must.Get(net.ResolveUnixAddr("unixgram", serverSock))
|
|
|
|
|
+
|
|
|
|
|
+ var clientSock [2]string
|
|
|
|
|
+ for i := range clientSock {
|
|
|
|
|
+ clientSock[i] = filepath.Join(td, fmt.Sprintf("c%d.sock", i))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ uc, err := net.ListenUnixgram("unixgram", serverAddr)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ go s.ServeUnixConn(uc, ProtocolUnixDGRAM)
|
|
|
|
|
+
|
|
|
|
|
+ var clientc [2]*net.UnixConn
|
|
|
|
|
+ for i := range clientc {
|
|
|
|
|
+ c, err := net.DialUnix("unixgram",
|
|
|
|
|
+ must.Get(net.ResolveUnixAddr("unixgram", clientSock[i])),
|
|
|
|
|
+ serverAddr)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ defer c.Close()
|
|
|
|
|
+ clientc[i] = c
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ sendBetweenClients(t, clientc, s, nil)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// sendBetweenClients is a test helper that tries to send an ethernet frame from
|
|
|
|
|
+// one client to another.
|
|
|
|
|
+//
|
|
|
|
|
+// It first makes the two clients send a packet to a fictitious node 3, which
|
|
|
|
|
+// forces their src MACs to be registered with a networkWriter internally so
|
|
|
|
|
+// they can receive traffic.
|
|
|
|
|
+//
|
|
|
|
|
+// Normally a node starts up spamming DHCP + NDP but we don't get that as a side
|
|
|
|
|
+// effect here, so this does it manually.
|
|
|
|
|
+//
|
|
|
|
|
+// It also then waits for them to be registered.
|
|
|
|
|
+//
|
|
|
|
|
+// wrap is an optional function that wraps the packet before sending it.
|
|
|
|
|
+func sendBetweenClients(t testing.TB, clientc [2]*net.UnixConn, s *Server, wrap func([]byte) []byte) {
|
|
|
|
|
+ t.Helper()
|
|
|
|
|
+ if wrap == nil {
|
|
|
|
|
+ wrap = func(b []byte) []byte { return b }
|
|
|
|
|
+ }
|
|
|
|
|
+ for i, c := range clientc {
|
|
|
|
|
+ must.Get(c.Write(wrap(mkEth(nodeMac(3), nodeMac(i+1), testingEthertype, []byte("hello")))))
|
|
|
|
|
+ }
|
|
|
|
|
+ awaitCond(t, 5*time.Second, func() error {
|
|
|
|
|
+ if n := s.RegisteredWritersForTest(); n != 2 {
|
|
|
|
|
+ return fmt.Errorf("got %d registered writers, want 2", n)
|
|
|
|
|
+ }
|
|
|
|
|
+ return nil
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Now see if node1 can write to node2 and node2 receives it.
|
|
|
|
|
+ pkt := wrap(mkEth(nodeMac(2), nodeMac(1), testingEthertype, []byte("test-msg")))
|
|
|
|
|
+ t.Logf("writing % 02x", pkt)
|
|
|
|
|
+ must.Get(clientc[0].Write(pkt))
|
|
|
|
|
+
|
|
|
|
|
+ buf := make([]byte, len(pkt))
|
|
|
|
|
+ clientc[1].SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
|
|
|
+ n, err := clientc[1].Read(buf)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ got := buf[:n]
|
|
|
|
|
+ if !bytes.Equal(got, pkt) {
|
|
|
|
|
+ t.Errorf("bad packet\n got: % 02x\nwant: % 02x", got, pkt)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func awaitCond(t testing.TB, timeout time.Duration, cond func() error) {
|
|
|
|
|
+ t.Helper()
|
|
|
|
|
+ t0 := time.Now()
|
|
|
|
|
+ for {
|
|
|
|
|
+ if err := cond(); err == nil {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if time.Since(t0) > timeout {
|
|
|
|
|
+ t.Fatalf("timed out after %v", timeout)
|
|
|
|
|
+ }
|
|
|
|
|
+ time.Sleep(10 * time.Millisecond)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|