tstest/natlab/vnet: add more tests

This adds tests for DNS requests, and ignoring IPv6 packets on v4-only
networks.

No behavior changes. But some things are pulled out into functions.

And the mkPacket helpers previously just for tests are moved into
non-test code to be used elsewhere to reduce duplication, doing the
checksum stuff automatically.

Updates #13038

Change-Id: I4dd0b73c75b2b9567b4be3f05a2792999d83f6a3
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/13308/head
Brad Fitzpatrick 3 months ago committed by Brad Fitzpatrick
parent d21ebc28af
commit 82c2c5c597

@ -65,6 +65,11 @@ func nodeMac(n int) MAC {
return MAC{0x52, 0xcc, 0xcc, 0xcc, 0xcc, byte(n)} return MAC{0x52, 0xcc, 0xcc, 0xcc, 0xcc, byte(n)}
} }
func routerMac(n int) MAC {
// 52=TS then 0xee for 'etwork
return MAC{0x52, 0xee, 0xee, 0xee, 0xee, byte(n)}
}
var lanSLAACBase = netip.MustParseAddr("fe80::50cc:ccff:fecc:cc01") var lanSLAACBase = netip.MustParseAddr("fe80::50cc:ccff:fecc:cc01")
// nodeLANIP6 returns a node number's Link Local SLAAC IPv6 address, // nodeLANIP6 returns a node number's Link Local SLAAC IPv6 address,
@ -148,7 +153,7 @@ func (c *Config) AddNetwork(opts ...any) *Network {
num := len(c.networks) + 1 num := len(c.networks) + 1
n := &Network{ n := &Network{
num: num, num: num,
mac: MAC{0x52, 0xee, 0xee, 0xee, 0xee, byte(num)}, // 52=TS then 0xee for 'etwork mac: routerMac(num),
} }
c.networks = append(c.networks, n) c.networks = append(c.networks, n)
for _, o := range opts { for _, o := range opts {

@ -1218,18 +1218,11 @@ func (n *network) serializedUDPPacket(src, dst netip.AddrPort, payload []byte, e
SrcPort: layers.UDPPort(src.Port()), SrcPort: layers.UDPPort(src.Port()),
DstPort: layers.UDPPort(dst.Port()), DstPort: layers.UDPPort(dst.Port()),
} }
udp.SetNetworkLayerForChecksum(ip)
buffer := gopacket.NewSerializeBuffer()
options := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true}
layers := []gopacket.SerializableLayer{eth, ip, udp, gopacket.Payload(payload)}
if eth == nil { if eth == nil {
layers = layers[1:] return mkPacketErr(ip, udp, gopacket.Payload(payload))
} } else {
if err := gopacket.SerializeLayers(buffer, options, layers...); err != nil { return mkPacketErr(eth, ip, udp, gopacket.Payload(payload))
return nil, fmt.Errorf("serializing UDP from %v to %v: %v", src, dst, err)
} }
return buffer.Bytes(), nil
} }
// HandleEthernetPacketForRouter handles a packet that is // HandleEthernetPacketForRouter handles a packet that is
@ -1434,14 +1427,12 @@ func (n *network) handleIPv6RouterSolicitation(ep EthernetPacket, rs *layers.ICM
}, },
}, },
} }
icmp.SetNetworkLayerForChecksum(ip) pkt, err := mkPacketErr(eth, ip, icmp, ra)
buffer := gopacket.NewSerializeBuffer() if err != nil {
options := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true}
if err := gopacket.SerializeLayers(buffer, options, eth, ip, icmp, ra); err != nil {
n.logf("serializing ICMPv6 RA: %v", err) n.logf("serializing ICMPv6 RA: %v", err)
return return
} }
n.writeEth(buffer.Bytes()) n.writeEth(pkt)
} }
func (n *network) handleIPv6NeighborSolicitation(ep EthernetPacket, ns *layers.ICMPv6NeighborSolicitation) { func (n *network) handleIPv6NeighborSolicitation(ep EthernetPacket, ns *layers.ICMPv6NeighborSolicitation) {
@ -2203,3 +2194,35 @@ func (c *NodeAgentClient) EnableHostFirewall(ctx context.Context) error {
} }
return nil return nil
} }
func mkPacket(layers ...gopacket.SerializableLayer) []byte {
return must.Get(mkPacketErr(layers...))
}
func mkPacketErr(ll ...gopacket.SerializableLayer) ([]byte, error) {
var nl gopacket.NetworkLayer
for _, la := range ll {
switch la := la.(type) {
case *layers.IPv4:
nl = la
case *layers.IPv6:
nl = la
}
}
for _, la := range ll {
switch la := la.(type) {
case *layers.TCP:
la.SetNetworkLayerForChecksum(nl)
case *layers.UDP:
la.SetNetworkLayerForChecksum(nl)
case *layers.ICMPv6:
la.SetNetworkLayerForChecksum(nl)
}
}
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true}
if err := gopacket.SerializeLayers(buf, opts, ll...); err != nil {
return nil, fmt.Errorf("serializing packet: %v", err)
}
return buf.Bytes(), nil
}

