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