mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-20 13:41:41 +00:00
tstest/natlab/vnet: add start of IPv6 support
Updates #13038 Change-Id: Ic3d095f167daf6c7129463e881b18f2e0d5693f5 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
31b5239a2f
commit
b78df4d48a
@ -28,6 +28,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"tailscale.com/atomicfile"
|
||||||
"tailscale.com/client/tailscale"
|
"tailscale.com/client/tailscale"
|
||||||
"tailscale.com/hostinfo"
|
"tailscale.com/hostinfo"
|
||||||
"tailscale.com/util/must"
|
"tailscale.com/util/must"
|
||||||
@ -85,6 +86,17 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if distro.Get() == distro.Gokrazy {
|
if distro.Get() == distro.Gokrazy {
|
||||||
|
cmdLine, _ := os.ReadFile("/proc/cmdline")
|
||||||
|
explicitNS := false
|
||||||
|
for _, s := range strings.Fields(string(cmdLine)) {
|
||||||
|
if ns, ok := strings.CutPrefix(s, "tta.nameserver="); ok {
|
||||||
|
err := atomicfile.WriteFile("/tmp/resolv.conf", []byte("nameserver "+ns+"\n"), 0644)
|
||||||
|
log.Printf("Wrote /tmp/resolv.conf: %v", err)
|
||||||
|
explicitNS = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !explicitNS {
|
||||||
nsRx := regexp.MustCompile(`(?m)^nameserver (.*)`)
|
nsRx := regexp.MustCompile(`(?m)^nameserver (.*)`)
|
||||||
for t := time.Now(); time.Since(t) < 10*time.Second; time.Sleep(10 * time.Millisecond) {
|
for t := time.Now(); time.Since(t) < 10*time.Second; time.Sleep(10 * time.Millisecond) {
|
||||||
all, _ := os.ReadFile("/etc/resolv.conf")
|
all, _ := os.ReadFile("/etc/resolv.conf")
|
||||||
@ -93,6 +105,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("Tailscale Test Agent running.")
|
log.Printf("Tailscale Test Agent running.")
|
||||||
|
|
||||||
|
@ -2,12 +2,18 @@
|
|||||||
|
|
||||||
echo "Type 'C-a c' to enter monitor; q to quit."
|
echo "Type 'C-a c' to enter monitor; q to quit."
|
||||||
|
|
||||||
|
# If the USE_V6 environment is set to 1, set the nameserver explicitly to.
|
||||||
|
EXTRA_ARG=""
|
||||||
|
if [ "$USE_V6" = "1" ]; then
|
||||||
|
EXTRA_ARG="tta.nameserver=2411::411"
|
||||||
|
fi
|
||||||
|
|
||||||
set -eux
|
set -eux
|
||||||
qemu-system-x86_64 -M microvm,isa-serial=off \
|
qemu-system-x86_64 -M microvm,isa-serial=off \
|
||||||
-m 1G \
|
-m 1G \
|
||||||
-nodefaults -no-user-config -nographic \
|
-nodefaults -no-user-config -nographic \
|
||||||
-kernel $HOME/src/github.com/tailscale/gokrazy-kernel/vmlinuz \
|
-kernel $HOME/src/github.com/tailscale/gokrazy-kernel/vmlinuz \
|
||||||
-append "console=hvc0 root=PARTUUID=60c24cc1-f3f9-427a-8199-76baa2d60001/PARTNROFF=1 ro init=/gokrazy/init panic=10 oops=panic pci=off nousb tsc=unstable clocksource=hpet tailscale-tta=1 tailscaled.env=TS_DEBUG_RAW_DISCO=1" \
|
-append "console=hvc0 root=PARTUUID=60c24cc1-f3f9-427a-8199-76baa2d60001/PARTNROFF=1 ro init=/gokrazy/init panic=10 oops=panic pci=off nousb tsc=unstable clocksource=hpet tailscale-tta=1 tailscaled.env=TS_DEBUG_RAW_DISCO=1 ${EXTRA_ARG}" \
|
||||||
-drive id=blk0,file=$HOME/src/tailscale.com/gokrazy/natlabapp.img,format=raw \
|
-drive id=blk0,file=$HOME/src/tailscale.com/gokrazy/natlabapp.img,format=raw \
|
||||||
-device virtio-blk-device,drive=blk0 \
|
-device virtio-blk-device,drive=blk0 \
|
||||||
-device virtio-rng-device \
|
-device virtio-rng-device \
|
||||||
|
@ -25,10 +25,12 @@ var (
|
|||||||
listen = flag.String("listen", "/tmp/qemu.sock", "path to listen on")
|
listen = flag.String("listen", "/tmp/qemu.sock", "path to listen on")
|
||||||
nat = flag.String("nat", "easy", "type of NAT to use")
|
nat = flag.String("nat", "easy", "type of NAT to use")
|
||||||
nat2 = flag.String("nat2", "hard", "type of NAT to use for second network")
|
nat2 = flag.String("nat2", "hard", "type of NAT to use for second network")
|
||||||
portmap = flag.Bool("portmap", false, "enable portmapping")
|
portmap = flag.Bool("portmap", false, "enable portmapping; requires --v4")
|
||||||
dgram = flag.Bool("dgram", false, "enable datagram mode; for use with macOS Hypervisor.Framework and VZFileHandleNetworkDeviceAttachment")
|
dgram = flag.Bool("dgram", false, "enable datagram mode; for use with macOS Hypervisor.Framework and VZFileHandleNetworkDeviceAttachment")
|
||||||
blend = flag.Bool("blend", true, "blend reality (controlplane.tailscale.com and DERPs) into the virtual network")
|
blend = flag.Bool("blend", true, "blend reality (controlplane.tailscale.com and DERPs) into the virtual network")
|
||||||
pcapFile = flag.String("pcap", "", "if non-empty, filename to write pcap")
|
pcapFile = flag.String("pcap", "", "if non-empty, filename to write pcap")
|
||||||
|
v4 = flag.Bool("v4", true, "enable IPv4")
|
||||||
|
v6 = flag.Bool("v6", true, "enable IPv6")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -61,9 +63,18 @@ func main() {
|
|||||||
var c vnet.Config
|
var c vnet.Config
|
||||||
c.SetPCAPFile(*pcapFile)
|
c.SetPCAPFile(*pcapFile)
|
||||||
c.SetBlendReality(*blend)
|
c.SetBlendReality(*blend)
|
||||||
node1 := c.AddNode(c.AddNetwork("2.1.1.1", "192.168.1.1/24", vnet.NAT(*nat)))
|
|
||||||
|
var net1opt = []any{vnet.NAT(*nat)}
|
||||||
|
if *v4 {
|
||||||
|
net1opt = append(net1opt, "2.1.1.1", "192.168.1.1/24")
|
||||||
|
}
|
||||||
|
if *v6 {
|
||||||
|
net1opt = append(net1opt, "2000:52::1/64")
|
||||||
|
}
|
||||||
|
|
||||||
|
node1 := c.AddNode(c.AddNetwork(net1opt...))
|
||||||
c.AddNode(c.AddNetwork("2.2.2.2", "10.2.0.1/16", vnet.NAT(*nat2)))
|
c.AddNode(c.AddNetwork("2.2.2.2", "10.2.0.1/16", vnet.NAT(*nat2)))
|
||||||
if *portmap {
|
if *portmap && *v4 {
|
||||||
node1.Network().AddService(vnet.NATPMP)
|
node1.Network().AddService(vnet.NATPMP)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,9 +83,11 @@ func main() {
|
|||||||
log.Fatalf("newServer: %v", err)
|
log.Fatalf("newServer: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *blend {
|
||||||
if err := s.PopulateDERPMapIPs(); err != nil {
|
if err := s.PopulateDERPMapIPs(); err != nil {
|
||||||
log.Printf("warning: ignoring failure to populate DERP map: %v", err)
|
log.Printf("warning: ignoring failure to populate DERP map: %v", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
s.WriteStartingBanner(os.Stdout)
|
s.WriteStartingBanner(os.Stdout)
|
||||||
nc := s.NodeAgentClient(node1)
|
nc := s.NodeAgentClient(node1)
|
||||||
|
@ -95,6 +95,10 @@ func findKernelPath(goMod string) (string, error) {
|
|||||||
|
|
||||||
type addNodeFunc func(c *vnet.Config) *vnet.Node // returns nil to omit test
|
type addNodeFunc func(c *vnet.Config) *vnet.Node // returns nil to omit test
|
||||||
|
|
||||||
|
func v6cidr(n int) string {
|
||||||
|
return fmt.Sprintf("2000:%d::1/64", n)
|
||||||
|
}
|
||||||
|
|
||||||
func easy(c *vnet.Config) *vnet.Node {
|
func easy(c *vnet.Config) *vnet.Node {
|
||||||
n := c.NumNodes() + 1
|
n := c.NumNodes() + 1
|
||||||
return c.AddNode(c.AddNetwork(
|
return c.AddNode(c.AddNetwork(
|
||||||
@ -102,6 +106,20 @@ func easy(c *vnet.Config) *vnet.Node {
|
|||||||
fmt.Sprintf("192.168.%d.1/24", n), vnet.EasyNAT))
|
fmt.Sprintf("192.168.%d.1/24", n), vnet.EasyNAT))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func easyAnd6(c *vnet.Config) *vnet.Node {
|
||||||
|
n := c.NumNodes() + 1
|
||||||
|
return c.AddNode(c.AddNetwork(
|
||||||
|
fmt.Sprintf("2.%d.%d.%d", n, n, n), // public IP
|
||||||
|
fmt.Sprintf("192.168.%d.1/24", n),
|
||||||
|
v6cidr(n),
|
||||||
|
vnet.EasyNAT))
|
||||||
|
}
|
||||||
|
|
||||||
|
func just6(c *vnet.Config) *vnet.Node {
|
||||||
|
n := c.NumNodes() + 1
|
||||||
|
return c.AddNode(c.AddNetwork(v6cidr(n))) // public IPv6 prefix
|
||||||
|
}
|
||||||
|
|
||||||
// easy + host firewall
|
// easy + host firewall
|
||||||
func easyFW(c *vnet.Config) *vnet.Node {
|
func easyFW(c *vnet.Config) *vnet.Node {
|
||||||
n := c.NumNodes() + 1
|
n := c.NumNodes() + 1
|
||||||
@ -255,6 +273,11 @@ func (nt *natTest) runTest(node1, node2 addNodeFunc) pingRoute {
|
|||||||
for _, e := range node.Env() {
|
for _, e := range node.Env() {
|
||||||
fmt.Fprintf(&envBuf, " tailscaled.env=%s=%s", e.Key, e.Value)
|
fmt.Fprintf(&envBuf, " tailscaled.env=%s=%s", e.Key, e.Value)
|
||||||
}
|
}
|
||||||
|
sysLogAddr := net.JoinHostPort(vnet.FakeSyslogIPv4().String(), "995")
|
||||||
|
if node.IsV6Only() {
|
||||||
|
fmt.Fprintf(&envBuf, " tta.nameserver=%s", vnet.FakeDNSIPv6())
|
||||||
|
sysLogAddr = net.JoinHostPort(vnet.FakeSyslogIPv6().String(), "995")
|
||||||
|
}
|
||||||
envStr := envBuf.String()
|
envStr := envBuf.String()
|
||||||
|
|
||||||
cmd := exec.Command("qemu-system-x86_64",
|
cmd := exec.Command("qemu-system-x86_64",
|
||||||
@ -262,7 +285,7 @@ func (nt *natTest) runTest(node1, node2 addNodeFunc) pingRoute {
|
|||||||
"-m", "384M",
|
"-m", "384M",
|
||||||
"-nodefaults", "-no-user-config", "-nographic",
|
"-nodefaults", "-no-user-config", "-nographic",
|
||||||
"-kernel", nt.kernel,
|
"-kernel", nt.kernel,
|
||||||
"-append", "console=hvc0 root=PARTUUID=60c24cc1-f3f9-427a-8199-76baa2d60001/PARTNROFF=1 ro init=/gokrazy/init panic=10 oops=panic pci=off nousb tsc=unstable clocksource=hpet gokrazy.remote_syslog.target=52.52.0.9:995 tailscale-tta=1"+envStr,
|
"-append", "console=hvc0 root=PARTUUID=60c24cc1-f3f9-427a-8199-76baa2d60001/PARTNROFF=1 ro init=/gokrazy/init panic=10 oops=panic pci=off nousb tsc=unstable clocksource=hpet gokrazy.remote_syslog.target="+sysLogAddr+" tailscale-tta=1"+envStr,
|
||||||
"-drive", "id=blk0,file="+disk+",format=qcow2",
|
"-drive", "id=blk0,file="+disk+",format=qcow2",
|
||||||
"-device", "virtio-blk-device,drive=blk0",
|
"-device", "virtio-blk-device,drive=blk0",
|
||||||
"-netdev", "stream,id=net0,addr.type=unix,addr.path="+sockAddr,
|
"-netdev", "stream,id=net0,addr.type=unix,addr.path="+sockAddr,
|
||||||
@ -445,6 +468,20 @@ func TestEasyEasy(t *testing.T) {
|
|||||||
nt.want(routeDirect)
|
nt.want(routeDirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJustIPv6(t *testing.T) {
|
||||||
|
t.Skip("TODO: get this working")
|
||||||
|
nt := newNatTest(t)
|
||||||
|
nt.runTest(just6, just6)
|
||||||
|
nt.want(routeDirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEasy4AndJust6(t *testing.T) {
|
||||||
|
t.Skip("TODO: get this working")
|
||||||
|
nt := newNatTest(t)
|
||||||
|
nt.runTest(easyAnd6, just6)
|
||||||
|
nt.want(routeDirect)
|
||||||
|
}
|
||||||
|
|
||||||
func TestSameLAN(t *testing.T) {
|
func TestSameLAN(t *testing.T) {
|
||||||
nt := newNatTest(t)
|
nt := newNatTest(t)
|
||||||
nt.runTest(easy, sameLAN)
|
nt.runTest(easy, sameLAN)
|
||||||
|
@ -132,6 +132,7 @@ type TailscaledEnv struct {
|
|||||||
func (c *Config) AddNetwork(opts ...any) *Network {
|
func (c *Config) AddNetwork(opts ...any) *Network {
|
||||||
num := len(c.networks)
|
num := len(c.networks)
|
||||||
n := &Network{
|
n := &Network{
|
||||||
|
num: num + 1,
|
||||||
mac: MAC{0x52, 0xee, 0xee, 0xee, 0xee, byte(num) + 1}, // 52=TS then 0xee for 'etwork
|
mac: MAC{0x52, 0xee, 0xee, 0xee, 0xee, byte(num) + 1}, // 52=TS then 0xee for 'etwork
|
||||||
}
|
}
|
||||||
c.networks = append(c.networks, n)
|
c.networks = append(c.networks, n)
|
||||||
@ -139,9 +140,15 @@ func (c *Config) AddNetwork(opts ...any) *Network {
|
|||||||
switch o := o.(type) {
|
switch o := o.(type) {
|
||||||
case string:
|
case string:
|
||||||
if ip, err := netip.ParseAddr(o); err == nil {
|
if ip, err := netip.ParseAddr(o); err == nil {
|
||||||
n.wanIP = ip
|
n.wanIP4 = ip
|
||||||
} else if ip, err := netip.ParsePrefix(o); err == nil {
|
} else if ip, err := netip.ParsePrefix(o); err == nil {
|
||||||
n.lanIP = ip
|
// If the prefix is IPv4, treat it as the router's internal IPv4 address + CIDR.
|
||||||
|
// If the prefix is IPv6, treat it as the router's WAN IPv6 + CIDR (typically a /64).
|
||||||
|
if ip.Addr().Is4() {
|
||||||
|
n.lanIP4 = ip
|
||||||
|
} else if ip.Addr().Is6() {
|
||||||
|
n.wanIP6 = ip
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if n.err == nil {
|
if n.err == nil {
|
||||||
n.err = fmt.Errorf("unknown string option %q", o)
|
n.err = fmt.Errorf("unknown string option %q", o)
|
||||||
@ -208,6 +215,21 @@ func (n *Node) SetVerboseSyslog(v bool) {
|
|||||||
n.verboseSyslog = v
|
n.verboseSyslog = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsV6Only reports whether this node is only connected to IPv6 networks.
|
||||||
|
func (n *Node) IsV6Only() bool {
|
||||||
|
for _, net := range n.nets {
|
||||||
|
if net.CanV4() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, net := range n.nets {
|
||||||
|
if net.CanV6() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Network returns the first network this node is connected to,
|
// Network returns the first network this node is connected to,
|
||||||
// or nil if none.
|
// or nil if none.
|
||||||
func (n *Node) Network() *Network {
|
func (n *Node) Network() *Network {
|
||||||
@ -219,11 +241,14 @@ func (n *Node) Network() *Network {
|
|||||||
|
|
||||||
// Network is the configuration of a network in the virtual network.
|
// Network is the configuration of a network in the virtual network.
|
||||||
type Network struct {
|
type Network struct {
|
||||||
|
num int // 1-based
|
||||||
mac MAC // MAC address of the router/gateway
|
mac MAC // MAC address of the router/gateway
|
||||||
natType NAT
|
natType NAT
|
||||||
|
|
||||||
wanIP netip.Addr
|
wanIP6 netip.Prefix // global unicast router in host bits; CIDR is /64 delegated to LAN
|
||||||
lanIP netip.Prefix
|
|
||||||
|
wanIP4 netip.Addr // IPv4 WAN IP, if any
|
||||||
|
lanIP4 netip.Prefix
|
||||||
nodes []*Node
|
nodes []*Node
|
||||||
|
|
||||||
svcs set.Set[NetworkService]
|
svcs set.Set[NetworkService]
|
||||||
@ -232,6 +257,14 @@ type Network struct {
|
|||||||
err error // carried error
|
err error // carried error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *Network) CanV4() bool {
|
||||||
|
return n.lanIP4.IsValid() || n.wanIP4.IsValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Network) CanV6() bool {
|
||||||
|
return n.wanIP6.IsValid()
|
||||||
|
}
|
||||||
|
|
||||||
func (n *Network) CanTakeMoreNodes() bool {
|
func (n *Network) CanTakeMoreNodes() bool {
|
||||||
if n.natType == One2OneNAT {
|
if n.natType == One2OneNAT {
|
||||||
return len(n.nodes) == 0
|
return len(n.nodes) == 0
|
||||||
@ -282,24 +315,43 @@ func (s *Server) initFromConfig(c *Config) error {
|
|||||||
if conf.err != nil {
|
if conf.err != nil {
|
||||||
return conf.err
|
return conf.err
|
||||||
}
|
}
|
||||||
if !conf.lanIP.IsValid() {
|
if !conf.lanIP4.IsValid() && !conf.wanIP6.IsValid() {
|
||||||
conf.lanIP = netip.MustParsePrefix("192.168.0.0/24")
|
conf.lanIP4 = netip.MustParsePrefix("192.168.0.0/24")
|
||||||
}
|
}
|
||||||
n := &network{
|
n := &network{
|
||||||
|
num: conf.num,
|
||||||
s: s,
|
s: s,
|
||||||
mac: conf.mac,
|
mac: conf.mac,
|
||||||
portmap: conf.svcs.Contains(NATPMP), // TODO: expand network.portmap
|
portmap: conf.svcs.Contains(NATPMP), // TODO: expand network.portmap
|
||||||
wanIP: conf.wanIP,
|
wanIP6: conf.wanIP6,
|
||||||
lanIP: conf.lanIP,
|
v4: conf.lanIP4.IsValid(),
|
||||||
|
v6: conf.wanIP6.IsValid(),
|
||||||
|
wanIP4: conf.wanIP4,
|
||||||
|
lanIP4: conf.lanIP4,
|
||||||
nodesByIP: map[netip.Addr]*node{},
|
nodesByIP: map[netip.Addr]*node{},
|
||||||
|
nodesByMAC: map[MAC]*node{},
|
||||||
logf: logger.WithPrefix(log.Printf, fmt.Sprintf("[net-%v] ", conf.mac)),
|
logf: logger.WithPrefix(log.Printf, fmt.Sprintf("[net-%v] ", conf.mac)),
|
||||||
}
|
}
|
||||||
netOfConf[conf] = n
|
netOfConf[conf] = n
|
||||||
s.networks.Add(n)
|
s.networks.Add(n)
|
||||||
if _, ok := s.networkByWAN[conf.wanIP]; ok {
|
if conf.wanIP4.IsValid() {
|
||||||
return fmt.Errorf("two networks have the same WAN IP %v; Anycast not (yet?) supported", conf.wanIP)
|
if conf.wanIP4.Is6() {
|
||||||
|
return fmt.Errorf("invalid IPv6 address in wanIP")
|
||||||
|
}
|
||||||
|
if _, ok := s.networkByWAN.Lookup(conf.wanIP4); ok {
|
||||||
|
return fmt.Errorf("two networks have the same WAN IP %v; Anycast not (yet?) supported", conf.wanIP4)
|
||||||
|
}
|
||||||
|
s.networkByWAN.Insert(netip.PrefixFrom(conf.wanIP4, 32), n)
|
||||||
|
}
|
||||||
|
if conf.wanIP6.IsValid() {
|
||||||
|
if conf.wanIP6.Addr().Is4() {
|
||||||
|
return fmt.Errorf("invalid IPv4 address in wanIP6")
|
||||||
|
}
|
||||||
|
if _, ok := s.networkByWAN.LookupPrefix(conf.wanIP6); ok {
|
||||||
|
return fmt.Errorf("two networks have the same WAN IPv6 %v; Anycast not (yet?) supported", conf.wanIP6)
|
||||||
|
}
|
||||||
|
s.networkByWAN.Insert(conf.wanIP6, n)
|
||||||
}
|
}
|
||||||
s.networkByWAN[conf.wanIP] = n
|
|
||||||
n.lanInterfaceID = must.Get(s.pcapWriter.AddInterface(pcapgo.NgInterface{
|
n.lanInterfaceID = must.Get(s.pcapWriter.AddInterface(pcapgo.NgInterface{
|
||||||
Name: fmt.Sprintf("network%d-lan", i+1),
|
Name: fmt.Sprintf("network%d-lan", i+1),
|
||||||
LinkType: layers.LinkTypeIPv4,
|
LinkType: layers.LinkTypeIPv4,
|
||||||
@ -330,14 +382,17 @@ func (s *Server) initFromConfig(c *Config) error {
|
|||||||
s.nodes = append(s.nodes, n)
|
s.nodes = append(s.nodes, n)
|
||||||
s.nodeByMAC[n.mac] = n
|
s.nodeByMAC[n.mac] = n
|
||||||
|
|
||||||
|
if n.net.v4 {
|
||||||
// Allocate a lanIP for the node. Use the network's CIDR and use final
|
// Allocate a lanIP for the node. Use the network's CIDR and use final
|
||||||
// octet 101 (for first node), 102, etc. The node number comes from the
|
// octet 101 (for first node), 102, etc. The node number comes from the
|
||||||
// last octent of the MAC address (0-based)
|
// last octent of the MAC address (0-based)
|
||||||
ip4 := n.net.lanIP.Addr().As4()
|
ip4 := n.net.lanIP4.Addr().As4()
|
||||||
ip4[3] = 100 + n.mac[5]
|
ip4[3] = 100 + n.mac[5]
|
||||||
n.lanIP = netip.AddrFrom4(ip4)
|
n.lanIP = netip.AddrFrom4(ip4)
|
||||||
n.net.nodesByIP[n.lanIP] = n
|
n.net.nodesByIP[n.lanIP] = n
|
||||||
}
|
}
|
||||||
|
n.net.nodesByMAC[n.mac] = n
|
||||||
|
}
|
||||||
|
|
||||||
// Now that nodes are populated, set up NAT:
|
// Now that nodes are populated, set up NAT:
|
||||||
for _, conf := range c.networks {
|
for _, conf := range c.networks {
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
var vips = map[string]virtualIP{} // DNS name => details
|
var vips = map[string]virtualIP{} // DNS name => details
|
||||||
|
|
||||||
var (
|
var (
|
||||||
fakeDNS = newVIP("dns", "4.11.4.11", "2000:4:11::4:11")
|
fakeDNS = newVIP("dns", "4.11.4.11", "2411::411")
|
||||||
fakeProxyControlplane = newVIP("controlplane.tailscale.com", 1)
|
fakeProxyControlplane = newVIP("controlplane.tailscale.com", 1)
|
||||||
fakeTestAgent = newVIP("test-driver.tailscale", 2)
|
fakeTestAgent = newVIP("test-driver.tailscale", 2)
|
||||||
fakeControl = newVIP("control.tailscale", 3)
|
fakeControl = newVIP("control.tailscale", 3)
|
||||||
@ -31,6 +31,18 @@ func (v virtualIP) Match(a netip.Addr) bool {
|
|||||||
return v.v4 == a.Unmap() || v.v6 == a
|
return v.v4 == a.Unmap() || v.v6 == a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FakeDNSIPv4 returns the fake DNS IPv4 address.
|
||||||
|
func FakeDNSIPv4() netip.Addr { return fakeDNS.v4 }
|
||||||
|
|
||||||
|
// FakeDNSIPv6 returns the fake DNS IPv6 address.
|
||||||
|
func FakeDNSIPv6() netip.Addr { return fakeDNS.v6 }
|
||||||
|
|
||||||
|
// FakeSyslogIPv4 returns the fake syslog IPv4 address.
|
||||||
|
func FakeSyslogIPv4() netip.Addr { return fakeSyslog.v4 }
|
||||||
|
|
||||||
|
// FakeSyslogIPv6 returns the fake syslog IPv6 address.
|
||||||
|
func FakeSyslogIPv6() netip.Addr { return fakeSyslog.v6 }
|
||||||
|
|
||||||
// newVIP returns a new virtual IP.
|
// newVIP returns a new virtual IP.
|
||||||
//
|
//
|
||||||
// opts may be an IPv4 an IPv6 (in string form) or an int (bounded by uint8) to
|
// opts may be an IPv4 an IPv6 (in string form) or an int (bounded by uint8) to
|
||||||
@ -67,8 +79,14 @@ func newVIP(name string, opts ...any) (v virtualIP) {
|
|||||||
}
|
}
|
||||||
if !v.v6.IsValid() && v.v4.IsValid() {
|
if !v.v6.IsValid() && v.v4.IsValid() {
|
||||||
// Map 1.2.3.4 to 2052::0102:0304
|
// Map 1.2.3.4 to 2052::0102:0304
|
||||||
a := [16]byte{0: 2, 2: 5, 3: 2} // 2052::
|
// But make 52.52.0.x map to 2052::x
|
||||||
|
a := [16]byte{0: 0x20, 1: 0x52} // 2052::
|
||||||
|
v4 := v.v4.As4()
|
||||||
|
if v4[0] == 52 && v4[1] == 52 && v4[2] == 0 {
|
||||||
|
a[15] = v4[3]
|
||||||
|
} else {
|
||||||
copy(a[12:], v.v4.AsSlice())
|
copy(a[12:], v.v4.AsSlice())
|
||||||
|
}
|
||||||
v.v6 = netip.AddrFrom16(a)
|
v.v6 = netip.AddrFrom16(a)
|
||||||
}
|
}
|
||||||
for _, b := range vips {
|
for _, b := range vips {
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gaissmai/bart"
|
||||||
"github.com/google/gopacket"
|
"github.com/google/gopacket"
|
||||||
"github.com/google/gopacket/layers"
|
"github.com/google/gopacket/layers"
|
||||||
"go4.org/mem"
|
"go4.org/mem"
|
||||||
@ -45,6 +46,7 @@ import (
|
|||||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/network/arp"
|
"gvisor.dev/gvisor/pkg/tcpip/network/arp"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||||
@ -99,7 +101,7 @@ func (n *network) InitNAT(natType NAT) error {
|
|||||||
}
|
}
|
||||||
t, err := ctor(n)
|
t, err := ctor(n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error creating NAT type %q for network %v: %w", natType, n.wanIP, err)
|
return fmt.Errorf("error creating NAT type %q for network %v: %w", natType, n.wanIP4, err)
|
||||||
}
|
}
|
||||||
n.setNATTable(t)
|
n.setNATTable(t)
|
||||||
n.natStyle.Store(natType)
|
n.natStyle.Store(natType)
|
||||||
@ -124,12 +126,13 @@ func (n *network) SoleLANIP() (netip.Addr, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WANIP implements [IPPool].
|
// WANIP implements [IPPool].
|
||||||
func (n *network) WANIP() netip.Addr { return n.wanIP }
|
func (n *network) WANIP() netip.Addr { return n.wanIP4 }
|
||||||
|
|
||||||
func (n *network) initStack() error {
|
func (n *network) initStack() error {
|
||||||
n.ns = stack.New(stack.Options{
|
n.ns = stack.New(stack.Options{
|
||||||
NetworkProtocols: []stack.NetworkProtocolFactory{
|
NetworkProtocols: []stack.NetworkProtocolFactory{
|
||||||
ipv4.NewProtocol,
|
ipv4.NewProtocol,
|
||||||
|
ipv6.NewProtocol,
|
||||||
arp.NewProtocol,
|
arp.NewProtocol,
|
||||||
},
|
},
|
||||||
TransportProtocols: []stack.TransportProtocolFactory{
|
TransportProtocols: []stack.TransportProtocolFactory{
|
||||||
@ -149,8 +152,11 @@ func (n *network) initStack() error {
|
|||||||
n.ns.SetPromiscuousMode(nicID, true)
|
n.ns.SetPromiscuousMode(nicID, true)
|
||||||
n.ns.SetSpoofing(nicID, true)
|
n.ns.SetSpoofing(nicID, true)
|
||||||
|
|
||||||
prefix := tcpip.AddrFrom4Slice(n.lanIP.Addr().AsSlice()).WithPrefix()
|
var routes []tcpip.Route
|
||||||
prefix.PrefixLen = n.lanIP.Bits()
|
|
||||||
|
if n.v4 {
|
||||||
|
prefix := tcpip.AddrFrom4Slice(n.lanIP4.Addr().AsSlice()).WithPrefix()
|
||||||
|
prefix.PrefixLen = n.lanIP4.Bits()
|
||||||
if tcpProb := n.ns.AddProtocolAddress(nicID, tcpip.ProtocolAddress{
|
if tcpProb := n.ns.AddProtocolAddress(nicID, tcpip.ProtocolAddress{
|
||||||
Protocol: ipv4.ProtocolNumber,
|
Protocol: ipv4.ProtocolNumber,
|
||||||
AddressWithPrefix: prefix,
|
AddressWithPrefix: prefix,
|
||||||
@ -162,12 +168,32 @@ func (n *network) initStack() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not create IPv4 subnet: %v", err)
|
return fmt.Errorf("could not create IPv4 subnet: %v", err)
|
||||||
}
|
}
|
||||||
n.ns.SetRouteTable([]tcpip.Route{
|
routes = append(routes, tcpip.Route{
|
||||||
{
|
|
||||||
Destination: ipv4Subnet,
|
Destination: ipv4Subnet,
|
||||||
NIC: nicID,
|
NIC: nicID,
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
if n.v6 {
|
||||||
|
prefix := tcpip.AddrFrom16(n.wanIP6.Addr().As16()).WithPrefix()
|
||||||
|
prefix.PrefixLen = n.wanIP6.Bits()
|
||||||
|
if tcpProb := n.ns.AddProtocolAddress(nicID, tcpip.ProtocolAddress{
|
||||||
|
Protocol: ipv6.ProtocolNumber,
|
||||||
|
AddressWithPrefix: prefix,
|
||||||
|
}, stack.AddressProperties{}); tcpProb != nil {
|
||||||
|
return errors.New(tcpProb.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
ipv6Subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice(make([]byte, 16)), tcpip.MaskFromBytes(make([]byte, 16)))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create IPv6 subnet: %v", err)
|
||||||
|
}
|
||||||
|
routes = append(routes, tcpip.Route{
|
||||||
|
Destination: ipv6Subnet,
|
||||||
|
NIC: nicID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
n.ns.SetRouteTable(routes)
|
||||||
|
|
||||||
const tcpReceiveBufferSize = 0 // default
|
const tcpReceiveBufferSize = 0 // default
|
||||||
const maxInFlightConnectionAttempts = 8192
|
const maxInFlightConnectionAttempts = 8192
|
||||||
@ -186,23 +212,43 @@ func (n *network) initStack() error {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
n.handleIPPacketFromGvisor(pkt.ToView().AsSlice())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
ipRaw := pkt.ToView().AsSlice()
|
func (n *network) handleIPPacketFromGvisor(ipRaw []byte) {
|
||||||
goPkt := gopacket.NewPacket(
|
if len(ipRaw) == 0 {
|
||||||
|
panic("empty packet from gvisor")
|
||||||
|
}
|
||||||
|
var goPkt gopacket.Packet
|
||||||
|
ipVer := ipRaw[0] >> 4 // 4 or 6
|
||||||
|
switch ipVer {
|
||||||
|
case 4:
|
||||||
|
goPkt = gopacket.NewPacket(
|
||||||
ipRaw,
|
ipRaw,
|
||||||
layers.LayerTypeIPv4, gopacket.Lazy)
|
layers.LayerTypeIPv4, gopacket.Lazy)
|
||||||
layerV4 := goPkt.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
|
case 6:
|
||||||
|
goPkt = gopacket.NewPacket(
|
||||||
dstIP, _ := netip.AddrFromSlice(layerV4.DstIP)
|
ipRaw,
|
||||||
node, ok := n.nodesByIP[dstIP]
|
layers.LayerTypeIPv6, gopacket.Lazy)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
flow, ok := flow(goPkt)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("no MAC for dest IP %v", dstIP)
|
panic("unexpected gvisor packet")
|
||||||
continue
|
}
|
||||||
|
node, ok := n.nodeForDestIP(flow.dst)
|
||||||
|
if !ok {
|
||||||
|
n.logf("no node for netstack dest IP %v", flow.dst)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
eth := &layers.Ethernet{
|
eth := &layers.Ethernet{
|
||||||
SrcMAC: n.mac.HWAddr(),
|
SrcMAC: n.mac.HWAddr(),
|
||||||
DstMAC: node.mac.HWAddr(),
|
DstMAC: node.mac.HWAddr(),
|
||||||
EthernetType: layers.EthernetTypeIPv4,
|
EthernetType: flow.etherType(),
|
||||||
}
|
}
|
||||||
buffer := gopacket.NewSerializeBuffer()
|
buffer := gopacket.NewSerializeBuffer()
|
||||||
options := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true}
|
options := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true}
|
||||||
@ -216,26 +262,23 @@ func (n *network) initStack() error {
|
|||||||
}
|
}
|
||||||
switch gl := layer.(type) {
|
switch gl := layer.(type) {
|
||||||
case *layers.TCP:
|
case *layers.TCP:
|
||||||
gl.SetNetworkLayerForChecksum(layerV4)
|
gl.SetNetworkLayerForChecksum(goPkt.NetworkLayer())
|
||||||
case *layers.UDP:
|
case *layers.UDP:
|
||||||
gl.SetNetworkLayerForChecksum(layerV4)
|
gl.SetNetworkLayerForChecksum(goPkt.NetworkLayer())
|
||||||
}
|
}
|
||||||
sls = append(sls, sl)
|
sls = append(sls, sl)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gopacket.SerializeLayers(buffer, options, sls...); err != nil {
|
if err := gopacket.SerializeLayers(buffer, options, sls...); err != nil {
|
||||||
log.Printf("Serialize error: %v", err)
|
n.logf("gvisor: serialize error: %v", err)
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
if nw, ok := n.writers.Load(node.mac); ok {
|
if nw, ok := n.writers.Load(node.mac); ok {
|
||||||
nw.write(buffer.Bytes())
|
nw.write(buffer.Bytes())
|
||||||
} else {
|
} else {
|
||||||
log.Printf("No writeFunc for %v", node.mac)
|
n.logf("gvisor write: no writeFunc for %v", node.mac)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func netaddrIPFromNetstackIP(s tcpip.Address) netip.Addr {
|
func netaddrIPFromNetstackIP(s tcpip.Address) netip.Addr {
|
||||||
switch s.Len() {
|
switch s.Len() {
|
||||||
@ -264,6 +307,8 @@ func (n *network) acceptTCP(r *tcp.ForwarderRequest) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("vnet-AcceptTCP: %v", stringifyTEI(reqDetails))
|
||||||
|
|
||||||
var wq waiter.Queue
|
var wq waiter.Queue
|
||||||
ep, err := r.CreateEndpoint(&wq)
|
ep, err := r.CreateEndpoint(&wq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -327,8 +372,6 @@ func (n *network) acceptTCP(r *tcp.ForwarderRequest) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("vnet-AcceptTCP: %v", stringifyTEI(reqDetails))
|
|
||||||
|
|
||||||
var targetDial string
|
var targetDial string
|
||||||
if n.s.derpIPs.Contains(destIP) {
|
if n.s.derpIPs.Contains(destIP) {
|
||||||
targetDial = destIP.String() + ":" + strconv.Itoa(int(destPort))
|
targetDial = destIP.String() + ":" + strconv.Itoa(int(destPort))
|
||||||
@ -419,6 +462,12 @@ func (m MAC) IsBroadcast() bool {
|
|||||||
return m == MAC{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
return m == MAC{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsIPv6Multicast reports whether m is an IPv6 multicast MAC address,
|
||||||
|
// typically one containing a solicited-node multicast address.
|
||||||
|
func (m MAC) IsIPv6Multicast() bool {
|
||||||
|
return m[0] == 0x33 && m[1] == 0x33
|
||||||
|
}
|
||||||
|
|
||||||
func macOf(hwa net.HardwareAddr) (_ MAC, ok bool) {
|
func macOf(hwa net.HardwareAddr) (_ MAC, ok bool) {
|
||||||
if len(hwa) != 6 {
|
if len(hwa) != 6 {
|
||||||
return MAC{}, false
|
return MAC{}, false
|
||||||
@ -455,13 +504,18 @@ func (nw *networkWriter) write(b []byte) {
|
|||||||
|
|
||||||
type network struct {
|
type network struct {
|
||||||
s *Server
|
s *Server
|
||||||
mac MAC
|
num int // 1-based
|
||||||
|
mac MAC // of router
|
||||||
portmap bool
|
portmap bool
|
||||||
lanInterfaceID int
|
lanInterfaceID int
|
||||||
wanInterfaceID int
|
wanInterfaceID int
|
||||||
wanIP netip.Addr
|
v4 bool // network supports IPv4
|
||||||
lanIP netip.Prefix // with host bits set (e.g. 192.168.2.1/24)
|
v6 bool // network support IPv6
|
||||||
nodesByIP map[netip.Addr]*node
|
wanIP6 netip.Prefix // router's WAN IPv6, if any, as a /64.
|
||||||
|
wanIP4 netip.Addr // router's LAN IPv4, if any
|
||||||
|
lanIP4 netip.Prefix // router's LAN IP + CIDR (e.g. 192.168.2.1/24)
|
||||||
|
nodesByIP map[netip.Addr]*node // by LAN IPv4
|
||||||
|
nodesByMAC map[MAC]*node
|
||||||
logf func(format string, args ...any)
|
logf func(format string, args ...any)
|
||||||
|
|
||||||
ns *stack.Stack
|
ns *stack.Stack
|
||||||
@ -473,6 +527,9 @@ type network struct {
|
|||||||
portMap map[netip.AddrPort]portMapping // WAN ip:port -> LAN ip:port
|
portMap map[netip.AddrPort]portMapping // WAN ip:port -> LAN ip:port
|
||||||
portMapFlow map[portmapFlowKey]netip.AddrPort // (lanAP, peerWANAP) -> portmapped wanAP
|
portMapFlow map[portmapFlowKey]netip.AddrPort // (lanAP, peerWANAP) -> portmapped wanAP
|
||||||
|
|
||||||
|
macMu sync.Mutex
|
||||||
|
macOfIPv6 map[netip.Addr]MAC // IPv6 source IP -> MAC
|
||||||
|
|
||||||
// writers is a map of MAC -> networkWriters to write packets to that MAC.
|
// writers is a map of MAC -> networkWriters to write packets to that MAC.
|
||||||
// It contains entries for connected nodes only.
|
// It contains entries for connected nodes only.
|
||||||
writers syncs.Map[MAC, networkWriter] // MAC -> to networkWriter for that MAC
|
writers syncs.Map[MAC, networkWriter] // MAC -> to networkWriter for that MAC
|
||||||
@ -494,7 +551,7 @@ func (n *network) registerWriter(mac MAC, raddr *net.UnixAddr, interfaceID int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *network) MACOfIP(ip netip.Addr) (_ MAC, ok bool) {
|
func (n *network) MACOfIP(ip netip.Addr) (_ MAC, ok bool) {
|
||||||
if n.lanIP.Addr() == ip {
|
if n.lanIP4.Addr() == ip {
|
||||||
return n.mac, true
|
return n.mac, true
|
||||||
}
|
}
|
||||||
if n, ok := n.nodesByIP[ip]; ok {
|
if n, ok := n.nodesByIP[ip]; ok {
|
||||||
@ -559,7 +616,7 @@ type Server struct {
|
|||||||
nodes []*node
|
nodes []*node
|
||||||
nodeByMAC map[MAC]*node
|
nodeByMAC map[MAC]*node
|
||||||
networks set.Set[*network]
|
networks set.Set[*network]
|
||||||
networkByWAN map[netip.Addr]*network
|
networkByWAN *bart.Table[*network]
|
||||||
|
|
||||||
control *testcontrol.Server
|
control *testcontrol.Server
|
||||||
derps []*derpServer
|
derps []*derpServer
|
||||||
@ -621,10 +678,11 @@ func New(c *Config) (*Server, error) {
|
|||||||
ExplicitBaseURL: "http://control.tailscale",
|
ExplicitBaseURL: "http://control.tailscale",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
blendReality: c.blendReality,
|
||||||
derpIPs: set.Of[netip.Addr](),
|
derpIPs: set.Of[netip.Addr](),
|
||||||
|
|
||||||
nodeByMAC: map[MAC]*node{},
|
nodeByMAC: map[MAC]*node{},
|
||||||
networkByWAN: map[netip.Addr]*network{},
|
networkByWAN: &bart.Table[*network]{},
|
||||||
networks: set.Of[*network](),
|
networks: set.Of[*network](),
|
||||||
}
|
}
|
||||||
for range 2 {
|
for range 2 {
|
||||||
@ -655,15 +713,6 @@ func (s *Server) HWAddr(mac MAC) net.HardwareAddr {
|
|||||||
return net.HardwareAddr(mac[:])
|
return net.HardwareAddr(mac[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPv4ForDNS returns the IP address for the given DNS query name (for IPv4 A
|
|
||||||
// queries only).
|
|
||||||
func (s *Server) IPv4ForDNS(qname string) (netip.Addr, bool) {
|
|
||||||
if v, ok := vips[qname]; ok {
|
|
||||||
return v.v4, v.v4.IsValid()
|
|
||||||
}
|
|
||||||
return netip.Addr{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
type Protocol int
|
type Protocol int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -767,6 +816,7 @@ func (s *Server) ServeUnixConn(uc *net.UnixConn, proto Protocol) {
|
|||||||
packet := gopacket.NewPacket(packetRaw, layers.LayerTypeEthernet, gopacket.Lazy)
|
packet := gopacket.NewPacket(packetRaw, layers.LayerTypeEthernet, gopacket.Lazy)
|
||||||
le, ok := packet.LinkLayer().(*layers.Ethernet)
|
le, ok := packet.LinkLayer().(*layers.Ethernet)
|
||||||
if !ok || len(le.SrcMAC) != 6 || len(le.DstMAC) != 6 {
|
if !ok || len(le.SrcMAC) != 6 || len(le.DstMAC) != 6 {
|
||||||
|
log.Printf("ignoring non-Ethernet packet: % 02x", packetRaw)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ep := EthernetPacket{le, packet}
|
ep := EthernetPacket{le, packet}
|
||||||
@ -815,7 +865,7 @@ func (s *Server) routeUDPPacket(up UDPPacket) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dstIP := up.Dst.Addr()
|
dstIP := up.Dst.Addr()
|
||||||
netw, ok := s.networkByWAN[dstIP]
|
netw, ok := s.networkByWAN.Lookup(dstIP)
|
||||||
if !ok {
|
if !ok {
|
||||||
if dstIP.IsPrivate() {
|
if dstIP.IsPrivate() {
|
||||||
// Not worth spamming logs. RFC 1918 space doesn't route.
|
// Not worth spamming logs. RFC 1918 space doesn't route.
|
||||||
@ -832,34 +882,59 @@ func (s *Server) routeUDPPacket(up UDPPacket) {
|
|||||||
//
|
//
|
||||||
// This only delivers to client devices and not the virtual router/gateway
|
// This only delivers to client devices and not the virtual router/gateway
|
||||||
// device.
|
// device.
|
||||||
func (n *network) writeEth(res []byte) {
|
//
|
||||||
|
// It reports whether a packet was written to any clients.
|
||||||
|
func (n *network) writeEth(res []byte) bool {
|
||||||
if len(res) < 12 {
|
if len(res) < 12 {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
dstMAC := MAC(res[0:6])
|
dstMAC := MAC(res[0:6])
|
||||||
srcMAC := MAC(res[6:12])
|
srcMAC := MAC(res[6:12])
|
||||||
if dstMAC.IsBroadcast() {
|
if dstMAC.IsBroadcast() {
|
||||||
|
num := 0
|
||||||
n.writers.Range(func(mac MAC, nw networkWriter) bool {
|
n.writers.Range(func(mac MAC, nw networkWriter) bool {
|
||||||
|
num++
|
||||||
nw.write(res)
|
nw.write(res)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return
|
return num > 0
|
||||||
}
|
}
|
||||||
if srcMAC == dstMAC {
|
if srcMAC == dstMAC {
|
||||||
n.logf("dropping write of packet from %v to itself", srcMAC)
|
n.logf("dropping write of packet from %v to itself", srcMAC)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
if nw, ok := n.writers.Load(dstMAC); ok {
|
if nw, ok := n.writers.Load(dstMAC); ok {
|
||||||
nw.write(res)
|
nw.write(res)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const debugMiss = false
|
||||||
|
if debugMiss {
|
||||||
|
gp := gopacket.NewPacket(res, layers.LayerTypeEthernet, gopacket.Lazy)
|
||||||
|
n.logf("no writeFunc for dst %v from src %v; pkt=%v", dstMAC, srcMAC, gp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
macAllRouters = MAC{0: 0x33, 1: 0x33, 5: 0x02}
|
||||||
|
)
|
||||||
|
|
||||||
func (n *network) HandleEthernetPacket(ep EthernetPacket) {
|
func (n *network) HandleEthernetPacket(ep EthernetPacket) {
|
||||||
packet := ep.gp
|
packet := ep.gp
|
||||||
dstMAC := ep.DstMAC()
|
dstMAC := ep.DstMAC()
|
||||||
isBroadcast := dstMAC.IsBroadcast()
|
isBroadcast := dstMAC.IsBroadcast()
|
||||||
forRouter := dstMAC == n.mac || isBroadcast
|
isV6SpecialMAC := dstMAC[0] == 0x33 && dstMAC[1] == 0x33
|
||||||
|
|
||||||
|
// forRouter is whether the packet is destined for the router itself
|
||||||
|
// or if it's a special thing (like V6 NDP) that the router should handle.
|
||||||
|
forRouter := dstMAC == n.mac || isBroadcast || isV6SpecialMAC
|
||||||
|
|
||||||
|
const debug = false
|
||||||
|
if debug {
|
||||||
|
n.logf("HandleEthernetPacket: %v => %v; type %v, forRouter=%v", ep.SrcMAC(), ep.DstMAC(), ep.le.EthernetType, forRouter)
|
||||||
|
}
|
||||||
|
|
||||||
switch ep.le.EthernetType {
|
switch ep.le.EthernetType {
|
||||||
default:
|
default:
|
||||||
@ -874,9 +949,38 @@ func (n *network) HandleEthernetPacket(ep EthernetPacket) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
case layers.EthernetTypeIPv6:
|
case layers.EthernetTypeIPv6:
|
||||||
// One day. Low value for now. IPv4 NAT modes is the main thing
|
if !n.v6 {
|
||||||
// this project wants to test.
|
n.logf("dropping IPv6 packet on v4-only network")
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
if dstMAC == macAllRouters {
|
||||||
|
if rs, ok := ep.gp.Layer(layers.LayerTypeICMPv6RouterSolicitation).(*layers.ICMPv6RouterSolicitation); ok {
|
||||||
|
n.handleIPv6RouterSolicitation(ep, rs)
|
||||||
|
} else {
|
||||||
|
n.logf("unexpected IPv6 packet to all-routers: %v", ep.gp)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isMcast := dstMAC.IsIPv6Multicast()
|
||||||
|
if isMcast || dstMAC == n.mac {
|
||||||
|
if ns, ok := ep.gp.Layer(layers.LayerTypeICMPv6NeighborSolicitation).(*layers.ICMPv6NeighborSolicitation); ok {
|
||||||
|
n.handleIPv6NeighborSolicitation(ep, ns)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ep.gp.Layer(layers.LayerTypeMLDv2MulticastListenerReport) != nil {
|
||||||
|
// We don't care about these (yet?) and Linux spams a bunch
|
||||||
|
// a bunch of them out, so explicitly ignore them to prevent
|
||||||
|
// log spam when verbose logging is enabled.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isMcast {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(bradfitz): handle packets to e.g. [fe80::50cc:ccff:fecc:cc01]:43619
|
||||||
|
// and don't fall through to the router below.
|
||||||
|
|
||||||
case layers.EthernetTypeIPv4:
|
case layers.EthernetTypeIPv4:
|
||||||
// Below
|
// Below
|
||||||
}
|
}
|
||||||
@ -884,10 +988,12 @@ func (n *network) HandleEthernetPacket(ep EthernetPacket) {
|
|||||||
// Send ethernet broadcasts and unicast ethernet frames to peers
|
// Send ethernet broadcasts and unicast ethernet frames to peers
|
||||||
// on the same network. This is all LAN traffic that isn't meant
|
// on the same network. This is all LAN traffic that isn't meant
|
||||||
// for the router/gw itself:
|
// for the router/gw itself:
|
||||||
|
if isBroadcast || !forRouter {
|
||||||
n.writeEth(ep.gp.Data())
|
n.writeEth(ep.gp.Data())
|
||||||
|
}
|
||||||
|
|
||||||
if forRouter {
|
if forRouter {
|
||||||
n.HandleEthernetIPv4PacketForRouter(ep)
|
n.HandleEthernetPacketForRouter(ep)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -927,6 +1033,25 @@ func (n *network) HandleUDPPacket(p UDPPacket) {
|
|||||||
n.WriteUDPPacketNoNAT(p)
|
n.WriteUDPPacketNoNAT(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *network) nodeForDestIP(ip netip.Addr) (node *node, ok bool) {
|
||||||
|
node, ok = n.nodesByIP[ip]
|
||||||
|
if !ok && ip.Is6() {
|
||||||
|
var mac MAC
|
||||||
|
n.macMu.Lock()
|
||||||
|
mac, ok = n.macOfIPv6[ip]
|
||||||
|
n.macMu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
log.Printf("XXX no MAC for IPv6 %v", ip)
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
node, ok = n.nodesByMAC[mac]
|
||||||
|
if !ok {
|
||||||
|
log.Printf("XXX no node for MAC %v", mac)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node, ok
|
||||||
|
}
|
||||||
|
|
||||||
// WriteUDPPacketNoNAT writes a UDP packet to the network, without
|
// WriteUDPPacketNoNAT writes a UDP packet to the network, without
|
||||||
// doing any NAT translation.
|
// doing any NAT translation.
|
||||||
//
|
//
|
||||||
@ -935,7 +1060,7 @@ func (n *network) HandleUDPPacket(p UDPPacket) {
|
|||||||
// same ethernet segment.
|
// same ethernet segment.
|
||||||
func (n *network) WriteUDPPacketNoNAT(p UDPPacket) {
|
func (n *network) WriteUDPPacketNoNAT(p UDPPacket) {
|
||||||
src, dst := p.Src, p.Dst
|
src, dst := p.Src, p.Dst
|
||||||
node, ok := n.nodesByIP[dst.Addr()]
|
node, ok := n.nodeForDestIP(dst.Addr())
|
||||||
if !ok {
|
if !ok {
|
||||||
n.logf("no node for dest IP %v in UDP packet %v=>%v", dst.Addr(), p.Src, p.Dst)
|
n.logf("no node for dest IP %v in UDP packet %v=>%v", dst.Addr(), p.Src, p.Dst)
|
||||||
return
|
return
|
||||||
@ -944,7 +1069,7 @@ func (n *network) WriteUDPPacketNoNAT(p UDPPacket) {
|
|||||||
eth := &layers.Ethernet{
|
eth := &layers.Ethernet{
|
||||||
SrcMAC: n.mac.HWAddr(), // of gateway
|
SrcMAC: n.mac.HWAddr(), // of gateway
|
||||||
DstMAC: node.mac.HWAddr(),
|
DstMAC: node.mac.HWAddr(),
|
||||||
EthernetType: layers.EthernetTypeIPv4,
|
EthernetType: p.etherType(),
|
||||||
}
|
}
|
||||||
ethRaw, err := n.serializedUDPPacket(src, dst, p.Payload, eth)
|
ethRaw, err := n.serializedUDPPacket(src, dst, p.Payload, eth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -954,19 +1079,40 @@ func (n *network) WriteUDPPacketNoNAT(p UDPPacket) {
|
|||||||
n.writeEth(ethRaw)
|
n.writeEth(ethRaw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type serializableNetworkLayer interface {
|
||||||
|
gopacket.SerializableLayer
|
||||||
|
gopacket.NetworkLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkIPLayer(proto layers.IPProtocol, src, dst netip.Addr) serializableNetworkLayer {
|
||||||
|
if src.Is4() {
|
||||||
|
return &layers.IPv4{
|
||||||
|
Version: 4,
|
||||||
|
TTL: 64,
|
||||||
|
Protocol: proto,
|
||||||
|
SrcIP: src.AsSlice(),
|
||||||
|
DstIP: dst.AsSlice(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if src.Is6() {
|
||||||
|
return &layers.IPv6{
|
||||||
|
Version: 6,
|
||||||
|
HopLimit: 64,
|
||||||
|
NextHeader: proto,
|
||||||
|
SrcIP: src.AsSlice(),
|
||||||
|
DstIP: dst.AsSlice(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("invalid src IP")
|
||||||
|
}
|
||||||
|
|
||||||
// serializedUDPPacket serializes a UDP packet with the given source and
|
// serializedUDPPacket serializes a UDP packet with the given source and
|
||||||
// destination IP:port pairs, and payload.
|
// destination IP:port pairs, and payload.
|
||||||
//
|
//
|
||||||
// If eth is non-nil, it will be used as the Ethernet layer, otherwise the
|
// If eth is non-nil, it will be used as the Ethernet layer, otherwise the
|
||||||
// Ethernet layer will be omitted from the serialization.
|
// Ethernet layer will be omitted from the serialization.
|
||||||
func (n *network) serializedUDPPacket(src, dst netip.AddrPort, payload []byte, eth *layers.Ethernet) ([]byte, error) {
|
func (n *network) serializedUDPPacket(src, dst netip.AddrPort, payload []byte, eth *layers.Ethernet) ([]byte, error) {
|
||||||
ip := &layers.IPv4{
|
ip := mkIPLayer(layers.IPProtocolUDP, src.Addr(), dst.Addr())
|
||||||
Version: 4,
|
|
||||||
TTL: 64,
|
|
||||||
Protocol: layers.IPProtocolUDP,
|
|
||||||
SrcIP: src.Addr().AsSlice(),
|
|
||||||
DstIP: dst.Addr().AsSlice(),
|
|
||||||
}
|
|
||||||
udp := &layers.UDP{
|
udp := &layers.UDP{
|
||||||
SrcPort: layers.UDPPort(src.Port()),
|
SrcPort: layers.UDPPort(src.Port()),
|
||||||
DstPort: layers.UDPPort(dst.Port()),
|
DstPort: layers.UDPPort(dst.Port()),
|
||||||
@ -980,27 +1126,34 @@ func (n *network) serializedUDPPacket(src, dst netip.AddrPort, payload []byte, e
|
|||||||
layers = layers[1:]
|
layers = layers[1:]
|
||||||
}
|
}
|
||||||
if err := gopacket.SerializeLayers(buffer, options, layers...); err != nil {
|
if err := gopacket.SerializeLayers(buffer, options, layers...); err != nil {
|
||||||
return nil, fmt.Errorf("serializing UDP: %v", err)
|
return nil, fmt.Errorf("serializing UDP from %v to %v: %v", src, dst, err)
|
||||||
}
|
}
|
||||||
return buffer.Bytes(), nil
|
return buffer.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleEthernetIPv4PacketForRouter handles an IPv4 packet that is
|
// HandleEthernetPacketForRouter handles an IPv4 packet that is
|
||||||
// directed to the router/gateway itself. The packet may be to the
|
// directed to the router/gateway itself. The packet may be to the
|
||||||
// broadcast MAC address, or to the router's MAC address. The target
|
// broadcast MAC address, or to the router's MAC address. The target
|
||||||
// IP may be the router's IP, or an internet (routed) IP.
|
// IP may be the router's IP, or an internet (routed) IP.
|
||||||
func (n *network) HandleEthernetIPv4PacketForRouter(ep EthernetPacket) {
|
func (n *network) HandleEthernetPacketForRouter(ep EthernetPacket) {
|
||||||
packet := ep.gp
|
packet := ep.gp
|
||||||
|
flow, ok := flow(packet)
|
||||||
v4, ok := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
|
n.logf("dropping non-IP packet: %v", packet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
srcIP, _ := netip.AddrFromSlice(v4.SrcIP)
|
srcIP, dstIP := flow.src, flow.dst
|
||||||
dstIP, _ := netip.AddrFromSlice(v4.DstIP)
|
|
||||||
toForward := dstIP != n.lanIP.Addr() && dstIP != netip.IPv4Unspecified()
|
toForward := dstIP != n.lanIP4.Addr() && dstIP != netip.IPv4Unspecified() && !dstIP.IsLinkLocalUnicast()
|
||||||
udp, isUDP := packet.Layer(layers.LayerTypeUDP).(*layers.UDP)
|
udp, isUDP := packet.Layer(layers.LayerTypeUDP).(*layers.UDP)
|
||||||
|
|
||||||
|
// Pre-NAT mapping, for DNS/etc responses:
|
||||||
|
if srcIP.Is6() {
|
||||||
|
n.macMu.Lock()
|
||||||
|
mak.Set(&n.macOfIPv6, srcIP, ep.SrcMAC())
|
||||||
|
n.macMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
if isDHCPRequest(packet) {
|
if isDHCPRequest(packet) {
|
||||||
res, err := n.s.createDHCPResponse(packet)
|
res, err := n.s.createDHCPResponse(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1017,8 +1170,6 @@ func (n *network) HandleEthernetIPv4PacketForRouter(ep EthernetPacket) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isDNSRequest(packet) {
|
if isDNSRequest(packet) {
|
||||||
// TODO(bradfitz): restrict this to 4.11.4.11? add DNS
|
|
||||||
// on gateway instead?
|
|
||||||
res, err := n.s.createDNSResponse(packet)
|
res, err := n.s.createDNSResponse(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.logf("createDNSResponse: %v", err)
|
n.logf("createDNSResponse: %v", err)
|
||||||
@ -1029,7 +1180,7 @@ func (n *network) HandleEthernetIPv4PacketForRouter(ep EthernetPacket) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isUDP && fakeSyslog.Match(dstIP) {
|
if isUDP && fakeSyslog.Match(dstIP) {
|
||||||
node, ok := n.nodesByIP[srcIP]
|
node, ok := n.nodeForDestIP(srcIP)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1083,6 +1234,12 @@ func (n *network) HandleEthernetIPv4PacketForRouter(ep EthernetPacket) {
|
|||||||
InterfaceIndex: n.wanInterfaceID,
|
InterfaceIndex: n.wanInterfaceID,
|
||||||
}, buf)
|
}, buf)
|
||||||
|
|
||||||
|
if src.Addr().Is6() {
|
||||||
|
n.macMu.Lock()
|
||||||
|
mak.Set(&n.macOfIPv6, src.Addr(), ep.SrcMAC())
|
||||||
|
n.macMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
n.s.routeUDPPacket(UDPPacket{
|
n.s.routeUDPPacket(UDPPacket{
|
||||||
Src: src,
|
Src: src,
|
||||||
Dst: dst,
|
Dst: dst,
|
||||||
@ -1092,14 +1249,23 @@ func (n *network) HandleEthernetIPv4PacketForRouter(ep EthernetPacket) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if toForward && n.s.shouldInterceptTCP(packet) {
|
if toForward && n.s.shouldInterceptTCP(packet) {
|
||||||
ipp := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
|
var base *layers.BaseLayer
|
||||||
pktCopy := make([]byte, 0, len(ipp.Contents)+len(ipp.Payload))
|
proto := header.IPv4ProtocolNumber
|
||||||
pktCopy = append(pktCopy, ipp.Contents...)
|
if v4, ok := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ok {
|
||||||
pktCopy = append(pktCopy, ipp.Payload...)
|
base = &v4.BaseLayer
|
||||||
|
} else if v6, ok := packet.Layer(layers.LayerTypeIPv6).(*layers.IPv6); ok {
|
||||||
|
base = &v6.BaseLayer
|
||||||
|
proto = header.IPv6ProtocolNumber
|
||||||
|
} else {
|
||||||
|
panic("not v4, not v6")
|
||||||
|
}
|
||||||
|
pktCopy := make([]byte, 0, len(base.Contents)+len(base.Payload))
|
||||||
|
pktCopy = append(pktCopy, base.Contents...)
|
||||||
|
pktCopy = append(pktCopy, base.Payload...)
|
||||||
packetBuf := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
packetBuf := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||||
Payload: buffer.MakeWithData(pktCopy),
|
Payload: buffer.MakeWithData(pktCopy),
|
||||||
})
|
})
|
||||||
n.linkEP.InjectInbound(header.IPv4ProtocolNumber, packetBuf)
|
n.linkEP.InjectInbound(proto, packetBuf)
|
||||||
packetBuf.DecRef()
|
packetBuf.DecRef()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1111,9 +1277,116 @@ func (n *network) HandleEthernetIPv4PacketForRouter(ep EthernetPacket) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//log.Printf("Got packet: %v", packet)
|
n.logf("router got unknown packet: %v", packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *network) handleIPv6RouterSolicitation(ep EthernetPacket, rs *layers.ICMPv6RouterSolicitation) {
|
||||||
|
v6 := ep.gp.Layer(layers.LayerTypeIPv6).(*layers.IPv6)
|
||||||
|
|
||||||
|
// Send a router advertisement back.
|
||||||
|
eth := &layers.Ethernet{
|
||||||
|
SrcMAC: n.mac.HWAddr(),
|
||||||
|
DstMAC: ep.SrcMAC().HWAddr(),
|
||||||
|
EthernetType: layers.EthernetTypeIPv6,
|
||||||
|
}
|
||||||
|
n.logf("sending IPv6 router advertisement to %v from %v", eth.SrcMAC, eth.DstMAC)
|
||||||
|
ip := &layers.IPv6{
|
||||||
|
Version: 6,
|
||||||
|
HopLimit: 255,
|
||||||
|
NextHeader: layers.IPProtocolICMPv6,
|
||||||
|
SrcIP: net.ParseIP("fe80::1"),
|
||||||
|
DstIP: v6.SrcIP,
|
||||||
|
}
|
||||||
|
icmp := &layers.ICMPv6{
|
||||||
|
TypeCode: layers.CreateICMPv6TypeCode(layers.ICMPv6TypeRouterAdvertisement, 0),
|
||||||
|
}
|
||||||
|
pfx := make([]byte, 0, 30) // it's 32 on the wire, once gopacket adds two byte header
|
||||||
|
pfx = append(pfx, byte(64)) // CIDR length
|
||||||
|
pfx = append(pfx, byte(0xc0)) // flags: On-Link, Autonomous
|
||||||
|
pfx = binary.BigEndian.AppendUint32(pfx, 86400) // valid lifetime
|
||||||
|
pfx = binary.BigEndian.AppendUint32(pfx, 14400) // preferred lifetime
|
||||||
|
pfx = binary.BigEndian.AppendUint32(pfx, 0) // reserved
|
||||||
|
wanIP := n.wanIP6.Addr().As16()
|
||||||
|
pfx = append(pfx, wanIP[:]...)
|
||||||
|
|
||||||
|
ra := &layers.ICMPv6RouterAdvertisement{
|
||||||
|
RouterLifetime: 1800,
|
||||||
|
Options: []layers.ICMPv6Option{
|
||||||
|
{
|
||||||
|
Type: layers.ICMPv6OptPrefixInfo,
|
||||||
|
Data: pfx,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
icmp.SetNetworkLayerForChecksum(ip)
|
||||||
|
buffer := gopacket.NewSerializeBuffer()
|
||||||
|
options := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true}
|
||||||
|
if err := gopacket.SerializeLayers(buffer, options, eth, ip, icmp, ra); err != nil {
|
||||||
|
n.logf("serializing ICMPv6 RA: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n.writeEth(buffer.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *network) handleIPv6NeighborSolicitation(ep EthernetPacket, ns *layers.ICMPv6NeighborSolicitation) {
|
||||||
|
v6 := ep.gp.Layer(layers.LayerTypeIPv6).(*layers.IPv6)
|
||||||
|
|
||||||
|
targetIP, ok := netip.AddrFromSlice(ns.TargetAddress)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var srcMAC MAC
|
||||||
|
if targetIP == netip.MustParseAddr("fe80::1") {
|
||||||
|
srcMAC = n.mac
|
||||||
|
} else {
|
||||||
|
n.logf("Ignoring IPv6 NS request from %v for target %v", ep.SrcMAC(), targetIP)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n.logf("replying to IPv6 NS %v->%v about target %v (replySrc=%v)", ep.SrcMAC(), ep.DstMAC(), targetIP, srcMAC)
|
||||||
|
|
||||||
|
// Send a neighbor advertisement back.
|
||||||
|
eth := &layers.Ethernet{
|
||||||
|
SrcMAC: srcMAC.HWAddr(),
|
||||||
|
DstMAC: ep.SrcMAC().HWAddr(),
|
||||||
|
EthernetType: layers.EthernetTypeIPv6,
|
||||||
|
}
|
||||||
|
ip := &layers.IPv6{
|
||||||
|
Version: 6,
|
||||||
|
HopLimit: 255,
|
||||||
|
NextHeader: layers.IPProtocolICMPv6,
|
||||||
|
SrcIP: ns.TargetAddress,
|
||||||
|
DstIP: v6.SrcIP,
|
||||||
|
}
|
||||||
|
icmp := &layers.ICMPv6{
|
||||||
|
TypeCode: layers.CreateICMPv6TypeCode(layers.ICMPv6TypeNeighborAdvertisement, 0),
|
||||||
|
}
|
||||||
|
var flags uint8 = 0x40 // solicited
|
||||||
|
if srcMAC == n.mac {
|
||||||
|
flags |= 0x80 // router
|
||||||
|
}
|
||||||
|
flags |= 0x20 // override
|
||||||
|
|
||||||
|
na := &layers.ICMPv6NeighborAdvertisement{
|
||||||
|
TargetAddress: ns.TargetAddress,
|
||||||
|
Flags: flags,
|
||||||
|
}
|
||||||
|
na.Options = append(na.Options, layers.ICMPv6Option{
|
||||||
|
Type: layers.ICMPv6OptTargetAddress,
|
||||||
|
Data: srcMAC.HWAddr(),
|
||||||
|
})
|
||||||
|
icmp.SetNetworkLayerForChecksum(ip)
|
||||||
|
buffer := gopacket.NewSerializeBuffer()
|
||||||
|
options := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true}
|
||||||
|
if err := gopacket.SerializeLayers(buffer, options, eth, ip, icmp, na); err != nil {
|
||||||
|
n.logf("serializing ICMPv6 RA: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !n.writeEth(buffer.Bytes()) {
|
||||||
|
n.logf("failed to writeEth for IPv6 NA reply for %v", targetIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createDHCPResponse creates a DHCPv4 response for the given DHCPv4 request.
|
||||||
func (s *Server) createDHCPResponse(request gopacket.Packet) ([]byte, error) {
|
func (s *Server) createDHCPResponse(request gopacket.Packet) ([]byte, error) {
|
||||||
ethLayer := request.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
|
ethLayer := request.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
|
||||||
srcMAC, ok := macOf(ethLayer.SrcMAC)
|
srcMAC, ok := macOf(ethLayer.SrcMAC)
|
||||||
@ -1125,7 +1398,7 @@ func (s *Server) createDHCPResponse(request gopacket.Packet) ([]byte, error) {
|
|||||||
log.Printf("DHCP request from unknown node %v; ignoring", srcMAC)
|
log.Printf("DHCP request from unknown node %v; ignoring", srcMAC)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
gwIP := node.net.lanIP.Addr()
|
gwIP := node.net.lanIP4.Addr()
|
||||||
|
|
||||||
ipLayer := request.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
|
ipLayer := request.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
|
||||||
udpLayer := request.Layer(layers.LayerTypeUDP).(*layers.UDP)
|
udpLayer := request.Layer(layers.LayerTypeUDP).(*layers.UDP)
|
||||||
@ -1185,7 +1458,7 @@ func (s *Server) createDHCPResponse(request gopacket.Packet) ([]byte, error) {
|
|||||||
},
|
},
|
||||||
layers.DHCPOption{
|
layers.DHCPOption{
|
||||||
Type: layers.DHCPOptSubnetMask,
|
Type: layers.DHCPOptSubnetMask,
|
||||||
Data: net.CIDRMask(node.net.lanIP.Bits(), 32),
|
Data: net.CIDRMask(node.net.lanIP4.Bits(), 32),
|
||||||
Length: 4,
|
Length: 4,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -1194,7 +1467,7 @@ func (s *Server) createDHCPResponse(request gopacket.Packet) ([]byte, error) {
|
|||||||
eth := &layers.Ethernet{
|
eth := &layers.Ethernet{
|
||||||
SrcMAC: node.net.mac.HWAddr(),
|
SrcMAC: node.net.mac.HWAddr(),
|
||||||
DstMAC: ethLayer.SrcMAC,
|
DstMAC: ethLayer.SrcMAC,
|
||||||
EthernetType: layers.EthernetTypeIPv4,
|
EthernetType: layers.EthernetTypeIPv4, // never IPv6 for DHCP
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := &layers.IPv4{
|
ip := &layers.IPv4{
|
||||||
@ -1249,48 +1522,80 @@ func (s *Server) shouldInterceptTCP(pkt gopacket.Packet) bool {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
ipv4, ok := pkt.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if tcp.DstPort == 123 {
|
if tcp.DstPort == 123 {
|
||||||
// Test port for TCP interception. Not really useful, but cute for
|
// Test port for TCP interception. Not really useful, but cute for
|
||||||
// demos.
|
// demos.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
dstIP, _ := netip.AddrFromSlice(ipv4.DstIP.To4())
|
flow, ok := flow(pkt)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if tcp.DstPort == 80 || tcp.DstPort == 443 {
|
if tcp.DstPort == 80 || tcp.DstPort == 443 {
|
||||||
for _, v := range []virtualIP{fakeControl, fakeDERP1, fakeDERP2, fakeLogCatcher} {
|
for _, v := range []virtualIP{fakeControl, fakeDERP1, fakeDERP2, fakeLogCatcher} {
|
||||||
if v.Match(dstIP) {
|
if v.Match(flow.dst) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fakeProxyControlplane.Match(dstIP) {
|
if fakeProxyControlplane.Match(flow.dst) {
|
||||||
return s.blendReality
|
return s.blendReality
|
||||||
}
|
}
|
||||||
if s.derpIPs.Contains(dstIP) {
|
if s.derpIPs.Contains(flow.dst) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tcp.DstPort == 8008 && fakeTestAgent.Match(dstIP) {
|
if tcp.DstPort == 8008 && fakeTestAgent.Match(flow.dst) {
|
||||||
// Connection from cmd/tta.
|
// Connection from cmd/tta.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ipSrcDst struct {
|
||||||
|
src netip.Addr
|
||||||
|
dst netip.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f ipSrcDst) etherType() layers.EthernetType {
|
||||||
|
if f.dst.Is6() {
|
||||||
|
return layers.EthernetTypeIPv6
|
||||||
|
}
|
||||||
|
return layers.EthernetTypeIPv4
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p UDPPacket) etherType() layers.EthernetType {
|
||||||
|
if p.Dst.Addr().Is6() {
|
||||||
|
return layers.EthernetTypeIPv6
|
||||||
|
}
|
||||||
|
return layers.EthernetTypeIPv4
|
||||||
|
}
|
||||||
|
|
||||||
|
func flow(gp gopacket.Packet) (f ipSrcDst, ok bool) {
|
||||||
|
if gp == nil {
|
||||||
|
return f, false
|
||||||
|
}
|
||||||
|
n := gp.NetworkLayer()
|
||||||
|
if n == nil {
|
||||||
|
return f, false
|
||||||
|
}
|
||||||
|
sb, db := n.NetworkFlow().Endpoints()
|
||||||
|
src, _ := netip.AddrFromSlice(sb.Raw())
|
||||||
|
dst, _ := netip.AddrFromSlice(db.Raw())
|
||||||
|
return ipSrcDst{src: src, dst: dst}, src.IsValid() && dst.IsValid()
|
||||||
|
}
|
||||||
|
|
||||||
// isDNSRequest reports whether pkt is a DNS request to the fake DNS server.
|
// isDNSRequest reports whether pkt is a DNS request to the fake DNS server.
|
||||||
func isDNSRequest(pkt gopacket.Packet) bool {
|
func isDNSRequest(pkt gopacket.Packet) bool {
|
||||||
udp, ok := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP)
|
udp, ok := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP)
|
||||||
if !ok || udp.DstPort != 53 {
|
if !ok || udp.DstPort != 53 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
ip, ok := pkt.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
|
f, ok := flow(pkt)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
dstIP, ok := netip.AddrFromSlice(ip.DstIP)
|
if !fakeDNS.Match(f.dst) {
|
||||||
if !ok || !fakeDNS.Match(dstIP) {
|
// TODO(bradfitz): maybe support configs where DNS is local in the LAN
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
dns, ok := pkt.Layer(layers.LayerTypeDNS).(*layers.DNS)
|
dns, ok := pkt.Layer(layers.LayerTypeDNS).(*layers.DNS)
|
||||||
@ -1316,8 +1621,11 @@ func makeSTUNReply(req UDPPacket) (res UDPPacket, ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) createDNSResponse(pkt gopacket.Packet) ([]byte, error) {
|
func (s *Server) createDNSResponse(pkt gopacket.Packet) ([]byte, error) {
|
||||||
|
flow, ok := flow(pkt)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
ethLayer := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
|
ethLayer := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
|
||||||
ipLayer := pkt.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
|
|
||||||
udpLayer := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP)
|
udpLayer := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP)
|
||||||
dnsLayer := pkt.Layer(layers.LayerTypeDNS).(*layers.DNS)
|
dnsLayer := pkt.Layer(layers.LayerTypeDNS).(*layers.DNS)
|
||||||
|
|
||||||
@ -1349,11 +1657,16 @@ func (s *Server) createDNSResponse(pkt gopacket.Packet) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
names = append(names, q.Type.String()+"/"+string(q.Name))
|
names = append(names, q.Type.String()+"/"+string(q.Name))
|
||||||
if q.Class != layers.DNSClassIN || q.Type != layers.DNSTypeA {
|
if q.Class != layers.DNSClassIN {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip, ok := s.IPv4ForDNS(string(q.Name)); ok {
|
if q.Type == layers.DNSTypeA || q.Type == layers.DNSTypeAAAA {
|
||||||
|
if v, ok := vips[string(q.Name)]; ok {
|
||||||
|
ip := v.v4
|
||||||
|
if q.Type == layers.DNSTypeAAAA {
|
||||||
|
ip = v.v6
|
||||||
|
}
|
||||||
response.ANCount++
|
response.ANCount++
|
||||||
response.Answers = append(response.Answers, layers.DNSResourceRecord{
|
response.Answers = append(response.Answers, layers.DNSResourceRecord{
|
||||||
Name: q.Name,
|
Name: q.Name,
|
||||||
@ -1364,19 +1677,15 @@ func (s *Server) createDNSResponse(pkt gopacket.Packet) ([]byte, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make reply layers, all reversed.
|
||||||
eth2 := &layers.Ethernet{
|
eth2 := &layers.Ethernet{
|
||||||
SrcMAC: ethLayer.DstMAC,
|
SrcMAC: ethLayer.DstMAC,
|
||||||
DstMAC: ethLayer.SrcMAC,
|
DstMAC: ethLayer.SrcMAC,
|
||||||
EthernetType: layers.EthernetTypeIPv4,
|
EthernetType: flow.etherType(),
|
||||||
}
|
|
||||||
ip2 := &layers.IPv4{
|
|
||||||
Version: 4,
|
|
||||||
TTL: 64,
|
|
||||||
Protocol: layers.IPProtocolUDP,
|
|
||||||
SrcIP: ipLayer.DstIP,
|
|
||||||
DstIP: ipLayer.SrcIP,
|
|
||||||
}
|
}
|
||||||
|
ip2 := mkIPLayer(layers.IPProtocolUDP, flow.dst, flow.src)
|
||||||
udp2 := &layers.UDP{
|
udp2 := &layers.UDP{
|
||||||
SrcPort: udpLayer.DstPort,
|
SrcPort: udpLayer.DstPort,
|
||||||
DstPort: udpLayer.SrcPort,
|
DstPort: udpLayer.SrcPort,
|
||||||
@ -1393,7 +1702,7 @@ func (s *Server) createDNSResponse(pkt gopacket.Packet) ([]byte, error) {
|
|||||||
if debugDNS {
|
if debugDNS {
|
||||||
if len(response.Answers) > 0 {
|
if len(response.Answers) > 0 {
|
||||||
back := gopacket.NewPacket(buffer.Bytes(), layers.LayerTypeEthernet, gopacket.Lazy)
|
back := gopacket.NewPacket(buffer.Bytes(), layers.LayerTypeEthernet, gopacket.Lazy)
|
||||||
log.Printf("Generated: %v", back)
|
log.Printf("createDNSResponse generated answers: %v", back)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("made empty response for %q", names)
|
log.Printf("made empty response for %q", names)
|
||||||
}
|
}
|
||||||
@ -1409,6 +1718,11 @@ func (s *Server) createDNSResponse(pkt gopacket.Packet) ([]byte, error) {
|
|||||||
//
|
//
|
||||||
// If newSrc is invalid, the packet should be dropped.
|
// If newSrc is invalid, the packet should be dropped.
|
||||||
func (n *network) doNATOut(src, dst netip.AddrPort) (newSrc netip.AddrPort) {
|
func (n *network) doNATOut(src, dst netip.AddrPort) (newSrc netip.AddrPort) {
|
||||||
|
if src.Addr().Is6() {
|
||||||
|
// TODO(bradfitz): IPv6 NAT? For now, normal IPv6 only.
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
n.natMu.Lock()
|
n.natMu.Lock()
|
||||||
defer n.natMu.Unlock()
|
defer n.natMu.Unlock()
|
||||||
|
|
||||||
@ -1433,6 +1747,11 @@ type portmapFlowKey struct {
|
|||||||
//
|
//
|
||||||
// If newDst is invalid, the packet should be dropped.
|
// If newDst is invalid, the packet should be dropped.
|
||||||
func (n *network) doNATIn(src, dst netip.AddrPort) (newDst netip.AddrPort) {
|
func (n *network) doNATIn(src, dst netip.AddrPort) (newDst netip.AddrPort) {
|
||||||
|
if dst.Addr().Is6() {
|
||||||
|
// TODO(bradfitz): IPv6 NAT? For now, normal IPv6 only.
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
n.natMu.Lock()
|
n.natMu.Lock()
|
||||||
defer n.natMu.Unlock()
|
defer n.natMu.Unlock()
|
||||||
|
|
||||||
@ -1473,7 +1792,7 @@ func (n *network) doPortMap(src netip.Addr, dstLANPort, wantExtPort uint16, sec
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
wanAP := netip.AddrPortFrom(n.wanIP, wantExtPort)
|
wanAP := netip.AddrPortFrom(n.wanIP4, wantExtPort)
|
||||||
dst := netip.AddrPortFrom(src, dstLANPort)
|
dst := netip.AddrPortFrom(src, dstLANPort)
|
||||||
|
|
||||||
if sec == 0 {
|
if sec == 0 {
|
||||||
@ -1505,7 +1824,7 @@ func (n *network) doPortMap(src netip.Addr, dstLANPort, wantExtPort uint16, sec
|
|||||||
return wanAP.Port(), true
|
return wanAP.Port(), true
|
||||||
}
|
}
|
||||||
wantExtPort = rand.N(uint16(32<<10)) + 32<<10
|
wantExtPort = rand.N(uint16(32<<10)) + 32<<10
|
||||||
wanAP = netip.AddrPortFrom(n.wanIP, wantExtPort)
|
wanAP = netip.AddrPortFrom(n.wanIP4, wantExtPort)
|
||||||
}
|
}
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
@ -1540,7 +1859,7 @@ func (n *network) createARPResponse(pkt gopacket.Packet) ([]byte, error) {
|
|||||||
|
|
||||||
a2 := &layers.ARP{
|
a2 := &layers.ARP{
|
||||||
AddrType: layers.LinkTypeEthernet,
|
AddrType: layers.LinkTypeEthernet,
|
||||||
Protocol: layers.EthernetTypeIPv4,
|
Protocol: layers.EthernetTypeIPv4, // never IPv6; IPv6 equivalent of ARP is handleIPv6NeighborSolicitation
|
||||||
HwAddressSize: 6,
|
HwAddressSize: 6,
|
||||||
ProtAddressSize: 4,
|
ProtAddressSize: 4,
|
||||||
Operation: layers.ARPReply,
|
Operation: layers.ARPReply,
|
||||||
@ -1573,7 +1892,7 @@ func (n *network) handleNATPMPRequest(req UDPPacket) {
|
|||||||
0, 0, // result code success
|
0, 0, // result code success
|
||||||
)
|
)
|
||||||
res = binary.BigEndian.AppendUint32(res, uint32(time.Now().Unix()))
|
res = binary.BigEndian.AppendUint32(res, uint32(time.Now().Unix()))
|
||||||
wan4 := n.wanIP.As4()
|
wan4 := n.wanIP4.As4()
|
||||||
res = append(res, wan4[:]...)
|
res = append(res, wan4[:]...)
|
||||||
n.WriteUDPPacketNoNAT(UDPPacket{
|
n.WriteUDPPacketNoNAT(UDPPacket{
|
||||||
Src: req.Dst,
|
Src: req.Dst,
|
||||||
@ -1637,7 +1956,7 @@ func (s *Server) WriteStartingBanner(w io.Writer) {
|
|||||||
fmt.Fprintf(w, "vnet serving clients:\n")
|
fmt.Fprintf(w, "vnet serving clients:\n")
|
||||||
|
|
||||||
for _, n := range s.nodes {
|
for _, n := range s.nodes {
|
||||||
fmt.Fprintf(w, " %v %15v (%v, %v)\n", n.mac, n.lanIP, n.net.wanIP, n.net.natStyle.Load())
|
fmt.Fprintf(w, " %v %15v (%v, %v)\n", n.mac, n.lanIP, n.net.wanIP4, n.net.natStyle.Load())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user