From e971923a92f736c069458cfddc623635fc4aa19c Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 29 Jul 2024 13:56:41 -0700 Subject: [PATCH] name NAT types, add constructors Change-Id: Id558e763773e6efa700cfb7943b64c78bfffc4ed Signed-off-by: Brad Fitzpatrick --- natlab/natlabd/nat.go | 56 ++++++++++++++++++++++++++++++++++++--- natlab/natlabd/natlabd.go | 39 +++++++++++++++++++++------ 2 files changed, 84 insertions(+), 11 deletions(-) diff --git a/natlab/natlabd/nat.go b/natlab/natlabd/nat.go index 28dfd17e3..d1d24ebf4 100644 --- a/natlab/natlabd/nat.go +++ b/natlab/natlabd/nat.go @@ -1,6 +1,7 @@ package main import ( + "errors" "math/rand/v2" "net/netip" "time" @@ -8,6 +9,38 @@ import ( "tailscale.com/util/mak" ) +// IPPool is the interface that a NAT implementation uses to get information +// about a network. +// +// Outside of tests, this is typically a *network. +type IPPool interface { + // WANIP returns the primary WAN IP address. + // + // TODO: add another method for networks with multiple WAN IP addresses. + WANIP() netip.Addr + + // SoleLanIP reports whether this network has a sole LAN client + // and if so, its IP address. + SoleLANIP() (_ netip.Addr, ok bool) + + // TODO: port availability stuff for interacting with portmapping +} + +// newTableFunc is a constructor for a NAT table. +// The provided IPPool is typically (outside of tests) a *network. +type newTableFunc func(IPPool) (NATTable, error) + +// natTypes are the known NAT types. +var natTypes = map[string]newTableFunc{} + +// registerNATType registers a NAT type. +func registerNATType(name string, f newTableFunc) { + if _, ok := natTypes[name]; ok { + panic("duplicate NAT type: " + name) + } + natTypes[name] = f +} + // NATTable is what a NAT implementation is expected to do. // // This project tests Tailscale as it faces various combinations various NAT @@ -46,7 +79,15 @@ type oneToOneNAT struct { wanIP netip.Addr } -var _ NATTable = (*oneToOneNAT)(nil) +func init() { + registerNATType("one2one", func(p IPPool) (NATTable, error) { + lanIP, ok := p.SoleLANIP() + if !ok { + return nil, errors.New("can't use one2one NAT type on networks other than single-node networks") + } + return &oneToOneNAT{lanIP: lanIP, wanIP: p.WANIP()}, nil + }) +} func (n *oneToOneNAT) PickOutgoingSrc(src, dst netip.AddrPort, at time.Time) (wanSrc netip.AddrPort) { return netip.AddrPortFrom(n.wanIP, src.Port()) @@ -86,7 +127,11 @@ type hardNAT struct { in map[hardKeyIn]lanAddrAndTime } -var _ NATTable = (*hardNAT)(nil) +func init() { + registerNATType("hard", func(p IPPool) (NATTable, error) { + return &hardNAT{wanIP: p.WANIP()}, nil + }) +} func (n *hardNAT) PickOutgoingSrc(src, dst netip.AddrPort, at time.Time) (wanSrc netip.AddrPort) { ko := hardKeyOut{src.Addr(), dst} @@ -146,7 +191,11 @@ type easyNAT struct { in map[uint16]lanAddrAndTime } -var _ NATTable = (*easyNAT)(nil) +func init() { + registerNATType("easy", func(p IPPool) (NATTable, error) { + return &easyNAT{wanIP: p.WANIP()}, nil + }) +} func (n *easyNAT) PickOutgoingSrc(src, dst netip.AddrPort, at time.Time) (wanSrc netip.AddrPort) { if pm, ok := n.out[src]; ok { @@ -162,6 +211,7 @@ func (n *easyNAT) PickOutgoingSrc(src, dst netip.AddrPort, at time.Time) (wanSrc port := 32<<10 + (start+off)%(32<<10) if _, ok := n.in[port]; !ok { wanAddr := netip.AddrPortFrom(n.wanIP, port) + // Found a free port. mak.Set(&n.out, src, portMappingAndTime{port: port, at: at}) mak.Set(&n.in, port, lanAddrAndTime{lanAddr: src, at: at}) diff --git a/natlab/natlabd/natlabd.go b/natlab/natlabd/natlabd.go index 850d73ae3..064c7d546 100644 --- a/natlab/natlabd/natlabd.go +++ b/natlab/natlabd/natlabd.go @@ -45,7 +45,7 @@ import ( var ( listen = flag.String("listen", "/tmp/qemu.sock", "path to listen on") - hard = flag.Bool("hard", false, "use hard NAT") + natType = flag.String("nat", "easy", "type of NAT to use") portmap = flag.Bool("portmap", false, "enable portmapping") ) @@ -84,7 +84,9 @@ func main() { } s.nodes[node1.mac] = node1 - net1.InitNAT(*hard) + if err := net1.InitNAT(*natType); err != nil { + log.Fatalf("InitNAT: %v", err) + } net1.portmap = *portmap net2 := &network{ s: s, @@ -98,7 +100,9 @@ func main() { lanIP: netip.MustParseAddr("10.2.0.102"), } s.nodes[node2.mac] = node2 - net2.InitNAT(*hard) + if err := net2.InitNAT(*natType); err != nil { + log.Fatalf("InitNAT: %v", err) + } if err := s.checkWorld(); err != nil { log.Fatalf("checkWorld: %v", err) @@ -201,12 +205,17 @@ func (s *Server) checkWorld() error { return nil } -func (n *network) InitNAT(useHardNAT bool) { - if useHardNAT { - n.SetNATTable(&hardNAT{wanIP: n.wanIP}) - } else { - n.SetNATTable(&easyNAT{wanIP: n.wanIP}) +func (n *network) InitNAT(natType string) error { + ctor, ok := natTypes[natType] + if !ok { + return fmt.Errorf("unknown NAT type %q", natType) } + t, err := ctor(n) + if err != nil { + return fmt.Errorf("error creating NAT type %q for network %v: %w", natType, n.wanIP, err) + } + n.SetNATTable(t) + return nil } func (n *network) SetNATTable(nt NATTable) { @@ -215,6 +224,20 @@ func (n *network) SetNATTable(nt NATTable) { n.natTable = nt } +// SoleLANIP implements [IPPool]. +func (n *network) SoleLANIP() (netip.Addr, bool) { + if len(n.nodesByIP) != 1 { + return netip.Addr{}, false + } + for ip := range n.nodesByIP { + return ip, true + } + return netip.Addr{}, false +} + +// WANIP implements [IPPool]. +func (n *network) WANIP() netip.Addr { return n.wanIP } + func (n *network) initStack() error { n.ns = stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{