@ -10,11 +10,15 @@ import (
"fmt"
"net"
"net/netip"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"tailscale.com/util/must"
)
// TestPacketSideEffects tests that upon receiving certain
@ -32,13 +36,7 @@ func TestPacketSideEffects(t *testing.T) {
} {
{
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 {
{
name : "drop-rando-ethertype" ,
@ -129,6 +127,14 @@ func mkEth(dst, src MAC, ethType layers.EthernetType, payload []byte) []byte {
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
// ethernet frame.
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 ( ) )
}
}
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 )
}
}