diff --git a/disco/pcap.go b/disco/pcap.go new file mode 100644 index 000000000..710354248 --- /dev/null +++ b/disco/pcap.go @@ -0,0 +1,40 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package disco + +import ( + "bytes" + "encoding/binary" + "net/netip" + + "tailscale.com/tailcfg" + "tailscale.com/types/key" +) + +// ToPCAPFrame marshals the bytes for a pcap record that describe a disco frame. +// +// Warning: Alloc garbage. Acceptable while capturing. +func ToPCAPFrame(src netip.AddrPort, derpNodeSrc key.NodePublic, payload []byte) []byte { + var ( + b bytes.Buffer + flag uint8 + ) + b.Grow(128) // Most disco frames will probably be smaller than this. + + if src.Addr() == tailcfg.DerpMagicIPAddr { + flag |= 0x01 + } + b.WriteByte(flag) // 1b: flag + + derpSrc := derpNodeSrc.Raw32() + b.Write(derpSrc[:]) // 32b: derp public key + binary.Write(&b, binary.LittleEndian, uint16(src.Port())) // 2b: port + addr, _ := src.Addr().MarshalBinary() + binary.Write(&b, binary.LittleEndian, uint16(len(addr))) // 2b: len(addr) + b.Write(addr) // Xb: addr + binary.Write(&b, binary.LittleEndian, uint16(len(payload))) // 2b: len(payload) + b.Write(payload) // Xb: payload + + return b.Bytes() +} diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index b69f77f31..505ccc133 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -2351,6 +2351,8 @@ type PeerChange struct { // Mnemonic: 3.3.40 are numbers above the keys D, E, R, P. const DerpMagicIP = "127.3.3.40" +var DerpMagicIPAddr = netip.MustParseAddr(DerpMagicIP) + // EarlyNoise is the early payload that's sent over Noise but before the HTTP/2 // handshake when connecting to the coordination server. // diff --git a/wgengine/magicsock/endpoint.go b/wgengine/magicsock/endpoint.go index 2cbe4438e..374e430c1 100644 --- a/wgengine/magicsock/endpoint.go +++ b/wgengine/magicsock/endpoint.go @@ -858,7 +858,7 @@ func (de *endpoint) handlePongConnLocked(m *disco.Pong, di *discoInfo, src netip de.mu.Lock() defer de.mu.Unlock() - isDerp := src.Addr() == derpMagicIPAddr + isDerp := src.Addr() == tailcfg.DerpMagicIPAddr sp, ok := de.sentPing[m.TxID] if !ok { diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index c44f97d76..275345e11 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -7,10 +7,8 @@ package magicsock import ( "bufio" - "bytes" "context" crand "crypto/rand" - "encoding/binary" "errors" "fmt" "hash/fnv" @@ -357,8 +355,6 @@ func (c *Conn) addDerpPeerRoute(peer key.NodePublic, derpID int, dc *derphttp.Cl mak.Set(&c.derpRoute, peer, derpRoute{derpID, dc}) } -var derpMagicIPAddr = netip.MustParseAddr(tailcfg.DerpMagicIP) - // activeDerp contains fields for an active DERP connection. type activeDerp struct { c *derphttp.Client @@ -860,7 +856,7 @@ func (c *Conn) Ping(peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnst // c.mu must be held func (c *Conn) populateCLIPingResponseLocked(res *ipnstate.PingResult, latency time.Duration, ep netip.AddrPort) { res.LatencySeconds = latency.Seconds() - if ep.Addr() != derpMagicIPAddr { + if ep.Addr() != tailcfg.DerpMagicIPAddr { res.Endpoint = ep.String() return } @@ -958,7 +954,7 @@ func (c *Conn) goDerpConnect(node int) { if node == 0 { return } - go c.derpWriteChanOfAddr(netip.AddrPortFrom(derpMagicIPAddr, uint16(node)), key.NodePublic{}) + go c.derpWriteChanOfAddr(netip.AddrPortFrom(tailcfg.DerpMagicIPAddr, uint16(node)), key.NodePublic{}) } // determineEndpoints returns the machine's endpoint addresses. It @@ -1232,7 +1228,7 @@ func (c *Conn) sendUDPStd(addr netip.AddrPort, b []byte) (sent bool, err error) // IPv6 address when the local machine doesn't have IPv6 support // returns (false, nil); it's not an error, but nothing was sent. func (c *Conn) sendAddr(addr netip.AddrPort, pubKey key.NodePublic, b []byte) (sent bool, err error) { - if addr.Addr() != derpMagicIPAddr { + if addr.Addr() != tailcfg.DerpMagicIPAddr { return c.sendUDP(addr, b) } @@ -1325,7 +1321,7 @@ func bufferedDerpWritesBeforeDrop() int { // If peer is non-zero, it can be used to find an active reverse // path, without using addr. func (c *Conn) derpWriteChanOfAddr(addr netip.AddrPort, peer key.NodePublic) chan<- derpWriteRequest { - if addr.Addr() != derpMagicIPAddr { + if addr.Addr() != tailcfg.DerpMagicIPAddr { return nil } regionID := int(addr.Port()) @@ -1839,7 +1835,7 @@ func (c *Conn) processDERPReadResult(dm derpReadResult, b []byte) (n int, ep *en return 0, nil } - ipp := netip.AddrPortFrom(derpMagicIPAddr, uint16(regionID)) + ipp := netip.AddrPortFrom(tailcfg.DerpMagicIPAddr, uint16(regionID)) if c.handleDiscoMessage(b[:n], ipp, dm.src, discoRXPathDERP) { return 0, nil } @@ -1886,7 +1882,7 @@ var debugIPv4DiscoPingPenalty = envknob.RegisterDuration("TS_DISCO_PONG_IPV4_DEL // The dstKey should only be non-zero if the dstDisco key // unambiguously maps to exactly one peer. func (c *Conn) sendDiscoMessage(dst netip.AddrPort, dstKey key.NodePublic, dstDisco key.DiscoPublic, m disco.Message, logLevel discoLogLevel) (sent bool, err error) { - isDERP := dst.Addr() == derpMagicIPAddr + isDERP := dst.Addr() == tailcfg.DerpMagicIPAddr if _, isPong := m.(*disco.Pong); isPong && !isDERP && dst.Addr().Is4() { time.Sleep(debugIPv4DiscoPingPenalty()) } @@ -1946,34 +1942,6 @@ func (c *Conn) sendDiscoMessage(dst netip.AddrPort, dstKey key.NodePublic, dstDi return sent, err } -// discoPcapFrame marshals the bytes for a pcap record that describe a -// disco frame. -// -// Warning: Alloc garbage. Acceptable while capturing. -func discoPcapFrame(src netip.AddrPort, derpNodeSrc key.NodePublic, payload []byte) []byte { - var ( - b bytes.Buffer - flag uint8 - ) - b.Grow(128) // Most disco frames will probably be smaller than this. - - if src.Addr() == derpMagicIPAddr { - flag |= 0x01 - } - b.WriteByte(flag) // 1b: flag - - derpSrc := derpNodeSrc.Raw32() - b.Write(derpSrc[:]) // 32b: derp public key - binary.Write(&b, binary.LittleEndian, uint16(src.Port())) // 2b: port - addr, _ := src.Addr().MarshalBinary() - binary.Write(&b, binary.LittleEndian, uint16(len(addr))) // 2b: len(addr) - b.Write(addr) // Xb: addr - binary.Write(&b, binary.LittleEndian, uint16(len(payload))) // 2b: len(payload) - b.Write(payload) // Xb: payload - - return b.Bytes() -} - type discoRXPath string const ( @@ -2063,7 +2031,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke // Emit information about the disco frame into the pcap stream // if a capture hook is installed. if cb := c.captureHook.Load(); cb != nil { - cb(capture.PathDisco, time.Now(), discoPcapFrame(src, derpNodeSrc, payload), packet.CaptureMeta{}) + cb(capture.PathDisco, time.Now(), disco.ToPCAPFrame(src, derpNodeSrc, payload), packet.CaptureMeta{}) } dm, err := disco.Parse(payload) @@ -2080,7 +2048,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke return } - isDERP := src.Addr() == derpMagicIPAddr + isDERP := src.Addr() == tailcfg.DerpMagicIPAddr if isDERP { metricRecvDiscoDERP.Add(1) } else { @@ -2178,7 +2146,7 @@ func (c *Conn) handlePingLocked(dm *disco.Ping, src netip.AddrPort, di *discoInf likelyHeartBeat := src == di.lastPingFrom && time.Since(di.lastPingTime) < 5*time.Second di.lastPingFrom = src di.lastPingTime = time.Now() - isDerp := src.Addr() == derpMagicIPAddr + isDerp := src.Addr() == tailcfg.DerpMagicIPAddr // If we can figure out with certainty which node key this disco // message is for, eagerly update our IP<>node and disco<>node