@ -21,6 +21,11 @@ import (
"tailscale.com/util/must" "tailscale.com/util/must"
) )
const (
ethType4 = layers.EthernetTypeIPv4
ethType6 = layers.EthernetTypeIPv6
)
// TestPacketSideEffects tests that upon receiving certain // TestPacketSideEffects tests that upon receiving certain
// packets, other packets and/or log statements are generated. // packets, other packets and/or log statements are generated.
func TestPacketSideEffects(t *testing.T) { func TestPacketSideEffects(t *testing.T) {
@ -36,7 +41,7 @@ func TestPacketSideEffects(t *testing.T) {
}{ }{
{ {
netName: "basic", netName: "basic",
setup: newTwoNodesSameNetworkServer, setup: newTwoNodesSameNetwork,
tests: []netTest{ tests: []netTest{
{ {
name: "drop-rando-ethertype", name: "drop-rando-ethertype",
@ -63,6 +68,37 @@ func TestPacketSideEffects(t *testing.T) {
pktSubstr("Unable to decode EthernetType 4660"), pktSubstr("Unable to decode EthernetType 4660"),
), ),
}, },
{
name: "dns-request-v4",
pkt: mkDNSReq(4),
check: all(
numPkts(1),
pktSubstr("Data=[52, 52, 0, 3] IP=52.52.0.3"),
),
},
{
name: "dns-request-v6",
pkt: mkDNSReq(6),
check: all(
numPkts(1),
pktSubstr(" IP=2052::3 "),
),
},
},
},
{
netName: "v4",
setup: newTwoNodesSameV4Network,
tests: []netTest{
{
name: "no-v6-reply-on-v4-only",
pkt: mkIPv6RouterSolicit(nodeMac(1), nodeLANIP6(1)),
check: all(
numPkts(0),
logSubstr("dropping IPv6 packet on v4-only network"),
),
},
// TODO(bradfitz): DHCP request + response
}, },
}, },
{ {
@ -170,16 +206,7 @@ func mkIPv6RouterSolicit(srcMAC MAC, srcIP netip.Addr) []byte {
}}, }},
} }
icmp.SetNetworkLayerForChecksum(ip) icmp.SetNetworkLayerForChecksum(ip)
return mkEth(macAllRouters, srcMAC, layers.EthernetTypeIPv6, mkPacket(ip, icmp, ra)) return mkEth(macAllRouters, srcMAC, ethType6, mkPacket(ip, icmp, ra))
}
func mkPacket(layers ...gopacket.SerializableLayer) []byte {
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true}
if err := gopacket.SerializeLayers(buf, opts, layers...); err != nil {
panic(fmt.Sprintf("serializing packet: %v", err))
}
return buf.Bytes()
} }
func mkAllNodesPing(srcMAC MAC, srcIP netip.Addr) []byte { func mkAllNodesPing(srcMAC MAC, srcIP netip.Addr) []byte {
@ -194,7 +221,68 @@ func mkAllNodesPing(srcMAC MAC, srcIP netip.Addr) []byte {
TypeCode: layers.CreateICMPv6TypeCode(layers.ICMPv6TypeEchoRequest, 0), TypeCode: layers.CreateICMPv6TypeCode(layers.ICMPv6TypeEchoRequest, 0),
} }
icmp.SetNetworkLayerForChecksum(ip) icmp.SetNetworkLayerForChecksum(ip)
return mkEth(macAllNodes, srcMAC, layers.EthernetTypeIPv6, mkPacket(ip, icmp)) return mkEth(macAllNodes, srcMAC, ethType6, mkPacket(ip, icmp))
}
// mkDNSReq makes a DNS request to "control.tailscale" using the source IPs as
// defined in this test file.
//
// ipVer must be 4 or 6:
// If 4, it makes an A record request.
// If 6, it makes a AAAA record request.
//
// (Yes, this is technically unrelated (you can request A records over IPv6 or
// AAAA records over IPv4), but for test coverage reasons, assume that the ipVer
// of 6 means to also request an AAAA record.)
func mkDNSReq(ipVer int) []byte {
eth := &layers.Ethernet{
SrcMAC: nodeMac(1).HWAddr(),
DstMAC: routerMac(1).HWAddr(),
EthernetType: layers.EthernetTypeIPv4,
}
if ipVer == 6 {
eth.EthernetType = layers.EthernetTypeIPv6
}
var ip serializableNetworkLayer
switch ipVer {
case 4:
ip = &layers.IPv4{
Version: 4,
Protocol: layers.IPProtocolUDP,
SrcIP: net.ParseIP("192.168.0.101"),
TTL: 64,
DstIP: FakeDNSIPv4().AsSlice(),
}
case 6:
ip = &layers.IPv6{
Version: 6,
HopLimit: 64,
NextHeader: layers.IPProtocolUDP,
SrcIP: net.ParseIP("2000:52::1"),
DstIP: FakeDNSIPv6().AsSlice(),
}
default:
panic("bad ipVer")
}
udp := &layers.UDP{
SrcPort: 12345,
DstPort: 53,
}
udp.SetNetworkLayerForChecksum(ip)
dns := &layers.DNS{
ID: 789,
Questions: []layers.DNSQuestion{{
Name: []byte("control.tailscale"),
Type: layers.DNSTypeA,
Class: layers.DNSClassIN,
}},
}
if ipVer == 6 {
dns.Questions[0].Type = layers.DNSTypeAAAA
}
return mkPacket(eth, ip, udp, dns)
} }
// sideEffects gathers side effects as a result of sending a packet and tests // sideEffects gathers side effects as a result of sending a packet and tests
@ -269,7 +357,15 @@ func numPkts(want int) func(*sideEffects) error {
} }
} }
func newTwoNodesSameNetworkServer() (*Server, error) { func newTwoNodesSameNetwork() (*Server, error) {
var c Config
nw := c.AddNetwork("192.168.0.1/24", "2052::1/64")
c.AddNode(nw)
c.AddNode(nw)
return New(&c)
}
func newTwoNodesSameV4Network() (*Server, error) {
var c Config var c Config
nw := c.AddNetwork("192.168.0.1/24") nw := c.AddNetwork("192.168.0.1/24")
c.AddNode(nw) c.AddNode(nw)
@ -286,7 +382,7 @@ func TestProtocolQEMU(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.Skipf("skipping on %s", runtime.GOOS) t.Skipf("skipping on %s", runtime.GOOS)
} }
s := must.Get(newTwoNodesSameNetworkServer()) s := must.Get(newTwoNodesSameNetwork())
defer s.Close() defer s.Close()
s.SetLoggerForTest(t.Logf) s.SetLoggerForTest(t.Logf)
@ -329,7 +425,7 @@ func TestProtocolUnixDgram(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.Skipf("skipping on %s", runtime.GOOS) t.Skipf("skipping on %s", runtime.GOOS)
} }
s := must.Get(newTwoNodesSameNetworkServer()) s := must.Get(newTwoNodesSameNetwork())
defer s.Close() defer s.Close()
s.SetLoggerForTest(t.Logf) s.SetLoggerForTest(t.Logf)

Loading…
Cancel
Save