// Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause package natlab import ( "context" "fmt" "net" "net/netip" "testing" "time" "tailscale.com/tstest" ) func TestAllocIPs(t *testing.T) { n := NewInternet() saw := map[netip.Addr]bool{} for range 255 { for _, f := range []func(*Interface) netip.Addr{n.allocIPv4, n.allocIPv6} { ip := f(nil) if saw[ip] { t.Fatalf("got duplicate %v", ip) } saw[ip] = true } } // This should work: n.allocIPv6(nil) // But allocating another IPv4 should panic, exhausting the // limited /24 range: defer func() { if e := recover(); fmt.Sprint(e) != "pool exhausted" { t.Errorf("unexpected panic: %v", e) } }() n.allocIPv4(nil) t.Fatalf("expected panic from IPv4") } func TestSendPacket(t *testing.T) { internet := NewInternet() foo := &Machine{Name: "foo"} bar := &Machine{Name: "bar"} ifFoo := foo.Attach("eth0", internet) ifBar := bar.Attach("enp0s1", internet) fooAddr := netip.AddrPortFrom(ifFoo.V4(), 123) barAddr := netip.AddrPortFrom(ifBar.V4(), 456) ctx := context.Background() fooPC, err := foo.ListenPacket(ctx, "udp4", fooAddr.String()) if err != nil { t.Fatal(err) } barPC, err := bar.ListenPacket(ctx, "udp4", barAddr.String()) if err != nil { t.Fatal(err) } const msg = "some message" if _, err := fooPC.WriteTo([]byte(msg), net.UDPAddrFromAddrPort(barAddr)); err != nil { t.Fatal(err) } buf := make([]byte, 1500) // TODO: care about MTUs in the natlab package somewhere n, addr, err := barPC.ReadFrom(buf) if err != nil { t.Fatal(err) } buf = buf[:n] if string(buf) != msg { t.Errorf("read %q; want %q", buf, msg) } if addr.String() != fooAddr.String() { t.Errorf("addr = %q; want %q", addr, fooAddr) } } func TestMultiNetwork(t *testing.T) { lan := &Network{ Name: "lan", Prefix4: mustPrefix("192.168.0.0/24"), } internet := NewInternet() client := &Machine{Name: "client"} nat := &Machine{Name: "nat"} server := &Machine{Name: "server"} ifClient := client.Attach("eth0", lan) ifNATWAN := nat.Attach("ethwan", internet) ifNATLAN := nat.Attach("ethlan", lan) ifServer := server.Attach("eth0", internet) ctx := context.Background() clientPC, err := client.ListenPacket(ctx, "udp", ":123") if err != nil { t.Fatal(err) } natPC, err := nat.ListenPacket(ctx, "udp", ":456") if err != nil { t.Fatal(err) } serverPC, err := server.ListenPacket(ctx, "udp", ":789") if err != nil { t.Fatal(err) } clientAddr := netip.AddrPortFrom(ifClient.V4(), 123) natLANAddr := netip.AddrPortFrom(ifNATLAN.V4(), 456) natWANAddr := netip.AddrPortFrom(ifNATWAN.V4(), 456) serverAddr := netip.AddrPortFrom(ifServer.V4(), 789) const msg1, msg2 = "hello", "world" if _, err := natPC.WriteTo([]byte(msg1), net.UDPAddrFromAddrPort(clientAddr)); err != nil { t.Fatal(err) } if _, err := natPC.WriteTo([]byte(msg2), net.UDPAddrFromAddrPort(serverAddr)); err != nil { t.Fatal(err) } buf := make([]byte, 1500) n, addr, err := clientPC.ReadFrom(buf) if err != nil { t.Fatal(err) } if string(buf[:n]) != msg1 { t.Errorf("read %q; want %q", buf[:n], msg1) } if addr.String() != natLANAddr.String() { t.Errorf("addr = %q; want %q", addr, natLANAddr) } n, addr, err = serverPC.ReadFrom(buf) if err != nil { t.Fatal(err) } if string(buf[:n]) != msg2 { t.Errorf("read %q; want %q", buf[:n], msg2) } if addr.String() != natWANAddr.String() { t.Errorf("addr = %q; want %q", addr, natLANAddr) } } type trivialNAT struct { clientIP netip.Addr lanIf, wanIf *Interface } func (n *trivialNAT) HandleIn(p *Packet, iface *Interface) *Packet { if iface == n.wanIf && p.Dst.Addr() == n.wanIf.V4() { p.Dst = netip.AddrPortFrom(n.clientIP, p.Dst.Port()) } return p } func (n trivialNAT) HandleOut(p *Packet, iface *Interface) *Packet { return p } func (n *trivialNAT) HandleForward(p *Packet, iif, oif *Interface) *Packet { // Outbound from LAN -> apply NAT, continue if iif == n.lanIf && oif == n.wanIf { if p.Src.Addr() == n.clientIP { p.Src = netip.AddrPortFrom(n.wanIf.V4(), p.Src.Port()) } return p } // Return traffic to LAN, allow if right dst. if iif == n.wanIf && oif == n.lanIf && p.Dst.Addr() == n.clientIP { return p } // Else drop. return nil } func TestPacketHandler(t *testing.T) { lan := &Network{ Name: "lan", Prefix4: mustPrefix("192.168.0.0/24"), Prefix6: mustPrefix("fd00:916::/64"), } internet := NewInternet() client := &Machine{Name: "client"} nat := &Machine{Name: "nat"} server := &Machine{Name: "server"} ifClient := client.Attach("eth0", lan) ifNATWAN := nat.Attach("wan", internet) ifNATLAN := nat.Attach("lan", lan) ifServer := server.Attach("server", internet) lan.SetDefaultGateway(ifNATLAN) nat.PacketHandler = &trivialNAT{ clientIP: ifClient.V4(), lanIf: ifNATLAN, wanIf: ifNATWAN, } ctx := context.Background() clientPC, err := client.ListenPacket(ctx, "udp4", ":123") if err != nil { t.Fatal(err) } serverPC, err := server.ListenPacket(ctx, "udp4", ":456") if err != nil { t.Fatal(err) } const msg = "some message" serverAddr := netip.AddrPortFrom(ifServer.V4(), 456) if _, err := clientPC.WriteTo([]byte(msg), net.UDPAddrFromAddrPort(serverAddr)); err != nil { t.Fatal(err) } buf := make([]byte, 1500) // TODO: care about MTUs in the natlab package somewhere n, addr, err := serverPC.ReadFrom(buf) if err != nil { t.Fatal(err) } buf = buf[:n] if string(buf) != msg { t.Errorf("read %q; want %q", buf, msg) } mappedAddr := netip.AddrPortFrom(ifNATWAN.V4(), 123) if addr.String() != mappedAddr.String() { t.Errorf("addr = %q; want %q", addr, mappedAddr) } } func TestFirewall(t *testing.T) { wan := NewInternet() lan := &Network{ Name: "lan", Prefix4: mustPrefix("10.0.0.0/8"), } m := &Machine{Name: "test"} trust := m.Attach("trust", lan) untrust := m.Attach("untrust", wan) client := ipp("192.168.0.2:1234") serverA := ipp("2.2.2.2:5678") serverB1 := ipp("7.7.7.7:9012") serverB2 := ipp("7.7.7.7:3456") t.Run("ip_port_dependent", func(t *testing.T) { f := &Firewall{ TrustedInterface: trust, SessionTimeout: 30 * time.Second, Type: AddressAndPortDependentFirewall, } testFirewall(t, f, []fwTest{ // client -> A authorizes A -> client {trust, untrust, client, serverA, true}, {untrust, trust, serverA, client, true}, {untrust, trust, serverA, client, true}, // B1 -> client fails until client -> B1 {untrust, trust, serverB1, client, false}, {trust, untrust, client, serverB1, true}, {untrust, trust, serverB1, client, true}, // B2 -> client still fails {untrust, trust, serverB2, client, false}, }) }) t.Run("ip_dependent", func(t *testing.T) { f := &Firewall{ TrustedInterface: trust, SessionTimeout: 30 * time.Second, Type: AddressDependentFirewall, } testFirewall(t, f, []fwTest{ // client -> A authorizes A -> client {trust, untrust, client, serverA, true}, {untrust, trust, serverA, client, true}, {untrust, trust, serverA, client, true}, // B1 -> client fails until client -> B1 {untrust, trust, serverB1, client, false}, {trust, untrust, client, serverB1, true}, {untrust, trust, serverB1, client, true}, // B2 -> client also works now {untrust, trust, serverB2, client, true}, }) }) t.Run("endpoint_independent", func(t *testing.T) { f := &Firewall{ TrustedInterface: trust, SessionTimeout: 30 * time.Second, Type: EndpointIndependentFirewall, } testFirewall(t, f, []fwTest{ // client -> A authorizes A -> client {trust, untrust, client, serverA, true}, {untrust, trust, serverA, client, true}, {untrust, trust, serverA, client, true}, // B1 -> client also works {untrust, trust, serverB1, client, true}, // B2 -> client also works {untrust, trust, serverB2, client, true}, }) }) } type fwTest struct { iif, oif *Interface src, dst netip.AddrPort ok bool } func testFirewall(t *testing.T, f *Firewall, tests []fwTest) { t.Helper() clock := &tstest.Clock{} f.TimeNow = clock.Now for _, test := range tests { clock.Advance(time.Second) p := &Packet{ Src: test.src, Dst: test.dst, Payload: []byte{}, } got := f.HandleForward(p, test.iif, test.oif) gotOK := got != nil if gotOK != test.ok { t.Errorf("iif=%s oif=%s src=%s dst=%s got ok=%v, want ok=%v", test.iif, test.oif, test.src, test.dst, gotOK, test.ok) } } } func ipp(str string) netip.AddrPort { ipp, err := netip.ParseAddrPort(str) if err != nil { panic(err) } return ipp } func TestNAT(t *testing.T) { internet := NewInternet() lan := &Network{ Name: "LAN", Prefix4: mustPrefix("192.168.0.0/24"), } m := &Machine{Name: "NAT"} wanIf := m.Attach("wan", internet) lanIf := m.Attach("lan", lan) t.Run("endpoint_independent_mapping", func(t *testing.T) { n := &SNAT44{ Machine: m, ExternalInterface: wanIf, Type: EndpointIndependentNAT, Firewall: &Firewall{ TrustedInterface: lanIf, }, } testNAT(t, n, lanIf, wanIf, []natTest{ { src: ipp("192.168.0.20:1234"), dst: ipp("2.2.2.2:5678"), wantNewMapping: true, }, { src: ipp("192.168.0.20:1234"), dst: ipp("7.7.7.7:9012"), wantNewMapping: false, }, { src: ipp("192.168.0.20:2345"), dst: ipp("7.7.7.7:9012"), wantNewMapping: true, }, }) }) t.Run("address_dependent_mapping", func(t *testing.T) { n := &SNAT44{ Machine: m, ExternalInterface: wanIf, Type: AddressDependentNAT, Firewall: &Firewall{ TrustedInterface: lanIf, }, } testNAT(t, n, lanIf, wanIf, []natTest{ { src: ipp("192.168.0.20:1234"), dst: ipp("2.2.2.2:5678"), wantNewMapping: true, }, { src: ipp("192.168.0.20:1234"), dst: ipp("2.2.2.2:9012"), wantNewMapping: false, }, { src: ipp("192.168.0.20:1234"), dst: ipp("7.7.7.7:9012"), wantNewMapping: true, }, { src: ipp("192.168.0.20:1234"), dst: ipp("7.7.7.7:1234"), wantNewMapping: false, }, }) }) t.Run("address_and_port_dependent_mapping", func(t *testing.T) { n := &SNAT44{ Machine: m, ExternalInterface: wanIf, Type: AddressAndPortDependentNAT, Firewall: &Firewall{ TrustedInterface: lanIf, }, } testNAT(t, n, lanIf, wanIf, []natTest{ { src: ipp("192.168.0.20:1234"), dst: ipp("2.2.2.2:5678"), wantNewMapping: true, }, { src: ipp("192.168.0.20:1234"), dst: ipp("2.2.2.2:9012"), wantNewMapping: true, }, { src: ipp("192.168.0.20:1234"), dst: ipp("7.7.7.7:9012"), wantNewMapping: true, }, { src: ipp("192.168.0.20:1234"), dst: ipp("7.7.7.7:1234"), wantNewMapping: true, }, }) }) } type natTest struct { src, dst netip.AddrPort wantNewMapping bool } func testNAT(t *testing.T, n *SNAT44, lanIf, wanIf *Interface, tests []natTest) { clock := &tstest.Clock{} n.TimeNow = clock.Now mappings := map[netip.AddrPort]bool{} for _, test := range tests { clock.Advance(time.Second) p := &Packet{ Src: test.src, Dst: test.dst, Payload: []byte("foo"), } gotPacket := n.HandleForward(p.Clone(), lanIf, wanIf) if gotPacket == nil { t.Errorf("n.HandleForward(%v) dropped packet", p) continue } if gotPacket.Dst != p.Dst { t.Errorf("n.HandleForward(%v) mutated dest ip:port, got %v", p, gotPacket.Dst) } gotNewMapping := !mappings[gotPacket.Src] if gotNewMapping != test.wantNewMapping { t.Errorf("n.HandleForward(%v) mapping was new=%v, want %v", p, gotNewMapping, test.wantNewMapping) } mappings[gotPacket.Src] = true // Check that the return path works and translates back // correctly. clock.Advance(time.Second) p2 := &Packet{ Src: test.dst, Dst: gotPacket.Src, Payload: []byte("bar"), } gotPacket2 := n.HandleIn(p2.Clone(), wanIf) if gotPacket2 == nil { t.Errorf("return packet was dropped") continue } if gotPacket2.Src != test.dst { t.Errorf("return packet has src=%v, want %v", gotPacket2.Src, test.dst) } if gotPacket2.Dst != test.src { t.Errorf("return packet has dst=%v, want %v", gotPacket2.Dst, test.src) } } }