mirror of
https://github.com/tailscale/tailscale.git
synced 2025-06-20 15:18:40 +00:00
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>
This commit is contained in:
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 {
|
||||||
|
return mkPacketErr(eth, ip, udp, gopacket.Payload(payload))
|
||||||
}
|
}
|
||||||
if err := gopacket.SerializeLayers(buffer, options, layers...); err != nil {
|
|
||||||
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…
x
Reference in New Issue
Block a user