name NAT types, add constructors

Change-Id: Id558e763773e6efa700cfb7943b64c78bfffc4ed
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2024-07-29 13:56:41 -07:00
parent 0e9bbbebeb
commit e971923a92
2 changed files with 84 additions and 11 deletions

View File

@ -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})

View File

@ -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{