This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+105 -35
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package magicsock
@@ -6,6 +6,7 @@ package magicsock
import (
"bufio"
"context"
"crypto/tls"
"fmt"
"maps"
"net"
@@ -44,12 +45,12 @@ const frameReceiveRecordRate = 5 * time.Second
// current connection for that regionID is dc. (but dc should not be
// used to write directly; it's owned by the read/write loops)
type derpRoute struct {
regionID int
regionID tailcfg.DERPRegionID
dc *derphttp.Client // don't use directly; see comment above
}
// removeDerpPeerRoute removes a DERP route entry previously added by addDerpPeerRoute.
func (c *Conn) removeDerpPeerRoute(peer key.NodePublic, regionID int, dc *derphttp.Client) {
func (c *Conn) removeDerpPeerRoute(peer key.NodePublic, regionID tailcfg.DERPRegionID, dc *derphttp.Client) {
c.mu.Lock()
defer c.mu.Unlock()
r2 := derpRoute{regionID, dc}
@@ -61,7 +62,7 @@ func (c *Conn) removeDerpPeerRoute(peer key.NodePublic, regionID int, dc *derpht
// addDerpPeerRoute adds a DERP route entry, noting that peer was seen
// on DERP node derpID, at least on the connection identified by dc.
// See issue 150 for details.
func (c *Conn) addDerpPeerRoute(peer key.NodePublic, regionID int, dc *derphttp.Client) {
func (c *Conn) addDerpPeerRoute(peer key.NodePublic, regionID tailcfg.DERPRegionID, dc *derphttp.Client) {
c.mu.Lock()
defer c.mu.Unlock()
mak.Set(&c.derpRoute, peer, derpRoute{regionID, dc})
@@ -78,7 +79,7 @@ func (c *Conn) addDerpPeerRoute(peer key.NodePublic, regionID int, dc *derphttp.
// first connection.
//
// This can also help nodes from a slow or misbehaving control plane.
func (c *Conn) fallbackDERPRegionForPeer(peer key.NodePublic) (regionID int) {
func (c *Conn) fallbackDERPRegionForPeer(peer key.NodePublic) (regionID tailcfg.DERPRegionID) {
c.mu.Lock()
defer c.mu.Unlock()
if dr, ok := c.derpRoute[peer]; ok {
@@ -100,7 +101,8 @@ type activeDerp struct {
}
var (
pickDERPFallbackForTests func() int
pickDERPFallbackForTests func() tailcfg.DERPRegionID
reSTUNHookForTests func(why string)
)
// pickDERPFallback returns a non-zero but deterministic DERP node to
@@ -109,7 +111,7 @@ var (
// checks aren't working).
//
// c.mu must NOT be held.
func (c *Conn) pickDERPFallback() int {
func (c *Conn) pickDERPFallback() tailcfg.DERPRegionID {
c.mu.Lock()
defer c.mu.Unlock()
@@ -154,7 +156,7 @@ var checkControlHealthDuringNearestDERPInTests = false
// region that it selected and set (via setNearestDERP).
//
// c.mu must NOT be held.
func (c *Conn) maybeSetNearestDERP(report *netcheck.Report) (preferredDERP int) {
func (c *Conn) maybeSetNearestDERP(report *netcheck.Report, force bool) (preferredDERP tailcfg.DERPRegionID) {
// Don't change our PreferredDERP if we don't have a connection to
// control; if we don't, then we can't inform peers about a DERP home
// change, which breaks all connectivity. Even if this DERP region is
@@ -168,7 +170,10 @@ func (c *Conn) maybeSetNearestDERP(report *netcheck.Report) (preferredDERP int)
//
// Despite the above behaviour, ensure that we set the nearest DERP if
// we don't currently have one set; any DERP server is better than
// none, even if not connected to control.
// none, even if not connected to control. The exception here is if we have
// a cached netmap with a previous DERP server. Retaining the previous DERP
// makes it easier for other nodes to find each other when control is not
// available.
var connectedToControl bool
if testenv.InTest() && !checkControlHealthDuringNearestDERPInTests {
connectedToControl = true
@@ -178,7 +183,7 @@ func (c *Conn) maybeSetNearestDERP(report *netcheck.Report) (preferredDERP int)
c.mu.Lock()
myDerp := c.myDerp
c.mu.Unlock()
if !connectedToControl {
if !connectedToControl && !force {
if myDerp != 0 {
metricDERPHomeNoChangeNoControl.Add(1)
return myDerp
@@ -197,16 +202,33 @@ func (c *Conn) maybeSetNearestDERP(report *netcheck.Report) (preferredDERP int)
}
if preferredDERP != myDerp {
c.logf(
"magicsock: home DERP changing from derp-%d [%dms] to derp-%d [%dms]",
c.myDerp, report.RegionLatency[myDerp].Milliseconds(), preferredDERP, report.RegionLatency[preferredDERP].Milliseconds())
"magicsock: home DERP changing from derp-%d [%dms] to derp-%d [%dms] (forced=%t)",
c.myDerp, report.RegionLatency[myDerp].Milliseconds(), preferredDERP, report.RegionLatency[preferredDERP].Milliseconds(), force)
}
if !c.setNearestDERP(preferredDERP) {
preferredDERP = 0
} else if preferredDERP != myDerp {
c.homeDERPChangedPub.Publish(HomeDERPChanged{Old: myDerp, New: preferredDERP})
}
return
}
func (c *Conn) derpRegionCodeLocked(regionID int) string {
// HomeDERPChanged is an event sent on the [eventbus.Bus] when a new home DERP
// server has been selected. Its publisher is [magicsock.Coon]; its main
// subscriber is [ipnlocal.LocalBackend] that updates the homeDERP used by the
// netmap cache.
// TODO(cmol): Move the subscriber to not inject into localBackend, but rather
// into the netmap at the controlClient mapSession level once there is a stable
// abstraction to use.
type HomeDERPChanged struct {
Old, New tailcfg.DERPRegionID
}
func (c *Conn) ForceSetNearestDERP(regionID tailcfg.DERPRegionID) tailcfg.DERPRegionID {
return c.maybeSetNearestDERP(&netcheck.Report{PreferredDERP: regionID}, true)
}
func (c *Conn) derpRegionCodeLocked(regionID tailcfg.DERPRegionID) string {
if c.derpMap == nil {
return ""
}
@@ -219,14 +241,14 @@ func (c *Conn) derpRegionCodeLocked(regionID int) string {
// setHomeDERPGaugeLocked updates the home DERP gauge metric.
//
// c.mu must be held.
func (c *Conn) setHomeDERPGaugeLocked(derpNum int) {
func (c *Conn) setHomeDERPGaugeLocked(derpNum tailcfg.DERPRegionID) {
if c.homeDERPGauge != nil {
c.homeDERPGauge.Set(float64(derpNum))
}
}
// c.mu must NOT be held.
func (c *Conn) setNearestDERP(derpNum int) (wantDERP bool) {
func (c *Conn) setNearestDERP(derpNum tailcfg.DERPRegionID) (wantDERP bool) {
c.mu.Lock()
defer c.mu.Unlock()
if !c.wantDerpLocked() {
@@ -284,7 +306,7 @@ func (c *Conn) startDerpHomeConnectLocked() {
// DERP region ID.
//
// c.mu may be held, but does not need to be.
func (c *Conn) goDerpConnect(regionID int) {
func (c *Conn) goDerpConnect(regionID tailcfg.DERPRegionID) {
if regionID == 0 {
return
}
@@ -314,7 +336,7 @@ const derpWriteQueueDepth = 32
//
// It returns nil if the network is down, the Conn is closed, or the regionID is
// not known.
func (c *Conn) derpWriteChanForRegion(regionID int, peer key.NodePublic) chan derpWriteRequest {
func (c *Conn) derpWriteChanForRegion(regionID tailcfg.DERPRegionID, peer key.NodePublic) chan derpWriteRequest {
if c.networkDown() {
return nil
}
@@ -368,8 +390,8 @@ func (c *Conn) derpWriteChanForRegion(regionID int, peer key.NodePublic) chan de
firstDerp := false
if c.activeDerp == nil {
firstDerp = true
c.activeDerp = make(map[int]activeDerp)
c.prevDerp = make(map[int]*syncs.WaitGroupChan)
c.activeDerp = make(map[tailcfg.DERPRegionID]activeDerp)
c.prevDerp = make(map[tailcfg.DERPRegionID]*syncs.WaitGroupChan)
}
// Note that derphttp.NewRegionClient does not dial the server
@@ -392,6 +414,10 @@ func (c *Conn) derpWriteChanForRegion(regionID int, peer key.NodePublic) chan de
return derpMap.Regions[regionID]
})
dc.HealthTracker = c.health
dc.AppName = c.derpAppName
if c.extraRootCAs != nil {
dc.TLSConfig = &tls.Config{RootCAs: c.extraRootCAs}
}
dc.SetCanAckPings(true)
dc.NotePreferred(c.myDerp == regionID)
@@ -436,7 +462,14 @@ func (c *Conn) derpWriteChanForRegion(regionID int, peer key.NodePublic) chan de
go c.runDerpReader(ctx, regionID, dc, wg, startGate)
go c.runDerpWriter(ctx, dc, ch, wg, startGate)
go c.derpActiveFunc()
go func() {
select {
case <-ctx.Done():
return
case <-startGate:
c.derpActiveFunc()
}
}()
return ad.writeCh
}
@@ -448,7 +481,7 @@ func (c *Conn) derpWriteChanForRegion(regionID int, peer key.NodePublic) chan de
// If there's any change, it logs.
//
// c.mu must be held.
func (c *Conn) setPeerLastDerpLocked(peer key.NodePublic, regionID, homeID int) {
func (c *Conn) setPeerLastDerpLocked(peer key.NodePublic, regionID, homeID tailcfg.DERPRegionID) {
if peer.IsZero() {
return
}
@@ -484,7 +517,7 @@ func (c *Conn) setPeerLastDerpLocked(peer key.NodePublic, regionID, homeID int)
// get at the packet contents they need to call copyBuf to copy it
// out, which also releases the buffer.
type derpReadResult struct {
regionID int
regionID tailcfg.DERPRegionID
n int // length of data received
src key.NodePublic
// copyBuf is called to copy the data to dst. It returns how
@@ -497,7 +530,7 @@ type derpReadResult struct {
// runDerpReader runs in a goroutine for the life of a DERP
// connection, handling received packets.
func (c *Conn) runDerpReader(ctx context.Context, regionID int, dc *derphttp.Client, wg *syncs.WaitGroupChan, startGate <-chan struct{}) {
func (c *Conn) runDerpReader(ctx context.Context, regionID tailcfg.DERPRegionID, dc *derphttp.Client, wg *syncs.WaitGroupChan, startGate <-chan struct{}) {
defer wg.Decr()
defer dc.Close()
@@ -675,7 +708,7 @@ func (c *Conn) runDerpWriter(ctx context.Context, dc *derphttp.Client, ch <-chan
}
}
func (c *connBind) receiveDERP(buffs [][]byte, sizes []int, eps []conn.Endpoint) (int, error) {
func (c *connBind) receiveDERP(slab []byte, packets []conn.ReceivedPacket) (int, error) {
if s := c.Conn.health.ReceiveFuncStats(health.ReceiveDERP); s != nil {
s.Enter()
defer s.Exit()
@@ -685,13 +718,14 @@ func (c *connBind) receiveDERP(buffs [][]byte, sizes []int, eps []conn.Endpoint)
if c.isClosed() {
break
}
n, ep := c.processDERPReadResult(dm, buffs[0])
n, ep := c.processDERPReadResult(dm, slab)
if n == 0 {
// No data read occurred. Wait for another packet.
continue
}
sizes[0] = n
eps[0] = ep
packets[0].Size = n
packets[0].Offset = 0
packets[0].Endpoint = ep
return 1, nil
}
return 0, net.ErrClosed
@@ -701,7 +735,7 @@ func (c *Conn) processDERPReadResult(dm derpReadResult, b []byte) (n int, ep *en
if dm.copyBuf == nil {
return 0, nil
}
var regionID int
var regionID tailcfg.DERPRegionID
n, regionID = dm.n, dm.regionID
ncopy := dm.copyBuf(b)
if ncopy != n {
@@ -718,6 +752,10 @@ func (c *Conn) processDERPReadResult(dm derpReadResult, b []byte) (n int, ep *en
return 0, nil
}
if c.onDERPRecv != nil && c.onDERPRecv(regionID, dm.src, b[:n]) {
return 0, nil
}
var ok bool
c.mu.Lock()
ep, ok = c.peerMap.endpointForNodeKey(dm.src)
@@ -738,6 +776,15 @@ func (c *Conn) processDERPReadResult(dm derpReadResult, b []byte) (n int, ep *en
return n, ep
}
// SendDERPPacketTo sends an arbitrary packet to the given node key via
// the DERP relay for the given region. It creates the DERP connection
// to the region if one doesn't already exist.
func (c *Conn) SendDERPPacketTo(dstKey key.NodePublic, regionID tailcfg.DERPRegionID, pkt []byte) (sent bool, err error) {
return c.sendAddr(
netip.AddrPortFrom(tailcfg.DerpMagicIPAddr, uint16(regionID)),
dstKey, pkt, false, false)
}
// SetOnlyTCP443 set whether the magicsock connection is restricted
// to only using TCP port 443 outbound. If true, no UDP is allowed,
// no STUN checks are performend, etc.
@@ -747,7 +794,24 @@ func (c *Conn) SetOnlyTCP443(v bool) {
// SetDERPMap controls which (if any) DERP servers are used.
// A nil value means to disable DERP; it's disabled by default.
//
// SetDERPMap triggers a ReSTUN after updating the map. Callers that want to
// set the map without triggering a ReSTUN should use [Conn.SetDERPMapWithoutReSTUN]
// instead.
func (c *Conn) SetDERPMap(dm *tailcfg.DERPMap) {
c.setDERPMap(dm, true)
}
// SetDERPMapWithoutReSTUN is like [Conn.SetDERPMap] but does not trigger a
// ReSTUN after updating the map.
//
// It is used for setting the map from a cache, so the homeDERP can be set
// from cache before any STUN happens.
func (c *Conn) SetDERPMapWithoutReSTUN(dm *tailcfg.DERPMap) {
c.setDERPMap(dm, false)
}
func (c *Conn) setDERPMap(dm *tailcfg.DERPMap, doReStun bool) {
c.mu.Lock()
defer c.mu.Unlock()
@@ -760,7 +824,7 @@ func (c *Conn) SetDERPMap(dm *tailcfg.DERPMap) {
}
dm = &tailcfg.DERPMap{
OmitDefaultRegions: true,
Regions: map[int]*tailcfg.DERPRegion{
Regions: map[tailcfg.DERPRegionID]*tailcfg.DERPRegion{
999: {
RegionID: 999,
Nodes: []*tailcfg.DERPNode{{
@@ -804,8 +868,14 @@ func (c *Conn) SetDERPMap(dm *tailcfg.DERPMap) {
}
}
go c.ReSTUN("derp-map-update")
if doReStun {
if reSTUNHookForTests != nil {
reSTUNHookForTests("derp-map-update")
}
go c.ReSTUN("derp-map-update")
}
}
func (c *Conn) wantDerpLocked() bool { return c.derpMap != nil }
// c.mu must be held.
@@ -871,7 +941,7 @@ func (c *Conn) maybeCloseDERPsOnRebind(okayLocalIPs []netip.Prefix) {
// why is a reason for logging.
//
// c.mu must be held.
func (c *Conn) closeOrReconnectDERPLocked(regionID int, why string) {
func (c *Conn) closeOrReconnectDERPLocked(regionID tailcfg.DERPRegionID, why string) {
c.closeDerpLocked(regionID, why)
if !c.privateKey.IsZero() && c.myDerp == regionID {
c.startDerpHomeConnectLocked()
@@ -880,7 +950,7 @@ func (c *Conn) closeOrReconnectDERPLocked(regionID int, why string) {
// c.mu must be held.
// It is the responsibility of the caller to call logActiveDerpLocked after any set of closes.
func (c *Conn) closeDerpLocked(regionID int, why string) {
func (c *Conn) closeDerpLocked(regionID tailcfg.DERPRegionID, why string) {
if ad, ok := c.activeDerp[regionID]; ok {
c.logf("magicsock: closing connection to derp-%v (%v), age %v", regionID, why, time.Since(ad.createTime).Round(time.Second))
go ad.c.Close()
@@ -898,14 +968,14 @@ func (c *Conn) logActiveDerpLocked() {
return
}
buf.WriteString(":")
c.foreachActiveDerpSortedLocked(func(node int, ad activeDerp) {
c.foreachActiveDerpSortedLocked(func(node tailcfg.DERPRegionID, ad activeDerp) {
fmt.Fprintf(buf, " derp-%d=cr%v,wr%v", node, simpleDur(now.Sub(ad.createTime)), simpleDur(now.Sub(*ad.lastWrite)))
})
}))
}
// c.mu must be held.
func (c *Conn) foreachActiveDerpSortedLocked(fn func(regionID int, ad activeDerp)) {
func (c *Conn) foreachActiveDerpSortedLocked(fn func(regionID tailcfg.DERPRegionID, ad activeDerp)) {
if len(c.activeDerp) < 2 {
for id, ad := range c.activeDerp {
fn(id, ad)
@@ -971,7 +1041,7 @@ func (c *Conn) DERPs() int {
return len(c.activeDerp)
}
func (c *Conn) derpRegionCodeOfIDLocked(regionID int) string {
func (c *Conn) derpRegionCodeOfIDLocked(regionID tailcfg.DERPRegionID) string {
if c.derpMap == nil {
return ""
}