// Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause // Package vnet simulates a virtual Internet containing a set of networks with various // NAT behaviors. You can then plug VMs into the virtual internet at different points // to test Tailscale working end-to-end in various network conditions. // // See https://github.com/tailscale/tailscale/issues/13038 package vnet // TODO: // - [ ] tests for NAT tables import ( "bytes" "context" "crypto/tls" "encoding/binary" "encoding/json" "errors" "fmt" "io" "iter" "log" "maps" "math/rand/v2" "net" "net/http" "net/http/httptest" "net/netip" "os/exec" "strconv" "sync" "sync/atomic" "time" "github.com/gaissmai/bart" "github.com/google/gopacket" "github.com/google/gopacket/layers" "go4.org/mem" "gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/link/channel" "gvisor.dev/gvisor/pkg/tcpip/network/arp" "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/transport/icmp" "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" "gvisor.dev/gvisor/pkg/waiter" "tailscale.com/client/tailscale" "tailscale.com/derp" "tailscale.com/derp/derphttp" "tailscale.com/net/netutil" "tailscale.com/net/stun" "tailscale.com/syncs" "tailscale.com/tailcfg" "tailscale.com/tstest/integration/testcontrol" "tailscale.com/types/key" "tailscale.com/types/logger" "tailscale.com/util/mak" "tailscale.com/util/must" "tailscale.com/util/set" "tailscale.com/util/zstdframe" ) const nicID = 1 const ( stunPort = 3478 pcpPort = 5351 ssdpPort = 1900 ) func (s *Server) PopulateDERPMapIPs() error { out, err := exec.Command("tailscale", "debug", "derp-map").Output() if err != nil { return fmt.Errorf("tailscale debug derp-map: %v", err) } var dm tailcfg.DERPMap if err := json.Unmarshal(out, &dm); err != nil { return fmt.Errorf("unmarshal DERPMap: %v", err) } for _, r := range dm.Regions { for _, n := range r.Nodes { if n.IPv4 != "" { s.derpIPs.Add(netip.MustParseAddr(n.IPv4)) } } } return nil } func (n *network) InitNAT(natType NAT) error { ctor, ok := natTypes[natType] if !ok { return fmt.Errorf("unknown NAT type %q", natType) } t, err := ctor(n) if err != nil { return fmt.Errorf("error creating NAT type %q for network %v: %w", natType, n.wanIP4, err) } n.setNATTable(t) n.natStyle.Store(natType) return nil } func (n *network) setNATTable(nt NATTable) { n.natMu.Lock() defer n.natMu.Unlock() n.natTable = nt } // SoleLANIP implements [IPPool]. func (n *network) SoleLANIP() (netip.Addr, bool) { if len(n.nodesByIP4) != 1 { return netip.Addr{}, false } for ip := range n.nodesByIP4 { return ip, true } return netip.Addr{}, false } // WANIP implements [IPPool]. func (n *network) WANIP() netip.Addr { return n.wanIP4 } func (n *network) initStack() error { n.ns = stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ ipv4.NewProtocol, ipv6.NewProtocol, arp.NewProtocol, }, TransportProtocols: []stack.TransportProtocolFactory{ tcp.NewProtocol, icmp.NewProtocol4, }, }) sackEnabledOpt := tcpip.TCPSACKEnabled(true) // TCP SACK is disabled by default tcpipErr := n.ns.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt) if tcpipErr != nil { return fmt.Errorf("SetTransportProtocolOption SACK: %v", tcpipErr) } n.linkEP = channel.New(512, 1500, tcpip.LinkAddress(n.mac.HWAddr())) if tcpipProblem := n.ns.CreateNIC(nicID, n.linkEP); tcpipProblem != nil { return fmt.Errorf("CreateNIC: %v", tcpipProblem) } n.ns.SetPromiscuousMode(nicID, true) n.ns.SetSpoofing(nicID, true) var routes []tcpip.Route if n.v4 { prefix := tcpip.AddrFrom4Slice(n.lanIP4.Addr().AsSlice()).WithPrefix() prefix.PrefixLen = n.lanIP4.Bits() if tcpProb := n.ns.AddProtocolAddress(nicID, tcpip.ProtocolAddress{ Protocol: ipv4.ProtocolNumber, AddressWithPrefix: prefix, }, stack.AddressProperties{}); tcpProb != nil { return errors.New(tcpProb.String()) } ipv4Subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice(make([]byte, 4)), tcpip.MaskFromBytes(make([]byte, 4))) if err != nil { return fmt.Errorf("could not create IPv4 subnet: %v", err) } routes = append(routes, tcpip.Route{ Destination: ipv4Subnet, 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 maxInFlightConnectionAttempts = 8192 tcpFwd := tcp.NewForwarder(n.ns, tcpReceiveBufferSize, maxInFlightConnectionAttempts, n.acceptTCP) n.ns.SetTransportProtocolHandler(tcp.ProtocolNumber, func(tei stack.TransportEndpointID, pb *stack.PacketBuffer) (handled bool) { return tcpFwd.HandlePacket(tei, pb) }) go func() { for { pkt := n.linkEP.ReadContext(n.s.shutdownCtx) if pkt == nil { if n.s.shutdownCtx.Err() != nil { // Return without logging. return } continue } n.handleIPPacketFromGvisor(pkt.ToView().AsSlice()) } }() return nil } func (n *network) handleIPPacketFromGvisor(ipRaw []byte) { 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, layers.LayerTypeIPv4, gopacket.Lazy) case 6: goPkt = gopacket.NewPacket( ipRaw, layers.LayerTypeIPv6, gopacket.Lazy) default: panic(fmt.Sprintf("unexpected IP packet version %v", ipVer)) } flow, ok := flow(goPkt) if !ok { panic("unexpected gvisor packet") } node, ok := n.nodeByIP(flow.dst) if !ok { n.logf("no node for netstack dest IP %v", flow.dst) return } eth := &layers.Ethernet{ SrcMAC: n.mac.HWAddr(), DstMAC: node.mac.HWAddr(), EthernetType: flow.etherType(), } buffer := gopacket.NewSerializeBuffer() options := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true} sls := []gopacket.SerializableLayer{ eth, } for _, layer := range goPkt.Layers() { sl, ok := layer.(gopacket.SerializableLayer) if !ok { log.Fatalf("layer %s is not serializable", layer.LayerType().String()) } switch gl := layer.(type) { case *layers.TCP: gl.SetNetworkLayerForChecksum(goPkt.NetworkLayer()) case *layers.UDP: gl.SetNetworkLayerForChecksum(goPkt.NetworkLayer()) } sls = append(sls, sl) } if err := gopacket.SerializeLayers(buffer, options, sls...); err != nil { n.logf("gvisor: serialize error: %v", err) return } if nw, ok := n.writers.Load(node.mac); ok { nw.write(buffer.Bytes()) } else { n.logf("gvisor write: no writeFunc for %v", node.mac) } } func netaddrIPFromNetstackIP(s tcpip.Address) netip.Addr { switch s.Len() { case 4: return netip.AddrFrom4(s.As4()) case 16: return netip.AddrFrom16(s.As16()).Unmap() } return netip.Addr{} } func stringifyTEI(tei stack.TransportEndpointID) string { localHostPort := net.JoinHostPort(tei.LocalAddress.String(), strconv.Itoa(int(tei.LocalPort))) remoteHostPort := net.JoinHostPort(tei.RemoteAddress.String(), strconv.Itoa(int(tei.RemotePort))) return fmt.Sprintf("%s -> %s", remoteHostPort, localHostPort) } func (n *network) acceptTCP(r *tcp.ForwarderRequest) { reqDetails := r.ID() clientRemoteIP := netaddrIPFromNetstackIP(reqDetails.RemoteAddress) destIP := netaddrIPFromNetstackIP(reqDetails.LocalAddress) destPort := reqDetails.LocalPort if !clientRemoteIP.IsValid() { r.Complete(true) // sends a RST return } log.Printf("vnet-AcceptTCP: %v", stringifyTEI(reqDetails)) var wq waiter.Queue ep, err := r.CreateEndpoint(&wq) if err != nil { log.Printf("CreateEndpoint error for %s: %v", stringifyTEI(reqDetails), err) r.Complete(true) // sends a RST return } ep.SocketOptions().SetKeepAlive(true) if destPort == 123 { r.Complete(false) tc := gonet.NewTCPConn(&wq, ep) io.WriteString(tc, "Hello from Go\nGoodbye.\n") tc.Close() return } if destPort == 8008 && fakeTestAgent.Match(destIP) { node, ok := n.nodeByIP(clientRemoteIP) if !ok { n.logf("unknown client IP %v trying to connect to test driver", clientRemoteIP) r.Complete(true) return } r.Complete(false) tc := gonet.NewTCPConn(&wq, ep) ac := &agentConn{node, tc} n.s.addIdleAgentConn(ac) return } if destPort == 80 && fakeControl.Match(destIP) { r.Complete(false) tc := gonet.NewTCPConn(&wq, ep) hs := &http.Server{Handler: n.s.control} go hs.Serve(netutil.NewOneConnListener(tc, nil)) return } if fakeDERP1.Match(destIP) || fakeDERP2.Match(destIP) { if destPort == 443 { ds := n.s.derps[0] if fakeDERP2.Match(destIP) { ds = n.s.derps[1] } r.Complete(false) tc := gonet.NewTCPConn(&wq, ep) tlsConn := tls.Server(tc, ds.tlsConfig) hs := &http.Server{Handler: ds.handler} go hs.Serve(netutil.NewOneConnListener(tlsConn, nil)) return } if destPort == 80 { r.Complete(false) tc := gonet.NewTCPConn(&wq, ep) hs := &http.Server{Handler: n.s.derps[0].handler} go hs.Serve(netutil.NewOneConnListener(tc, nil)) return } } if destPort == 443 && fakeLogCatcher.Match(destIP) { r.Complete(false) tc := gonet.NewTCPConn(&wq, ep) go n.serveLogCatcherConn(clientRemoteIP, tc) return } var targetDial string if n.s.derpIPs.Contains(destIP) { targetDial = destIP.String() + ":" + strconv.Itoa(int(destPort)) } else if fakeProxyControlplane.Match(destIP) { targetDial = "controlplane.tailscale.com:" + strconv.Itoa(int(destPort)) } if targetDial != "" { c, err := net.Dial("tcp", targetDial) if err != nil { r.Complete(true) log.Printf("Dial controlplane: %v", err) return } defer c.Close() tc := gonet.NewTCPConn(&wq, ep) defer tc.Close() r.Complete(false) errc := make(chan error, 2) go func() { _, err := io.Copy(tc, c); errc <- err }() go func() { _, err := io.Copy(c, tc); errc <- err }() <-errc } else { r.Complete(true) // sends a RST } } // serveLogCatchConn serves a TCP connection to "log.tailscale.io", speaking the // logtail/logcatcher protocol. // // We terminate TLS with an arbitrary cert; the client is configured to not // validate TLS certs for this hostname when running under these integration // tests. func (n *network) serveLogCatcherConn(clientRemoteIP netip.Addr, c net.Conn) { tlsConfig := n.s.derps[0].tlsConfig // self-signed (stealing DERP's); test client configure to not check tlsConn := tls.Server(c, tlsConfig) var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { all, _ := io.ReadAll(r.Body) if r.Header.Get("Content-Encoding") == "zstd" { var err error all, err = zstdframe.AppendDecode(nil, all) if err != nil { log.Printf("LOGS DECODE ERROR zstd decode: %v", err) http.Error(w, "zstd decode error", http.StatusBadRequest) return } } var logs []struct { Logtail struct { Client_Time time.Time } Text string } if err := json.Unmarshal(all, &logs); err != nil { log.Printf("Logs decode error: %v", err) return } node := n.nodesByIP4[clientRemoteIP] if node != nil { node.logMu.Lock() defer node.logMu.Unlock() node.logCatcherWrites++ for _, lg := range logs { tStr := lg.Logtail.Client_Time.Round(time.Millisecond).Format(time.RFC3339Nano) fmt.Fprintf(&node.logBuf, "[%v] %s\n", tStr, lg.Text) } } }) hs := &http.Server{Handler: handler} hs.Serve(netutil.NewOneConnListener(tlsConn, nil)) } type EthernetPacket struct { le *layers.Ethernet gp gopacket.Packet } func (ep EthernetPacket) SrcMAC() MAC { return MAC(ep.le.SrcMAC) } func (ep EthernetPacket) DstMAC() MAC { return MAC(ep.le.DstMAC) } type MAC [6]byte func (m MAC) IsBroadcast() bool { 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) { if len(hwa) != 6 { return MAC{}, false } return MAC(hwa), true } func (m MAC) HWAddr() net.HardwareAddr { return net.HardwareAddr(m[:]) } func (m MAC) String() string { return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", m[0], m[1], m[2], m[3], m[4], m[5]) } type portMapping struct { dst netip.AddrPort // LAN IP:port expiry time.Time } // writerFunc is a function that writes an Ethernet frame to a connected client. // // ethFrame is the Ethernet frame to write. // // interfaceIndexID is the interface ID for the pcap file. type writerFunc func(dst vmClient, ethFrame []byte, interfaceIndexID int) // networkWriter are the arguments to a writerFunc and the writerFunc. type networkWriter struct { writer writerFunc // Function to write packets to the network c vmClient interfaceID int // The interface ID of the src node (for writing pcaps) } func (nw networkWriter) write(b []byte) { nw.writer(nw.c, b, nw.interfaceID) } type network struct { s *Server num int // 1-based mac MAC // of router portmap bool lanInterfaceID int wanInterfaceID int v4 bool // network supports IPv4 v6 bool // network support IPv6 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) nodesByIP4 map[netip.Addr]*node // by LAN IPv4 nodesByMAC map[MAC]*node logf func(format string, args ...any) ns *stack.Stack linkEP *channel.Endpoint natStyle syncs.AtomicValue[NAT] natMu sync.Mutex // held while using + changing natTable natTable NATTable portMap map[netip.AddrPort]portMapping // WAN ip:port -> LAN ip:port 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. // It contains entries for connected nodes only. writers syncs.Map[MAC, networkWriter] // MAC -> to networkWriter for that MAC } // registerWriter registers a client address with a MAC address. func (n *network) registerWriter(mac MAC, c vmClient) { nw := networkWriter{ writer: n.s.writeEthernetFrameToVM, c: c, } if node, ok := n.s.nodeByMAC[mac]; ok { nw.interfaceID = node.interfaceID } n.writers.Store(mac, nw) } func (n *network) unregisterWriter(mac MAC) { n.writers.Delete(mac) } func (n *network) MACOfIP(ip netip.Addr) (_ MAC, ok bool) { if n.lanIP4.Addr() == ip { return n.mac, true } if n, ok := n.nodesByIP4[ip]; ok { return n.mac, true } return MAC{}, false } type node struct { mac MAC num int // 1-based node number interfaceID int net *network lanIP netip.Addr // must be in net.lanIP prefix + unique in net verboseSyslog bool // logMu guards logBuf. // TODO(bradfitz): conditionally write these out to separate files at the end? // Currently they only hold logcatcher logs. logMu sync.Mutex logBuf bytes.Buffer logCatcherWrites int } // String returns the string "nodeN" where N is the 1-based node number. func (n *node) String() string { return fmt.Sprintf("node%d", n.num) } type derpServer struct { srv *derp.Server handler http.Handler tlsConfig *tls.Config } func newDERPServer() *derpServer { // Just to get a self-signed TLS cert: ts := httptest.NewTLSServer(nil) ts.Close() ds := &derpServer{ srv: derp.NewServer(key.NewNode(), logger.Discard), tlsConfig: ts.TLS, // self-signed; test client configure to not check } var mux http.ServeMux mux.Handle("/derp", derphttp.Handler(ds.srv)) mux.HandleFunc("/generate_204", derphttp.ServeNoContent) ds.handler = &mux return ds } type Server struct { shutdownCtx context.Context shutdownCancel context.CancelFunc shuttingDown atomic.Bool wg sync.WaitGroup blendReality bool optLogf func(format string, args ...any) // or nil to use log.Printf derpIPs set.Set[netip.Addr] nodes []*node nodeByMAC map[MAC]*node networks set.Set[*network] networkByWAN *bart.Table[*network] control *testcontrol.Server derps []*derpServer pcapWriter *pcapWriter // writeMu serializes all writes to VM clients. writeMu sync.Mutex scratch []byte mu sync.Mutex agentConnWaiter map[*node]chan<- struct{} // signaled after added to set agentConns set.Set[*agentConn] // not keyed by node; should be small/cheap enough to scan all agentDialer map[*node]DialFunc } func (s *Server) logf(format string, args ...any) { if s.optLogf != nil { s.optLogf(format, args...) } else { log.Printf(format, args...) } } func (s *Server) SetLoggerForTest(logf func(format string, args ...any)) { s.optLogf = logf } type DialFunc func(ctx context.Context, network, address string) (net.Conn, error) var derpMap = &tailcfg.DERPMap{ Regions: map[int]*tailcfg.DERPRegion{ 1: { RegionID: 1, RegionCode: "atlantis", RegionName: "Atlantis", Nodes: []*tailcfg.DERPNode{ { Name: "1a", RegionID: 1, HostName: "derp1.tailscale", IPv4: fakeDERP1.v4.String(), IPv6: fakeDERP1.v6.String(), InsecureForTests: true, CanPort80: true, }, }, }, 2: { RegionID: 2, RegionCode: "northpole", RegionName: "North Pole", Nodes: []*tailcfg.DERPNode{ { Name: "2a", RegionID: 2, HostName: "derp2.tailscale", IPv4: fakeDERP2.v4.String(), IPv6: fakeDERP2.v6.String(), InsecureForTests: true, CanPort80: true, }, }, }, }, } func New(c *Config) (*Server, error) { ctx, cancel := context.WithCancel(context.Background()) s := &Server{ shutdownCtx: ctx, shutdownCancel: cancel, control: &testcontrol.Server{ DERPMap: derpMap, ExplicitBaseURL: "http://control.tailscale", }, blendReality: c.blendReality, derpIPs: set.Of[netip.Addr](), nodeByMAC: map[MAC]*node{}, networkByWAN: &bart.Table[*network]{}, networks: set.Of[*network](), } for range 2 { s.derps = append(s.derps, newDERPServer()) } if err := s.initFromConfig(c); err != nil { return nil, err } for n := range s.networks { if err := n.initStack(); err != nil { return nil, fmt.Errorf("newServer: initStack: %v", err) } } return s, nil } func (s *Server) Close() { if shutdown := s.shuttingDown.Swap(true); !shutdown { s.shutdownCancel() s.pcapWriter.Close() } s.wg.Wait() } // MACs returns the MAC addresses of the configured nodes. func (s *Server) MACs() iter.Seq[MAC] { return maps.Keys(s.nodeByMAC) } func (s *Server) RegisterSinkForTest(mac MAC, fn func(eth []byte)) { n, ok := s.nodeByMAC[mac] if !ok { log.Fatalf("RegisterSinkForTest: unknown MAC %v", mac) } n.net.writers.Store(mac, networkWriter{ writer: func(_ vmClient, eth []byte, _ int) { fn(eth) }, }) } func (s *Server) HWAddr(mac MAC) net.HardwareAddr { // TODO: cache return net.HardwareAddr(mac[:]) } type Protocol int const ( ProtocolQEMU = Protocol(iota + 1) ProtocolUnixDGRAM // for macOS Virtualization.Framework and VZFileHandleNetworkDeviceAttachment ) func (s *Server) writeEthernetFrameToVM(c vmClient, ethPkt []byte, interfaceID int) { s.writeMu.Lock() defer s.writeMu.Unlock() if ethPkt == nil { return } switch c.proto() { case ProtocolQEMU: s.scratch = binary.BigEndian.AppendUint32(s.scratch[:0], uint32(len(ethPkt))) s.scratch = append(s.scratch, ethPkt...) if _, err := c.uc.Write(s.scratch); err != nil { log.Printf("Write pkt: %v", err) } case ProtocolUnixDGRAM: if _, err := c.uc.WriteToUnix(ethPkt, c.raddr); err != nil { log.Printf("Write pkt : %v", err) return } } must.Do(s.pcapWriter.WritePacket(gopacket.CaptureInfo{ Timestamp: time.Now(), CaptureLength: len(ethPkt), Length: len(ethPkt), InterfaceIndex: interfaceID, }, ethPkt)) } // vmClient is a comparable value representing a connection from a VM, either a // QEMU-style client (with streams over a Unix socket) or a datagram based // client (such as macOS Virtualization.framework clients). type vmClient struct { uc *net.UnixConn raddr *net.UnixAddr // nil for QEMU-style clients using streams; else datagram source } func (c vmClient) proto() Protocol { if c.raddr == nil { return ProtocolQEMU } return ProtocolUnixDGRAM } const ethernetHeaderLen = 14 // Handles a single connection from a QEMU-style client or muxd connections for dgram mode func (s *Server) ServeUnixConn(uc *net.UnixConn, proto Protocol) { if s.shuttingDown.Load() { return } s.wg.Add(1) defer s.wg.Done() context.AfterFunc(s.shutdownCtx, func() { uc.SetDeadline(time.Now()) }) log.Printf("Got conn %T %p", uc, uc) defer uc.Close() buf := make([]byte, 16<<10) didReg := map[MAC]bool{} for { var packetRaw []byte var raddr *net.UnixAddr switch proto { case ProtocolUnixDGRAM: n, addr, err := uc.ReadFromUnix(buf) raddr = addr if err != nil { log.Printf("ReadFromUnix: %v", err) continue } packetRaw = buf[:n] case ProtocolQEMU: if _, err := io.ReadFull(uc, buf[:4]); err != nil { if s.shutdownCtx.Err() != nil { // Return without logging. return } log.Printf("ReadFull header: %v", err) return } n := binary.BigEndian.Uint32(buf[:4]) if _, err := io.ReadFull(uc, buf[4:4+n]); err != nil { if s.shutdownCtx.Err() != nil { // Return without logging. return } log.Printf("ReadFull pkt: %v", err) return } packetRaw = buf[4 : 4+n] // raw ethernet frame } c := vmClient{uc, raddr} // For the first packet from a MAC, register a writerFunc to write to the VM. if len(packetRaw) < ethernetHeaderLen { continue } srcMAC := MAC(packetRaw[6:12]) srcNode, ok := s.nodeByMAC[srcMAC] if !ok { log.Printf("[conn %p] got frame from unknown MAC %v", c.uc, srcMAC) continue } if !didReg[srcMAC] { didReg[srcMAC] = true log.Printf("[conn %p] Registering writer for MAC %v, node %v", c.uc, srcMAC, srcNode.lanIP) srcNode.net.registerWriter(srcMAC, c) defer srcNode.net.unregisterWriter(srcMAC) } if err := s.handleEthernetFrameFromVM(packetRaw); err != nil { srcNode.net.logf("handleEthernetFrameFromVM: [conn %p], %v", c.uc, err) } } } func (s *Server) handleEthernetFrameFromVM(packetRaw []byte) error { packet := gopacket.NewPacket(packetRaw, layers.LayerTypeEthernet, gopacket.Lazy) le, ok := packet.LinkLayer().(*layers.Ethernet) if !ok || len(le.SrcMAC) != 6 || len(le.DstMAC) != 6 { return fmt.Errorf("ignoring non-Ethernet packet: % 02x", packetRaw) } ep := EthernetPacket{le, packet} srcMAC := ep.SrcMAC() srcNode, ok := s.nodeByMAC[srcMAC] if !ok { return fmt.Errorf("got frame from unknown MAC %v", srcMAC) } must.Do(s.pcapWriter.WritePacket(gopacket.CaptureInfo{ Timestamp: time.Now(), CaptureLength: len(packetRaw), Length: len(packetRaw), InterfaceIndex: srcNode.interfaceID, }, packetRaw)) srcNode.net.HandleEthernetPacket(ep) return nil } func (s *Server) routeUDPPacket(up UDPPacket) { // Find which network owns this based on the destination IP // and all the known networks' wan IPs. // But certain things (like STUN) we do in-process. if up.Dst.Port() == stunPort { // TODO(bradfitz): fake latency; time.AfterFunc the response if res, ok := makeSTUNReply(up); ok { //log.Printf("STUN reply: %+v", res) s.routeUDPPacket(res) } else { log.Printf("weird: STUN packet not handled") } return } dstIP := up.Dst.Addr() netw, ok := s.networkByWAN.Lookup(dstIP) if !ok { if dstIP.IsPrivate() { // Not worth spamming logs. RFC 1918 space doesn't route. return } log.Printf("no network to route UDP packet for %v", up.Dst) return } netw.HandleUDPPacket(up) } // writeEth writes a raw Ethernet frame to all (0, 1, or multiple) connected // clients on the network. // // This only delivers to client devices and not the virtual router/gateway // device. // // It reports whether a packet was written to any clients. func (n *network) writeEth(res []byte) bool { if len(res) < 12 { return false } dstMAC := MAC(res[0:6]) srcMAC := MAC(res[6:12]) if dstMAC.IsBroadcast() { num := 0 n.writers.Range(func(mac MAC, nw networkWriter) bool { if mac != srcMAC { num++ nw.write(res) } return true }) return num > 0 } if srcMAC == dstMAC { n.logf("dropping write of packet from %v to itself", srcMAC) return false } if nw, ok := n.writers.Load(dstMAC); ok { nw.write(res) 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} macBroadcast = MAC{0xff, 0xff, 0xff, 0xff, 0xff, 0xff} ) const ( testingEthertype layers.EthernetType = 0x1234 ) func (n *network) HandleEthernetPacket(ep EthernetPacket) { packet := ep.gp dstMAC := ep.DstMAC() isBroadcast := dstMAC.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 { default: n.logf("Dropping non-IP packet: %v", ep.le.EthernetType) return case 0x1234: // Permitted for testing. Not a real ethertype. case layers.EthernetTypeARP: res, err := n.createARPResponse(packet) if err != nil { n.logf("createARPResponse: %v", err) } else { n.writeEth(res) } return case layers.EthernetTypeIPv6: if !n.v6 { n.logf("dropping IPv6 packet on v4-only network") 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: // Below } // Send ethernet broadcasts and unicast ethernet frames to peers // on the same network. This is all LAN traffic that isn't meant // for the router/gw itself: if isBroadcast || !forRouter { n.writeEth(ep.gp.Data()) } if forRouter { n.HandleEthernetPacketForRouter(ep) } } // HandleUDPPacket handles a UDP packet arriving from the internet, // addressed to the router's WAN IP. It is then NATed back to a // LAN IP here and wrapped in an ethernet layer and delivered // to the network. func (n *network) HandleUDPPacket(p UDPPacket) { buf, err := n.serializedUDPPacket(p.Src, p.Dst, p.Payload, nil) if err != nil { n.logf("serializing UDP packet: %v", err) return } n.s.pcapWriter.WritePacket(gopacket.CaptureInfo{ Timestamp: time.Now(), CaptureLength: len(buf), Length: len(buf), InterfaceIndex: n.wanInterfaceID, }, buf) dst := n.doNATIn(p.Src, p.Dst) if !dst.IsValid() { n.logf("Warning: NAT dropped packet; no mapping for %v=>%v", p.Src, p.Dst) return } p.Dst = dst buf, err = n.serializedUDPPacket(p.Src, p.Dst, p.Payload, nil) if err != nil { n.logf("serializing UDP packet: %v", err) return } n.s.pcapWriter.WritePacket(gopacket.CaptureInfo{ Timestamp: time.Now(), CaptureLength: len(buf), Length: len(buf), InterfaceIndex: n.lanInterfaceID, }, buf) n.WriteUDPPacketNoNAT(p) } func (n *network) nodeByIP(ip netip.Addr) (node *node, ok bool) { if ip.Is4() { node, ok = n.nodesByIP4[ip] } if !ok && ip.Is6() { var mac MAC n.macMu.Lock() mac, ok = n.macOfIPv6[ip] n.macMu.Unlock() if !ok { log.Printf("warning: no known MAC for IPv6 %v", ip) return nil, false } node, ok = n.nodesByMAC[mac] if !ok { log.Printf("warning: no known node for MAC %v (IP %v)", mac, ip) } } return node, ok } // WriteUDPPacketNoNAT writes a UDP packet to the network, without // doing any NAT translation. // // The packet will always have the ethernet src MAC of the router // so this should not be used for packets between clients on the // same ethernet segment. func (n *network) WriteUDPPacketNoNAT(p UDPPacket) { src, dst := p.Src, p.Dst node, ok := n.nodeByIP(dst.Addr()) if !ok { n.logf("no node for dest IP %v in UDP packet %v=>%v", dst.Addr(), p.Src, p.Dst) return } eth := &layers.Ethernet{ SrcMAC: n.mac.HWAddr(), // of gateway DstMAC: node.mac.HWAddr(), EthernetType: p.etherType(), } ethRaw, err := n.serializedUDPPacket(src, dst, p.Payload, eth) if err != nil { n.logf("serializing UDP packet: %v", err) return } 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 // destination IP:port pairs, and payload. // // If eth is non-nil, it will be used as the Ethernet layer, otherwise the // Ethernet layer will be omitted from the serialization. func (n *network) serializedUDPPacket(src, dst netip.AddrPort, payload []byte, eth *layers.Ethernet) ([]byte, error) { ip := mkIPLayer(layers.IPProtocolUDP, src.Addr(), dst.Addr()) udp := &layers.UDP{ SrcPort: layers.UDPPort(src.Port()), DstPort: layers.UDPPort(dst.Port()), } udp.SetNetworkLayerForChecksum(ip) buffer := gopacket.NewSerializeBuffer() options := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true} layers := []gopacket.SerializableLayer{eth, ip, udp, gopacket.Payload(payload)} if eth == nil { layers = layers[1:] } if err := gopacket.SerializeLayers(buffer, options, layers...); err != nil { return nil, fmt.Errorf("serializing UDP from %v to %v: %v", src, dst, err) } return buffer.Bytes(), nil } // HandleEthernetPacketForRouter handles a packet that is // directed to the router/gateway itself. The packet may be to the // broadcast MAC address, or to the router's MAC address. The target // IP may be the router's IP, or an internet (routed) IP. func (n *network) HandleEthernetPacketForRouter(ep EthernetPacket) { packet := ep.gp flow, ok := flow(packet) if !ok { n.logf("dropping non-IP packet: %v", packet) return } dstIP := flow.dst toForward := dstIP != n.lanIP4.Addr() && dstIP != netip.IPv4Unspecified() && !dstIP.IsLinkLocalUnicast() // Pre-NAT mapping, for DNS/etc responses: if flow.src.Is6() { n.macMu.Lock() mak.Set(&n.macOfIPv6, flow.src, ep.SrcMAC()) n.macMu.Unlock() } if udp, ok := packet.Layer(layers.LayerTypeUDP).(*layers.UDP); ok { n.handleUDPPacketForRouter(ep, udp, toForward, flow) return } if toForward && n.s.shouldInterceptTCP(packet) { var base *layers.BaseLayer proto := header.IPv4ProtocolNumber if v4, ok := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4); ok { 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{ Payload: buffer.MakeWithData(pktCopy), }) n.linkEP.InjectInbound(proto, packetBuf) packetBuf.DecRef() return } if flow.src.Is6() && flow.src.IsLinkLocalUnicast() && !flow.dst.IsLinkLocalUnicast() { // Don't log. return } n.logf("router got unknown packet: %v", packet) } func (n *network) handleUDPPacketForRouter(ep EthernetPacket, udp *layers.UDP, toForward bool, flow ipSrcDst) { packet := ep.gp srcIP, dstIP := flow.src, flow.dst if isDHCPRequest(packet) { res, err := n.s.createDHCPResponse(packet) if err != nil { n.logf("createDHCPResponse: %v", err) return } n.writeEth(res) return } if isMDNSQuery(packet) || isIGMP(packet) { // Don't log. Spammy for now. return } if isDNSRequest(packet) { res, err := n.s.createDNSResponse(packet) if err != nil { n.logf("createDNSResponse: %v", err) return } n.writeEth(res) return } if fakeSyslog.Match(dstIP) { node, ok := n.nodeByIP(srcIP) if !ok { return } if node.verboseSyslog { // TODO(bradfitz): parse this and capture it, structured, into // node's log buffer. log.Printf("syslog from %v: %s", node, udp.Payload) } return } if dstIP == n.lanIP4.Addr() && isNATPMP(udp) { n.handleNATPMPRequest(UDPPacket{ Src: netip.AddrPortFrom(srcIP, uint16(udp.SrcPort)), Dst: netip.AddrPortFrom(dstIP, uint16(udp.DstPort)), Payload: udp.Payload, }) return } if toForward { src := netip.AddrPortFrom(srcIP, uint16(udp.SrcPort)) dst := netip.AddrPortFrom(dstIP, uint16(udp.DstPort)) buf, err := n.serializedUDPPacket(src, dst, udp.Payload, nil) if err != nil { n.logf("serializing UDP packet: %v", err) return } n.s.pcapWriter.WritePacket(gopacket.CaptureInfo{ Timestamp: time.Now(), CaptureLength: len(buf), Length: len(buf), InterfaceIndex: n.lanInterfaceID, }, buf) lanSrc := src // the original src, before NAT (for logging only) src = n.doNATOut(src, dst) if !src.IsValid() { n.logf("warning: NAT dropped packet; no NAT out mapping for %v=>%v", lanSrc, dst) return } buf, err = n.serializedUDPPacket(src, dst, udp.Payload, nil) if err != nil { n.logf("serializing UDP packet: %v", err) return } n.s.pcapWriter.WritePacket(gopacket.CaptureInfo{ Timestamp: time.Now(), CaptureLength: len(buf), Length: len(buf), InterfaceIndex: n.wanInterfaceID, }, buf) if src.Addr().Is6() { n.macMu.Lock() mak.Set(&n.macOfIPv6, src.Addr(), ep.SrcMAC()) n.macMu.Unlock() } n.s.routeUDPPacket(UDPPacket{ Src: src, Dst: dst, Payload: udp.Payload, }) return } if udp.DstPort == pcpPort || udp.DstPort == ssdpPort { // We handle NAT-PMP, but not these yet. // TODO(bradfitz): handle? marginal utility so far. // Don't log about them being unknown. return } n.logf("router got unknown UDP 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.DstMAC, eth.SrcMAC) 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) { ethLayer := request.Layer(layers.LayerTypeEthernet).(*layers.Ethernet) srcMAC, ok := macOf(ethLayer.SrcMAC) if !ok { return nil, nil } node, ok := s.nodeByMAC[srcMAC] if !ok { log.Printf("DHCP request from unknown node %v; ignoring", srcMAC) return nil, nil } gwIP := node.net.lanIP4.Addr() ipLayer := request.Layer(layers.LayerTypeIPv4).(*layers.IPv4) udpLayer := request.Layer(layers.LayerTypeUDP).(*layers.UDP) dhcpLayer := request.Layer(layers.LayerTypeDHCPv4).(*layers.DHCPv4) response := &layers.DHCPv4{ Operation: layers.DHCPOpReply, HardwareType: layers.LinkTypeEthernet, HardwareLen: 6, Xid: dhcpLayer.Xid, ClientHWAddr: dhcpLayer.ClientHWAddr, Flags: dhcpLayer.Flags, YourClientIP: node.lanIP.AsSlice(), Options: []layers.DHCPOption{ { Type: layers.DHCPOptServerID, Data: gwIP.AsSlice(), // DHCP server's IP Length: 4, }, }, } var msgType layers.DHCPMsgType for _, opt := range dhcpLayer.Options { if opt.Type == layers.DHCPOptMessageType && opt.Length > 0 { msgType = layers.DHCPMsgType(opt.Data[0]) } } switch msgType { case layers.DHCPMsgTypeDiscover: response.Options = append(response.Options, layers.DHCPOption{ Type: layers.DHCPOptMessageType, Data: []byte{byte(layers.DHCPMsgTypeOffer)}, Length: 1, }) case layers.DHCPMsgTypeRequest: response.Options = append(response.Options, layers.DHCPOption{ Type: layers.DHCPOptMessageType, Data: []byte{byte(layers.DHCPMsgTypeAck)}, Length: 1, }, layers.DHCPOption{ Type: layers.DHCPOptLeaseTime, Data: binary.BigEndian.AppendUint32(nil, 3600), // hour? sure. Length: 4, }, layers.DHCPOption{ Type: layers.DHCPOptRouter, Data: gwIP.AsSlice(), Length: 4, }, layers.DHCPOption{ Type: layers.DHCPOptDNS, Data: fakeDNS.v4.AsSlice(), Length: 4, }, layers.DHCPOption{ Type: layers.DHCPOptSubnetMask, Data: net.CIDRMask(node.net.lanIP4.Bits(), 32), Length: 4, }, ) } eth := &layers.Ethernet{ SrcMAC: node.net.mac.HWAddr(), DstMAC: ethLayer.SrcMAC, EthernetType: layers.EthernetTypeIPv4, // never IPv6 for DHCP } ip := &layers.IPv4{ Version: 4, TTL: 64, Protocol: layers.IPProtocolUDP, SrcIP: ipLayer.DstIP, DstIP: ipLayer.SrcIP, } udp := &layers.UDP{ SrcPort: udpLayer.DstPort, DstPort: udpLayer.SrcPort, } udp.SetNetworkLayerForChecksum(ip) buffer := gopacket.NewSerializeBuffer() options := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true} if err := gopacket.SerializeLayers(buffer, options, eth, ip, udp, response, ); err != nil { return nil, err } return buffer.Bytes(), nil } func isDHCPRequest(pkt gopacket.Packet) bool { v4, ok := pkt.Layer(layers.LayerTypeIPv4).(*layers.IPv4) if !ok || v4.Protocol != layers.IPProtocolUDP { return false } udp, ok := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP) return ok && udp.DstPort == 67 && udp.SrcPort == 68 } func isIGMP(pkt gopacket.Packet) bool { return pkt.Layer(layers.LayerTypeIGMP) != nil } func isMDNSQuery(pkt gopacket.Packet) bool { udp, ok := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP) // TODO(bradfitz): also check IPv4 DstIP=224.0.0.251 (or whatever) return ok && udp.SrcPort == 5353 && udp.DstPort == 5353 } func (s *Server) shouldInterceptTCP(pkt gopacket.Packet) bool { tcp, ok := pkt.Layer(layers.LayerTypeTCP).(*layers.TCP) if !ok { return false } if tcp.DstPort == 123 { // Test port for TCP interception. Not really useful, but cute for // demos. return true } flow, ok := flow(pkt) if !ok { return false } if flow.src.Is6() && flow.src.IsLinkLocalUnicast() { return false } if tcp.DstPort == 80 || tcp.DstPort == 443 { for _, v := range []virtualIP{fakeControl, fakeDERP1, fakeDERP2, fakeLogCatcher} { if v.Match(flow.dst) { return true } } if fakeProxyControlplane.Match(flow.dst) { return s.blendReality } if s.derpIPs.Contains(flow.dst) { return true } } if tcp.DstPort == 8008 && fakeTestAgent.Match(flow.dst) { // Connection from cmd/tta. return true } 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. func isDNSRequest(pkt gopacket.Packet) bool { udp, ok := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP) if !ok || udp.DstPort != 53 { return false } f, ok := flow(pkt) if !ok { return false } if !fakeDNS.Match(f.dst) { // TODO(bradfitz): maybe support configs where DNS is local in the LAN return false } dns, ok := pkt.Layer(layers.LayerTypeDNS).(*layers.DNS) return ok && dns.QR == false && len(dns.Questions) > 0 } func isNATPMP(udp *layers.UDP) bool { return udp.DstPort == 5351 && len(udp.Payload) > 0 && udp.Payload[0] == 0 // version 0, not 2 for PCP } func makeSTUNReply(req UDPPacket) (res UDPPacket, ok bool) { txid, err := stun.ParseBindingRequest(req.Payload) if err != nil { log.Printf("invalid STUN request: %v", err) return res, false } return UDPPacket{ Src: req.Dst, Dst: req.Src, Payload: stun.Response(txid, req.Src), }, true } 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) udpLayer := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP) dnsLayer := pkt.Layer(layers.LayerTypeDNS).(*layers.DNS) if dnsLayer.OpCode != layers.DNSOpCodeQuery || dnsLayer.QR || len(dnsLayer.Questions) == 0 { return nil, nil } response := &layers.DNS{ ID: dnsLayer.ID, QR: true, AA: true, TC: false, RD: dnsLayer.RD, RA: true, OpCode: layers.DNSOpCodeQuery, ResponseCode: layers.DNSResponseCodeNoErr, } var names []string for _, q := range dnsLayer.Questions { response.QDCount++ response.Questions = append(response.Questions, q) if mem.HasSuffix(mem.B(q.Name), mem.S(".pool.ntp.org")) { // Just drop DNS queries for NTP servers. For Debian/etc guests used // during development. Not needed. Assume VM guests get correct time // via their hypervisor. return nil, nil } names = append(names, q.Type.String()+"/"+string(q.Name)) if q.Class != layers.DNSClassIN { continue } 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.Answers = append(response.Answers, layers.DNSResourceRecord{ Name: q.Name, Type: q.Type, Class: q.Class, IP: ip.AsSlice(), TTL: 60, }) } } } // Make reply layers, all reversed. eth2 := &layers.Ethernet{ SrcMAC: ethLayer.DstMAC, DstMAC: ethLayer.SrcMAC, EthernetType: flow.etherType(), } ip2 := mkIPLayer(layers.IPProtocolUDP, flow.dst, flow.src) udp2 := &layers.UDP{ SrcPort: udpLayer.DstPort, DstPort: udpLayer.SrcPort, } udp2.SetNetworkLayerForChecksum(ip2) buffer := gopacket.NewSerializeBuffer() options := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true} if err := gopacket.SerializeLayers(buffer, options, eth2, ip2, udp2, response); err != nil { return nil, err } const debugDNS = false if debugDNS { if len(response.Answers) > 0 { back := gopacket.NewPacket(buffer.Bytes(), layers.LayerTypeEthernet, gopacket.Lazy) log.Printf("createDNSResponse generated answers: %v", back) } else { log.Printf("made empty response for %q", names) } } return buffer.Bytes(), nil } // doNATOut performs NAT on an outgoing packet from src to dst, where // src is a LAN IP and dst is a WAN IP. // // It returns the source WAN ip:port to use. // // If newSrc is invalid, the packet should be dropped. 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() defer n.natMu.Unlock() // First see if there's a port mapping, before doing NAT. if wanAP, ok := n.portMapFlow[portmapFlowKey{ peerWAN: dst, lanAP: src, }]; ok { return wanAP } return n.natTable.PickOutgoingSrc(src, dst, time.Now()) } type portmapFlowKey struct { peerWAN netip.AddrPort // the peer's WAN ip:port lanAP netip.AddrPort } // doNATIn performs NAT on an incoming packet from WAN src to WAN dst, returning // a new destination LAN ip:port to use. // // If newDst is invalid, the packet should be dropped. 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() defer n.natMu.Unlock() now := time.Now() // First see if there's a port mapping, before doing NAT. if lanAP, ok := n.portMap[dst]; ok { if now.Before(lanAP.expiry) { mak.Set(&n.portMapFlow, portmapFlowKey{ peerWAN: src, lanAP: lanAP.dst, }, dst) //n.logf("NAT: doNatIn: port mapping %v=>%v", dst, lanAP.dst) return lanAP.dst } n.logf("NAT: doNatIn: port mapping EXPIRED for %v=>%v", dst, lanAP.dst) delete(n.portMap, dst) return netip.AddrPort{} } return n.natTable.PickIncomingDst(src, dst, now) } // IsPublicPortUsed reports whether the given public port is currently in use. // // n.natMu must be held by the caller. (It's only called by nat implementations // which are always called with natMu held)) func (n *network) IsPublicPortUsed(ap netip.AddrPort) bool { _, ok := n.portMap[ap] return ok } func (n *network) doPortMap(src netip.Addr, dstLANPort, wantExtPort uint16, sec int) (gotPort uint16, ok bool) { n.natMu.Lock() defer n.natMu.Unlock() if !n.portmap { return 0, false } wanAP := netip.AddrPortFrom(n.wanIP4, wantExtPort) dst := netip.AddrPortFrom(src, dstLANPort) if sec == 0 { lanAP, ok := n.portMap[wanAP] if ok && lanAP.dst.Addr() == src { delete(n.portMap, wanAP) } return 0, false } // See if they already have a mapping and extend expiry if so. for k, v := range n.portMap { if v.dst == dst { n.portMap[k] = portMapping{ dst: dst, expiry: time.Now().Add(time.Duration(sec) * time.Second), } return k.Port(), true } } for try := 0; try < 20_000; try++ { if wanAP.Port() > 0 && !n.natTable.IsPublicPortUsed(wanAP) { mak.Set(&n.portMap, wanAP, portMapping{ dst: dst, expiry: time.Now().Add(time.Duration(sec) * time.Second), }) n.logf("vnet: allocated NAT mapping from %v to %v", wanAP, dst) return wanAP.Port(), true } wantExtPort = rand.N(uint16(32<<10)) + 32<<10 wanAP = netip.AddrPortFrom(n.wanIP4, wantExtPort) } return 0, false } func (n *network) createARPResponse(pkt gopacket.Packet) ([]byte, error) { ethLayer, ok := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet) if !ok { return nil, nil } arpLayer, ok := pkt.Layer(layers.LayerTypeARP).(*layers.ARP) if !ok || arpLayer.Operation != layers.ARPRequest || arpLayer.AddrType != layers.LinkTypeEthernet || arpLayer.Protocol != layers.EthernetTypeIPv4 || arpLayer.HwAddressSize != 6 || arpLayer.ProtAddressSize != 4 || len(arpLayer.DstProtAddress) != 4 { return nil, nil } wantIP := netip.AddrFrom4([4]byte(arpLayer.DstProtAddress)) foundMAC, ok := n.MACOfIP(wantIP) if !ok { return nil, nil } eth := &layers.Ethernet{ SrcMAC: foundMAC.HWAddr(), DstMAC: ethLayer.SrcMAC, EthernetType: layers.EthernetTypeARP, } a2 := &layers.ARP{ AddrType: layers.LinkTypeEthernet, Protocol: layers.EthernetTypeIPv4, // never IPv6; IPv6 equivalent of ARP is handleIPv6NeighborSolicitation HwAddressSize: 6, ProtAddressSize: 4, Operation: layers.ARPReply, SourceHwAddress: foundMAC.HWAddr(), SourceProtAddress: arpLayer.DstProtAddress, DstHwAddress: ethLayer.SrcMAC, DstProtAddress: arpLayer.SourceProtAddress, } buffer := gopacket.NewSerializeBuffer() options := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true} if err := gopacket.SerializeLayers(buffer, options, eth, a2); err != nil { return nil, err } return buffer.Bytes(), nil } func (n *network) handleNATPMPRequest(req UDPPacket) { if !n.portmap { return } if string(req.Payload) == "\x00\x00" { // https://www.rfc-editor.org/rfc/rfc6886#section-3.2 res := make([]byte, 0, 12) res = append(res, 0, // version 0 (NAT-PMP) 128, // response to op 0 (128+0) 0, 0, // result code success ) res = binary.BigEndian.AppendUint32(res, uint32(time.Now().Unix())) wan4 := n.wanIP4.As4() res = append(res, wan4[:]...) n.WriteUDPPacketNoNAT(UDPPacket{ Src: req.Dst, Dst: req.Src, Payload: res, }) return } // Map UDP request if len(req.Payload) == 12 && req.Payload[0] == 0 && req.Payload[1] == 1 { // https://www.rfc-editor.org/rfc/rfc6886#section-3.3 // "00 01 00 00 ed 40 00 00 00 00 1c 20" => // 00 ver // 01 op=map UDP // 00 00 reserved (0 in request; in response, this is the result code) // ed 40 internal port 60736 // 00 00 suggested external port // 00 00 1c 20 suggested lifetime in seconds (7200 sec = 2 hours) internalPort := binary.BigEndian.Uint16(req.Payload[4:6]) wantExtPort := binary.BigEndian.Uint16(req.Payload[6:8]) lifetimeSec := binary.BigEndian.Uint32(req.Payload[8:12]) gotPort, ok := n.doPortMap(req.Src.Addr(), internalPort, wantExtPort, int(lifetimeSec)) if !ok { n.logf("NAT-PMP map request for %v:%d failed", req.Src.Addr(), internalPort) return } res := make([]byte, 0, 16) res = append(res, 0, // version 0 (NAT-PMP) 1+128, // response to op 1 0, 0, // result code success ) res = binary.BigEndian.AppendUint32(res, uint32(time.Now().Unix())) res = binary.BigEndian.AppendUint16(res, internalPort) res = binary.BigEndian.AppendUint16(res, gotPort) res = binary.BigEndian.AppendUint32(res, lifetimeSec) n.WriteUDPPacketNoNAT(UDPPacket{ Src: req.Dst, Dst: req.Src, Payload: res, }) return } n.logf("TODO: handle NAT-PMP packet % 02x", req.Payload) } // UDPPacket is a UDP packet. // // For the purposes of this project, a UDP packet // (not a general IP packet) is the unit to be NAT'ed, // as that's all that Tailscale uses. type UDPPacket struct { Src netip.AddrPort Dst netip.AddrPort Payload []byte // everything after UDP header } func (s *Server) WriteStartingBanner(w io.Writer) { fmt.Fprintf(w, "vnet serving clients:\n") for _, n := range s.nodes { fmt.Fprintf(w, " %v %15v (%v, %v)\n", n.mac, n.lanIP, n.net.wanIP4, n.net.natStyle.Load()) } } type agentConn struct { node *node tc *gonet.TCPConn } func (s *Server) addIdleAgentConn(ac *agentConn) { //log.Printf("got agent conn from %v", ac.node.mac) s.mu.Lock() defer s.mu.Unlock() s.agentConns.Make() s.agentConns.Add(ac) if waiter, ok := s.agentConnWaiter[ac.node]; ok { select { case waiter <- struct{}{}: default: } } } func (s *Server) takeAgentConn(ctx context.Context, n *node) (_ *agentConn, ok bool) { const debug = false for { ac, ok := s.takeAgentConnOne(n) if ok { if debug { log.Printf("takeAgentConn: got agent conn for %v", n.mac) } return ac, true } s.mu.Lock() ready := make(chan struct{}) mak.Set(&s.agentConnWaiter, n, ready) s.mu.Unlock() if debug { log.Printf("takeAgentConn: waiting for agent conn for %v", n.mac) } select { case <-ctx.Done(): return nil, false case <-ready: case <-time.After(time.Second): // Try again regularly anyway, in case we have multiple clients // trying to hit the same node, or if a race means we weren't in the // select by the time addIdleAgentConn tried to signal us. } } } func (s *Server) takeAgentConnOne(n *node) (_ *agentConn, ok bool) { s.mu.Lock() defer s.mu.Unlock() miss := 0 for ac := range s.agentConns { if ac.node == n { s.agentConns.Delete(ac) return ac, true } miss++ } if miss > 0 { log.Printf("takeAgentConnOne: missed %d times for %v", miss, n.mac) } return nil, false } type NodeAgentClient struct { *tailscale.LocalClient HTTPClient *http.Client } func (s *Server) NodeAgentDialer(n *Node) DialFunc { s.mu.Lock() defer s.mu.Unlock() if d, ok := s.agentDialer[n.n]; ok { return d } d := func(ctx context.Context, network, addr string) (net.Conn, error) { ac, ok := s.takeAgentConn(ctx, n.n) if !ok { return nil, ctx.Err() } return ac.tc, nil } mak.Set(&s.agentDialer, n.n, d) return d } func (s *Server) NodeAgentClient(n *Node) *NodeAgentClient { d := s.NodeAgentDialer(n) return &NodeAgentClient{ LocalClient: &tailscale.LocalClient{ UseSocketOnly: true, OmitAuth: true, Dial: d, }, HTTPClient: &http.Client{ Transport: &http.Transport{ DialContext: d, }, }, } } // EnableHostFirewall enables the host's stateful firewall. func (c *NodeAgentClient) EnableHostFirewall(ctx context.Context) error { req, err := http.NewRequestWithContext(ctx, "GET", "http://unused/fw", nil) if err != nil { return err } res, err := c.HTTPClient.Do(req) if err != nil { return err } defer res.Body.Close() all, _ := io.ReadAll(res.Body) if res.StatusCode != 200 { return fmt.Errorf("unexpected status code %v: %s", res.Status, all) } return nil }