tstest/natlab: provide inbound interface to HandlePacket.

Requires a bunch of refactoring so that Networks only ever
refer to Interfaces that have been attached to them, and
Interfaces know about both their Network and Machine.

Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
David Anderson 2020-07-11 03:01:41 +00:00 committed by Dave Anderson
parent 977381f9cc
commit 0ed9f62ed0
2 changed files with 35 additions and 32 deletions

View File

@ -70,29 +70,29 @@ type Network struct {
Prefix6 netaddr.IPPrefix Prefix6 netaddr.IPPrefix
mu sync.Mutex mu sync.Mutex
machine map[netaddr.IP]*Machine machine map[netaddr.IP]*Interface
defaultGW *Machine // optional defaultGW *Interface // optional
lastV4 netaddr.IP lastV4 netaddr.IP
lastV6 netaddr.IP lastV6 netaddr.IP
} }
func (n *Network) SetDefaultGateway(gw *Machine) { func (n *Network) SetDefaultGateway(gwIf *Interface) {
n.mu.Lock() n.mu.Lock()
defer n.mu.Unlock() defer n.mu.Unlock()
n.defaultGW = gw n.defaultGW = gwIf
} }
func (n *Network) addMachineLocked(ip netaddr.IP, m *Machine) { func (n *Network) addMachineLocked(ip netaddr.IP, iface *Interface) {
if m == nil { if iface == nil {
return // for tests return // for tests
} }
if n.machine == nil { if n.machine == nil {
n.machine = map[netaddr.IP]*Machine{} n.machine = map[netaddr.IP]*Interface{}
} }
n.machine[ip] = m n.machine[ip] = iface
} }
func (n *Network) allocIPv4(m *Machine) netaddr.IP { func (n *Network) allocIPv4(iface *Interface) netaddr.IP {
n.mu.Lock() n.mu.Lock()
defer n.mu.Unlock() defer n.mu.Unlock()
if n.Prefix4.IsZero() { if n.Prefix4.IsZero() {
@ -107,11 +107,11 @@ func (n *Network) allocIPv4(m *Machine) netaddr.IP {
if !n.Prefix4.Contains(n.lastV4) { if !n.Prefix4.Contains(n.lastV4) {
panic("pool exhausted") panic("pool exhausted")
} }
n.addMachineLocked(n.lastV4, m) n.addMachineLocked(n.lastV4, iface)
return n.lastV4 return n.lastV4
} }
func (n *Network) allocIPv6(m *Machine) netaddr.IP { func (n *Network) allocIPv6(iface *Interface) netaddr.IP {
n.mu.Lock() n.mu.Lock()
defer n.mu.Unlock() defer n.mu.Unlock()
if n.Prefix6.IsZero() { if n.Prefix6.IsZero() {
@ -126,7 +126,7 @@ func (n *Network) allocIPv6(m *Machine) netaddr.IP {
if !n.Prefix6.Contains(n.lastV6) { if !n.Prefix6.Contains(n.lastV6) {
panic("pool exhausted") panic("pool exhausted")
} }
n.addMachineLocked(n.lastV6, m) n.addMachineLocked(n.lastV6, iface)
return n.lastV6 return n.lastV6
} }
@ -142,27 +142,28 @@ func addOne(a *[16]byte, index int) {
func (n *Network) write(p []byte, dst, src netaddr.IPPort) (num int, err error) { func (n *Network) write(p []byte, dst, src netaddr.IPPort) (num int, err error) {
n.mu.Lock() n.mu.Lock()
defer n.mu.Unlock() defer n.mu.Unlock()
m, ok := n.machine[dst.IP] iface, ok := n.machine[dst.IP]
if !ok { if !ok {
if n.defaultGW == nil { if n.defaultGW == nil {
trace(p, "net=%s dropped, no route to %v", n.Name, dst.IP) trace(p, "net=%s dropped, no route to %v", n.Name, dst.IP)
return len(p), nil return len(p), nil
} }
m = n.defaultGW iface = n.defaultGW
} }
// Pretend it went across the network. Make a copy so nobody // Pretend it went across the network. Make a copy so nobody
// can later mess with caller's memory. // can later mess with caller's memory.
trace(p, "net=%s src=%v dst=%v -> mach=%s", n.Name, src, dst, m.Name) trace(p, "net=%s src=%v dst=%v -> mach=%s iface=%s", n.Name, src, dst, iface.machine.Name, iface.name)
pcopy := append([]byte(nil), p...) pcopy := append([]byte(nil), p...)
go m.deliverIncomingPacket(pcopy, dst, src) go iface.machine.deliverIncomingPacket(pcopy, iface, dst, src)
return len(p), nil return len(p), nil
} }
type Interface struct { type Interface struct {
net *Network machine *Machine
name string // optional net *Network
ips []netaddr.IP // static; not mutated once created name string // optional
ips []netaddr.IP // static; not mutated once created
} }
// V4 returns the machine's first IPv4 address, or the zero value if none. // V4 returns the machine's first IPv4 address, or the zero value if none.
@ -226,7 +227,7 @@ func (v PacketVerdict) String() string {
} }
// A PacketHandler is a function that can process packets. // A PacketHandler is a function that can process packets.
type PacketHandler func(p []byte, dst, src netaddr.IPPort) PacketVerdict type PacketHandler func(p []byte, inIf *Interface, dst, src netaddr.IPPort) PacketVerdict
// A Machine is a representation of an operating system's network // A Machine is a representation of an operating system's network
// stack. It has a network routing table and can have multiple // stack. It has a network routing table and can have multiple
@ -261,11 +262,11 @@ func (m *Machine) Inject(p []byte, dst, src netaddr.IPPort) error {
return err return err
} }
func (m *Machine) deliverIncomingPacket(p []byte, dst, src netaddr.IPPort) { func (m *Machine) deliverIncomingPacket(p []byte, iface *Interface, dst, src netaddr.IPPort) {
// TODO: can't hold lock while handling packet. This is safe as // TODO: can't hold lock while handling packet. This is safe as
// long as you set HandlePacket before traffic starts flowing. // long as you set HandlePacket before traffic starts flowing.
if m.HandlePacket != nil { if m.HandlePacket != nil {
verdict := m.HandlePacket(p, dst, src) verdict := m.HandlePacket(p, iface, dst, src)
trace(p, "mach=%s src=%v dst=%v packethandler verdict=%s", m.Name, src, dst, verdict) trace(p, "mach=%s src=%v dst=%v packethandler verdict=%s", m.Name, src, dst, verdict)
if verdict == Drop { if verdict == Drop {
// Custom packet handler ate the packet, we're done. // Custom packet handler ate the packet, we're done.
@ -318,13 +319,14 @@ func unspecOf(ip netaddr.IP) netaddr.IP {
// default route. // default route.
func (m *Machine) Attach(interfaceName string, n *Network) *Interface { func (m *Machine) Attach(interfaceName string, n *Network) *Interface {
f := &Interface{ f := &Interface{
net: n, machine: m,
name: interfaceName, net: n,
name: interfaceName,
} }
if ip := n.allocIPv4(m); !ip.IsZero() { if ip := n.allocIPv4(f); !ip.IsZero() {
f.ips = append(f.ips, ip) f.ips = append(f.ips, ip)
} }
if ip := n.allocIPv6(m); !ip.IsZero() { if ip := n.allocIPv6(f); !ip.IsZero() {
f.ips = append(f.ips, ip) f.ips = append(f.ips, ip)
} }

View File

@ -16,7 +16,7 @@ func TestAllocIPs(t *testing.T) {
n := NewInternet() n := NewInternet()
saw := map[netaddr.IP]bool{} saw := map[netaddr.IP]bool{}
for i := 0; i < 255; i++ { for i := 0; i < 255; i++ {
for _, f := range []func(*Machine) netaddr.IP{n.allocIPv4, n.allocIPv6} { for _, f := range []func(*Interface) netaddr.IP{n.allocIPv4, n.allocIPv6} {
ip := f(nil) ip := f(nil)
if saw[ip] { if saw[ip] {
t.Fatalf("got duplicate %v", ip) t.Fatalf("got duplicate %v", ip)
@ -156,14 +156,15 @@ func TestPacketHandler(t *testing.T) {
client := &Machine{Name: "client"} client := &Machine{Name: "client"}
nat := &Machine{Name: "nat"} nat := &Machine{Name: "nat"}
lan.SetDefaultGateway(nat)
server := &Machine{Name: "server"} server := &Machine{Name: "server"}
ifClient := client.Attach("eth0", lan) ifClient := client.Attach("eth0", lan)
ifNATWAN := nat.Attach("wan", internet) ifNATWAN := nat.Attach("wan", internet)
_ = nat.Attach("lan", lan) ifNATLAN := nat.Attach("lan", lan)
ifServer := server.Attach("server", internet) ifServer := server.Attach("server", internet)
lan.SetDefaultGateway(ifNATLAN)
// This HandlePacket implements a basic (some might say "broken") // This HandlePacket implements a basic (some might say "broken")
// 1:1 NAT, where client's IP gets replaced with the NAT's WAN IP, // 1:1 NAT, where client's IP gets replaced with the NAT's WAN IP,
// and vice versa. // and vice versa.
@ -172,14 +173,14 @@ func TestPacketHandler(t *testing.T) {
// port remappings or any other things that NATs usually to. But // port remappings or any other things that NATs usually to. But
// it works as a demonstrator for a single client behind the NAT, // it works as a demonstrator for a single client behind the NAT,
// where the NAT box itself doesn't also make PacketConns. // where the NAT box itself doesn't also make PacketConns.
nat.HandlePacket = func(p []byte, dst, src netaddr.IPPort) PacketVerdict { nat.HandlePacket = func(p []byte, iface *Interface, dst, src netaddr.IPPort) PacketVerdict {
switch { switch {
case dst.IP.Is6(): case dst.IP.Is6():
return Continue // no NAT for ipv6 return Continue // no NAT for ipv6
case src.IP == ifClient.V4(): case iface == ifNATLAN && src.IP == ifClient.V4():
nat.Inject(p, dst, netaddr.IPPort{IP: ifNATWAN.V4(), Port: src.Port}) nat.Inject(p, dst, netaddr.IPPort{IP: ifNATWAN.V4(), Port: src.Port})
return Drop return Drop
case dst.IP == ifNATWAN.V4(): case iface == ifNATWAN && dst.IP == ifNATWAN.V4():
nat.Inject(p, netaddr.IPPort{IP: ifClient.V4(), Port: dst.Port}, src) nat.Inject(p, netaddr.IPPort{IP: ifClient.V4(), Port: dst.Port}, src)
return Drop return Drop
default: default: