diff --git a/tstest/natlab/machost/README.md b/tstest/natlab/machost/README.md deleted file mode 100644 index f9f7b0bb8..000000000 --- a/tstest/natlab/machost/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# macOS VM's for tstest and natlab - -## Building - -``` -%make all -``` - -Will build both the TailMac and the VMHost app. You will need a developer account. The default bundle identifiers -default to tailscale owned ids, so if you don't have (or aren't using) a tailscale dev account, you will need to change this. -This should build automatically as long as you have a valid developer cert. Signing is automatic. The binaries both -require proper entitlements, so they do need to be signed. - -There are separate recipes in the makefile to rebuild the individual components if needed. - -All binaries are copied to the bin directory. - -You can generally do all interactions via the TailMac command line util. - -## Locations - -Everything is persisted at ~/VM.bundle - -Each vm gets it's own directory under there. - -RestoreImage.ipsw is used to build new VMs. You may replace this manually if you wish. - -Individual parameters for each instance are saved in a json config file (config.json) - -## Installing - -### Default a parameters - -The default virtio socket device port is 51009 -The default server socket for the virtual network device is /tmp/qemu.sock -The default memory size is 4Gb -The default mac address for the socket based network is 5a:94:ef:e4:0c:ee -The defualt mac address for normal ethernet is 5a:94:ef:e4:0c:ef - -All of these parameters are configurable. - -### Creating and managing VMs - -To create a new VM (this will grab a restore image if needed). Restore images are large. Installation takes a minute -``` -TailMac create --id my_vm_id -``` - -To delete a new VM -``` -TailMac delete --id my_vm_id -``` - -To refresh an existing restore image: -``` -TailMac refresh -``` - -To clone an existing vm (this will clone the mac and port as well) -``` -TailMac clone --id old_vm_id --target-id new_vm_id -``` - -To reconfigure a vm with a specific mac and a virtio socket device port: -``` -TailMac configure --id vm_id --mac 11:22:33:44:55:66 --port 12345 --ethermac 22:33:44:55:66:77 --mem 4000000000 --sock "/var/netdevice.sock" -``` - -## Running a VM - -MacHost is an app bundle, but the main binary behaves as a command line util. You can invoke it -thusly: - -``` -TailMac --id machine_1 - ``` - - You may invoke multiple vms, but the limit on the number of concurrent instances is on the order of 2. - - To stop a running VM (this is a fire and forget thing): - - ``` - TailMac stop --id machine_1 - ``` diff --git a/tstest/natlab/vnet/vnet.go b/tstest/natlab/vnet/vnet.go index 0205559c9..4569180bc 100644 --- a/tstest/natlab/vnet/vnet.go +++ b/tstest/natlab/vnet/vnet.go @@ -222,8 +222,8 @@ func (n *network) initStack() error { log.Printf("Serialize error: %v", err) continue } - if writeFunc, ok := n.writeFunc.Load(node.mac); ok { - writeFunc(buffer.Bytes()) + if nw, ok := n.writers.Load(node.mac); ok { + nw.write(buffer.Bytes()) } else { log.Printf("No writeFunc for %v", node.mac) } @@ -465,6 +465,20 @@ type portMapping struct { expiry time.Time } +type writerFunc func([]byte, *net.UnixAddr, int) + +// Encapsulates both a write function, an optional outbound socket address +// for dgram mode and an interfaceID for packet captures. +type networkWriter struct { + writer writerFunc // Function to write packets to the network + addr *net.UnixAddr // Outbound socket address for dgram mode + interfaceID int // The interface ID of the src node (for writing pcaps) +} + +func (nw *networkWriter) write(b []byte) { + nw.writer(b, nw.addr, nw.interfaceID) +} + type network struct { s *Server mac MAC @@ -485,16 +499,23 @@ type network struct { portMap map[netip.AddrPort]portMapping // WAN ip:port -> LAN ip:port portMapFlow map[portmapFlowKey]netip.AddrPort // (lanAP, peerWANAP) -> portmapped wanAP - // writeFunc is a map of MAC -> func to write to that MAC. + // writers is a map of MAC -> networkWriters to write packets to that MAC. // It contains entries for connected nodes only. - writeFunc syncs.Map[MAC, func([]byte)] // MAC -> func to write to that MAC -} - -func (n *network) registerWriter(mac MAC, f func([]byte)) { - if f != nil { - n.writeFunc.Store(mac, f) + writers syncs.Map[MAC, networkWriter] // MAC -> to networkWriter for that MAC +} + +// Regsiters a writerFunc for a MAC address. +// raddr is and optional outbound socket address of the client interface for dgram mode. +// Pass nil for the writerFunc to deregister the writer. +func (n *network) registerWriter(mac MAC, raddr *net.UnixAddr, interfaceID int, wf writerFunc) { + if wf != nil { + n.writers.Store(mac, networkWriter{ + writer: wf, + addr: raddr, + interfaceID: interfaceID, + }) } else { - n.writeFunc.Delete(mac) + n.writers.Delete(mac) } } @@ -684,10 +705,10 @@ type Protocol int const ( ProtocolQEMU = Protocol(iota + 1) - ProtocolUnixDGRAM // for macOS Hypervisor.Framework and VZFileHandleNetworkDeviceAttachment + ProtocolUnixDGRAM // for macOS Virtualization.Framework and VZFileHandleNetworkDeviceAttachment ) -// serveConn serves a single connection from a client. +// 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 @@ -702,24 +723,37 @@ func (s *Server) ServeUnixConn(uc *net.UnixConn, proto Protocol) { bw := bufio.NewWriterSize(uc, 2<<10) var writeMu sync.Mutex - var srcNode *node - writePkt := func(pkt []byte) { + writePkt := func(pkt []byte, raddr *net.UnixAddr, interfaceID int) { if pkt == nil { return } writeMu.Lock() defer writeMu.Unlock() - if proto == ProtocolQEMU { + switch proto { + case ProtocolQEMU: hdr := binary.BigEndian.AppendUint32(bw.AvailableBuffer()[:0], uint32(len(pkt))) if _, err := bw.Write(hdr); err != nil { log.Printf("Write hdr: %v", err) return } + + if _, err := bw.Write(pkt); err != nil { + log.Printf("Write pkt: %v", err) + return + + } + case ProtocolUnixDGRAM: + if raddr == nil { + log.Printf("Write pkt: dgram mode write failure, no outbound socket address") + return + } + + if _, err := uc.WriteToUnix(pkt, raddr); err != nil { + log.Printf("Write pkt : %v", err) + return + } } - if _, err := bw.Write(pkt); err != nil { - log.Printf("Write pkt: %v", err) - return - } + if err := bw.Flush(); err != nil { log.Printf("Flush: %v", err) } @@ -727,22 +761,25 @@ func (s *Server) ServeUnixConn(uc *net.UnixConn, proto Protocol) { Timestamp: time.Now(), CaptureLength: len(pkt), Length: len(pkt), - InterfaceIndex: srcNode.interfaceID, + InterfaceIndex: interfaceID, }, pkt)) } buf := make([]byte, 16<<10) - var netw *network // non-nil after first packet for { var packetRaw []byte - if proto == ProtocolUnixDGRAM { - n, _, err := uc.ReadFromUnix(buf) + 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] - } else if proto == ProtocolQEMU { + case ProtocolQEMU: if _, err := io.ReadFull(uc, buf[:4]); err != nil { if s.shutdownCtx.Err() != nil { // Return without logging. @@ -772,29 +809,29 @@ func (s *Server) ServeUnixConn(uc *net.UnixConn, proto Protocol) { ep := EthernetPacket{le, packet} srcMAC := ep.SrcMAC() - if srcNode == nil { - srcNode, ok = s.nodeByMAC[srcMAC] - if !ok { - log.Printf("[conn %p] ignoring frame from unknown MAC %v", uc, srcMAC) - continue - } - log.Printf("[conn %p] MAC %v is node %v", uc, srcMAC, srcNode.lanIP) - netw = srcNode.net - netw.registerWriter(srcMAC, writePkt) - defer netw.registerWriter(srcMAC, nil) - } else { - if srcMAC != srcNode.mac { - log.Printf("[conn %p] ignoring frame from MAC %v, expected %v", uc, srcMAC, srcNode.mac) - continue - } + srcNode, ok := s.nodeByMAC[srcMAC] + if !ok { + log.Printf("[conn %p] got frame from unknown MAC %v", uc, srcMAC) + continue } + + // Register a writer for the source MAC address if one doesn't exist. + if _, ok := srcNode.net.writers.Load(srcMAC); !ok { + log.Printf("[conn %p] Registering writer for MAC %v is node %v", uc, srcMAC, srcNode.lanIP) + srcNode.net.registerWriter(srcMAC, raddr, srcNode.interfaceID, writePkt) + defer func() { + srcNode.net.registerWriter(srcMAC, nil, 0, nil) + }() + continue + } + must.Do(s.pcapWriter.WritePacket(gopacket.CaptureInfo{ Timestamp: time.Now(), CaptureLength: len(packetRaw), Length: len(packetRaw), InterfaceIndex: srcNode.interfaceID, }, packetRaw)) - netw.HandleEthernetPacket(ep) + srcNode.net.HandleEthernetPacket(ep) } } @@ -839,8 +876,8 @@ func (n *network) writeEth(res []byte) { dstMAC := MAC(res[0:6]) srcMAC := MAC(res[6:12]) if dstMAC.IsBroadcast() { - n.writeFunc.Range(func(mac MAC, writeFunc func([]byte)) bool { - writeFunc(res) + n.writers.Range(func(mac MAC, nw networkWriter) bool { + nw.write(res) return true }) return @@ -849,8 +886,8 @@ func (n *network) writeEth(res []byte) { n.logf("dropping write of packet from %v to itself", srcMAC) return } - if writeFunc, ok := n.writeFunc.Load(dstMAC); ok { - writeFunc(res) + if nw, ok := n.writers.Load(dstMAC); ok { + nw.write(res) return } } diff --git a/tstest/tailmac/TailMac.xcodeproj/xcshareddata/xcschemes/host.xcscheme b/tstest/tailmac/TailMac.xcodeproj/xcshareddata/xcschemes/host.xcscheme index 92d659ac1..060f48e0d 100644 --- a/tstest/tailmac/TailMac.xcodeproj/xcshareddata/xcschemes/host.xcscheme +++ b/tstest/tailmac/TailMac.xcodeproj/xcshareddata/xcschemes/host.xcscheme @@ -15,7 +15,7 @@ @@ -45,7 +45,7 @@ @@ -62,7 +62,7 @@