tstest/natlab: network address allocation

Pairing with @danderson
This commit is contained in:
Brad Fitzpatrick 2020-07-02 13:39:41 -07:00
parent bb1a9e4700
commit a477e70632
3 changed files with 121 additions and 14 deletions

2
go.mod
View File

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

2
go.sum
View File

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

View File

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