diff --git a/go.mod b/go.mod index 0254776af..cc95531aa 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,6 @@ require ( golang.org/x/sys v0.0.0-20200501052902-10377860bb8e golang.org/x/time v0.0.0-20191024005414-555d28b269f0 honnef.co/go/tools v0.0.1-2020.1.4 // indirect - inet.af/netaddr v0.0.0-20200701194149-10bc159763c4 + inet.af/netaddr v0.0.0-20200702150737-4591d218f82c rsc.io/goversion v1.2.0 ) diff --git a/go.sum b/go.sum index f19bc30af..48184b89b 100644 --- a/go.sum +++ b/go.sum @@ -166,5 +166,7 @@ inet.af/netaddr v0.0.0-20200701171350-6509743f79d9 h1:F41nQsn8UGDPDXsOPwZQiaK8Bm inet.af/netaddr v0.0.0-20200701171350-6509743f79d9/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww= inet.af/netaddr v0.0.0-20200701194149-10bc159763c4 h1:dSXrLpRy86h4wfZWvzjyA2tuhMzX/lx0st4hzAh1VMA= inet.af/netaddr v0.0.0-20200701194149-10bc159763c4/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww= +inet.af/netaddr v0.0.0-20200702150737-4591d218f82c h1:j3Z4HL4KcLBDU1kmRpXTD5fikKBqIkE+7vFKS5mCz3Y= +inet.af/netaddr v0.0.0-20200702150737-4591d218f82c/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww= rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w= rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo= diff --git a/tstest/natlab/natlab.go b/tstest/natlab/natlab.go index 5a090ff66..4f79e0e75 100644 --- a/tstest/natlab/natlab.go +++ b/tstest/natlab/natlab.go @@ -29,25 +29,100 @@ type PacketConner interface { PacketConn() net.PacketConn } +func mustPrefix(s string) netaddr.IPPrefix { + ipp, err := netaddr.ParseIPPrefix(s) + if err != nil { + panic(err) + } + return ipp +} + +// NewInternet returns a network that simulates the internet. +func NewInternet() *Network { + return &Network{ + v4Pool: mustPrefix("203.0.113.0/24"), // documentation netblock that looks Internet-y + v6Pool: mustPrefix("fc00:52::/64"), + } +} + type Network struct { - name string - dhcpPool netaddr.IPPrefix - alloced map[netaddr.IP]bool + name string + v4Pool netaddr.IPPrefix + v6Pool netaddr.IPPrefix pushRoute netaddr.IPPrefix + + mu sync.Mutex + machine map[netaddr.IP]*Machine + lastV4 netaddr.IP + lastV6 netaddr.IP +} + +func (n *Network) AllocIPv4() netaddr.IP { + n.mu.Lock() + defer n.mu.Unlock() + if n.lastV4.IsZero() { + n.lastV4 = n.v4Pool.IP + } + a := n.lastV4.As16() + addOne(&a, 15) + n.lastV4 = netaddr.IPFrom16(a) + if !n.v4Pool.Contains(n.lastV4) { + panic("pool exhausted") + } + return n.lastV4 +} + +func (n *Network) AllocIPv6() netaddr.IP { + n.mu.Lock() + defer n.mu.Unlock() + if n.lastV6.IsZero() { + n.lastV6 = n.v6Pool.IP + } + a := n.lastV6.As16() + addOne(&a, 15) + n.lastV6 = netaddr.IPFrom16(a) + if !n.v6Pool.Contains(n.lastV6) { + panic("pool exhausted") + } + return n.lastV6 +} + +func addOne(a *[16]byte, index int) { + if v := a[index]; v < 255 { + a[index]++ + } else { + a[index] = 0 + addOne(a, index-1) + } } func (n *Network) write(p []byte, dst, src netaddr.IPPort) (num int, err error) { panic("TODO") } -type iface struct { +type Interface struct { net *Network name string // optional ips []netaddr.IP // static; not mutated once created } -func (f *iface) String() string { +// V4 returns the machine's first IPv4 address, or the zero value if none. +func (f *Interface) V4() netaddr.IP { return f.pickIP(netaddr.IP.Is4) } + +// V6 returns the machine's first IPv6 address, or the zero value if none. +func (f *Interface) V6() netaddr.IP { return f.pickIP(netaddr.IP.Is6) } + +func (f *Interface) pickIP(pred func(netaddr.IP) bool) netaddr.IP { + for _, ip := range f.ips { + if pred(ip) { + return ip + } + } + return netaddr.IP{} +} + +func (f *Interface) String() string { // TODO: make this all better if f.name != "" { return f.name @@ -56,7 +131,7 @@ func (f *iface) String() string { } // Contains reports whether f contains ip as an IP. -func (f *iface) Contains(ip netaddr.IP) bool { +func (f *Interface) Contains(ip netaddr.IP) bool { for _, v := range f.ips { if ip == v { return true @@ -67,19 +142,43 @@ func (f *iface) Contains(ip netaddr.IP) bool { type routeEntry struct { prefix netaddr.IPPrefix - iface *iface + iface *Interface +} + +// NewMachine returns a new Machine without any network connection. +// The name is just for debugging and need not be globally unique. +// Use Attach to add networks. +func NewMachine(name string) *Machine { + return &Machine{name: name} } // A Machine is a representation of an operating system's network stack. // It has a network routing table and can have multiple attached networks. type Machine struct { + name string + mu sync.Mutex - interfaces []*iface + interfaces []*Interface routes []routeEntry // sorted by longest prefix to shortest conns map[netaddr.IPPort]*conn } +// Attach +func (m *Machine) Attach(interfaceName string, n *Network) *Interface { + f := &Interface{ + net: n, + name: interfaceName, + } + // TODO: get f.ips, routes + + m.mu.Lock() + defer m.mu.Unlock() + m.interfaces = append(m.interfaces, f) + + return f +} + var ( v4unspec = netaddr.IPv4(0, 0, 0, 0) v6unspec = netaddr.IPv6Unspecified() @@ -90,19 +189,25 @@ func (m *Machine) writePacket(p []byte, dst, src netaddr.IPPort) (n int, err err if err != nil { return 0, err } - unspec := src.IP == v4unspec || src.IP == v6unspec - if unspec { - // TODO: pick a src IP - } else { + origSrcIP := src.IP + switch { + case src.IP == v4unspec: + src.IP = iface.V4() + case src.IP == v6unspec: + src.IP = iface.V6() + default: if !iface.Contains(src.IP) { return 0, fmt.Errorf("can't send to %v with src %v on interface %v", dst.IP, src.IP, iface) } } + if src.IP.IsZero() { + return 0, fmt.Errorf("no matching address for address family for %v", origSrcIP) + } return iface.net.write(p, dst, src) } -func (m *Machine) interfaceForIP(ip netaddr.IP) (*iface, error) { +func (m *Machine) interfaceForIP(ip netaddr.IP) (*Interface, error) { m.mu.Lock() defer m.mu.Unlock() for _, re := range m.routes {