Przeglądaj źródła

Make HostInfo.remote atomic to fix torn reads on the send path (#1773)

John Maguire 3 miesięcy temu
rodzic
commit
0a953915bb
7 zmienionych plików z 39 dodań i 28 usunięć
  1. 2 2
      control.go
  2. 8 6
      control_test.go
  3. 12 5
      hostmap.go
  4. 4 4
      inside.go
  5. 8 6
      outside.go
  6. 2 2
      punchy.go
  7. 3 3
      relay_manager.go

+ 2 - 2
control.go

@@ -305,7 +305,7 @@ func (c *Control) CloseAllTunnels(excludeLighthouses bool) (closed int) {
 
 		c.l.Debug("Sending close tunnel message",
 			"vpnAddrs", h.vpnAddrs,
-			"udpAddr", h.remote,
+			"udpAddr", h.GetRemote(),
 		)
 		closed++
 	}
@@ -350,7 +350,7 @@ func copyHostInfo(h *HostInfo, preferredRanges []netip.Prefix) ControlHostInfo {
 		RemoteAddrs:            h.remotes.CopyAddrs(preferredRanges),
 		CurrentRelaysToMe:      h.relayState.CopyRelayIps(),
 		CurrentRelaysThroughMe: h.relayState.CopyRelayForIps(),
-		CurrentRemote:          h.remote,
+		CurrentRemote:          h.GetRemote(),
 	}
 
 	for i, a := range h.vpnAddrs {

+ 8 - 6
control_test.go

@@ -42,8 +42,7 @@ func TestControl_GetHostInfoByVpnIp(t *testing.T) {
 	assert.True(t, ok)
 
 	crt := &dummyCert{}
-	hm.unlockedAddHostInfo(&HostInfo{
-		remote:  remote1,
+	hi := &HostInfo{
 		remotes: remotes,
 		ConnectionState: &ConnectionState{
 			peerCert: &cert.CachedCertificate{Certificate: crt},
@@ -56,13 +55,14 @@ func TestControl_GetHostInfoByVpnIp(t *testing.T) {
 			relayForByAddr: map[netip.Addr]*Relay{},
 			relayForByIdx:  map[uint32]*Relay{},
 		},
-	}, &Interface{})
+	}
+	hi.remote.Store(&remote1)
+	hm.unlockedAddHostInfo(hi, &Interface{})
 
 	vpnIp2, ok := netip.AddrFromSlice(ipNet2.IP)
 	assert.True(t, ok)
 
-	hm.unlockedAddHostInfo(&HostInfo{
-		remote:  remote1,
+	hi2 := &HostInfo{
 		remotes: remotes,
 		ConnectionState: &ConnectionState{
 			peerCert: nil,
@@ -75,7 +75,9 @@ func TestControl_GetHostInfoByVpnIp(t *testing.T) {
 			relayForByAddr: map[netip.Addr]*Relay{},
 			relayForByIdx:  map[uint32]*Relay{},
 		},
-	}, &Interface{})
+	}
+	hi2.remote.Store(&remote1)
+	hm.unlockedAddHostInfo(hi2, &Interface{})
 
 	c := Control{
 		state: StateReady,

+ 12 - 5
hostmap.go

@@ -229,7 +229,7 @@ const (
 )
 
 type HostInfo struct {
-	remote          netip.AddrPort
+	remote          atomic.Pointer[netip.AddrPort]
 	remotes         *RemoteList
 	promoteCounter  atomic.Uint32
 	ConnectionState *ConnectionState
@@ -684,7 +684,7 @@ func (hm *HostMap) ForEachIndex(f controlEach) {
 func (i *HostInfo) TryPromoteBest(preferredRanges []netip.Prefix, ifce *Interface) {
 	c := i.promoteCounter.Add(1)
 	if c%ifce.tryPromoteEvery.Load() == 0 {
-		remote := i.remote
+		remote := i.GetRemote()
 
 		// return early if we are already on a preferred remote
 		if remote.IsValid() {
@@ -726,11 +726,18 @@ func (i *HostInfo) GetCert() *cert.CachedCertificate {
 	return nil
 }
 
+func (i *HostInfo) GetRemote() netip.AddrPort {
+	if p := i.remote.Load(); p != nil {
+		return *p
+	}
+	return netip.AddrPort{}
+}
+
 // TODO: Maybe use ViaSender here?
 func (i *HostInfo) SetRemote(remote netip.AddrPort) {
 	// We copy here because we likely got this remote from a source that reuses the object
-	if i.remote != remote {
-		i.remote = remote
+	if i.GetRemote() != remote {
+		i.remote.Store(&remote)
 		i.remotes.LearnRemote(i.vpnAddrs[0], remote)
 	}
 }
@@ -742,7 +749,7 @@ func (i *HostInfo) SetRemoteIfPreferred(hm *HostMap, via ViaSender) bool {
 		return false
 	}
 
-	currentRemote := i.remote
+	currentRemote := i.GetRemote()
 	if !currentRemote.IsValid() {
 		i.SetRemote(via.UdpAddr)
 		return true

+ 4 - 4
inside.go

@@ -333,7 +333,7 @@ func (f *Interface) SendVia(via *HostInfo,
 		via.logger(f.l).Info("Failed to EncryptDanger in sendVia", "error", err)
 		return
 	}
-	err = f.writers[0].WriteTo(out, via.remote)
+	err = f.writers[0].WriteTo(out, via.GetRemote())
 	if err != nil {
 		via.logger(f.l).Info("Failed to WriteTo in sendVia", "error", err)
 	}
@@ -344,7 +344,7 @@ func (f *Interface) sendNoMetrics(t header.MessageType, st header.MessageSubType
 	if ci.eKey == nil {
 		return
 	}
-	useRelay := !remote.IsValid() && !hostinfo.remote.IsValid()
+	useRelay := !remote.IsValid() && !hostinfo.GetRemote().IsValid()
 	fullOut := out
 
 	if useRelay {
@@ -403,8 +403,8 @@ func (f *Interface) sendNoMetrics(t header.MessageType, st header.MessageSubType
 				"udpAddr", remote,
 			)
 		}
-	} else if hostinfo.remote.IsValid() {
-		err = f.writers[q].WriteTo(out, hostinfo.remote)
+	} else if hr := hostinfo.GetRemote(); hr.IsValid() {
+		err = f.writers[q].WriteTo(out, hr)
 		if err != nil {
 			hostinfo.logger(f.l).Error("Failed to write outgoing packet",
 				"error", err,

+ 8 - 6
outside.go

@@ -277,7 +277,8 @@ func (f *Interface) sendCloseTunnel(h *HostInfo) {
 }
 
 func (f *Interface) handleHostRoaming(hostinfo *HostInfo, via ViaSender) {
-	if !via.IsRelayed && hostinfo.remote != via.UdpAddr {
+	curRemote := hostinfo.GetRemote()
+	if !via.IsRelayed && curRemote != via.UdpAddr {
 		if !f.lightHouse.GetRemoteAllowList().AllowAll(hostinfo.vpnAddrs, via.UdpAddr.Addr()) {
 			if f.l.Enabled(context.Background(), slog.LevelDebug) {
 				hostinfo.logger(f.l).Debug("lighthouse.remote_allow_list denied roaming", "newAddr", via.UdpAddr)
@@ -289,7 +290,7 @@ func (f *Interface) handleHostRoaming(hostinfo *HostInfo, via ViaSender) {
 			if f.l.Enabled(context.Background(), slog.LevelDebug) {
 				hostinfo.logger(f.l).Debug("Suppressing roam back to previous remote",
 					"suppressSeconds", RoamingSuppressSeconds,
-					"udpAddr", hostinfo.remote,
+					"udpAddr", curRemote,
 					"newAddr", via.UdpAddr,
 				)
 			}
@@ -297,11 +298,11 @@ func (f *Interface) handleHostRoaming(hostinfo *HostInfo, via ViaSender) {
 		}
 
 		hostinfo.logger(f.l).Info("Host roamed to new udp ip/port.",
-			"udpAddr", hostinfo.remote,
+			"udpAddr", curRemote,
 			"newAddr", via.UdpAddr,
 		)
 		hostinfo.lastRoam = time.Now()
-		hostinfo.lastRoamRemote = hostinfo.remote
+		hostinfo.lastRoamRemote = curRemote
 		hostinfo.SetRemote(via.UdpAddr)
 	}
 
@@ -590,10 +591,11 @@ func (f *Interface) handleRecvError(addr netip.AddrPort, h *header.H) {
 		return
 	}
 
-	if hostinfo.remote.IsValid() && hostinfo.remote != addr {
+	hr := hostinfo.GetRemote()
+	if hr.IsValid() && hr != addr {
 		f.l.Info("Someone spoofing recv_errors?",
 			"addr", addr,
-			"hostinfoRemote", hostinfo.remote,
+			"hostinfoRemote", hr,
 		)
 		return
 	}

+ 2 - 2
punchy.go

@@ -174,9 +174,9 @@ func (p *Punchy) SendPunch(hostinfo *HostInfo) {
 
 	if p.punchEverything.Load() {
 		p.sendPunchToAllRemotes(hostinfo)
-	} else if hostinfo.remote.IsValid() {
+	} else if hr := hostinfo.GetRemote(); hr.IsValid() {
 		p.metricPunchyTx.Inc(1)
-		p.punchConn.WriteTo([]byte{1}, hostinfo.remote)
+		p.punchConn.WriteTo([]byte{1}, hr)
 	}
 }
 

+ 3 - 3
relay_manager.go

@@ -94,7 +94,7 @@ func (rm *relayManager) StartRelays(f *Interface, vpnIp netip.Addr, hh *Handshak
 		}
 
 		relayHostInfo := rm.hostmap.QueryVpnAddr(relay)
-		if relayHostInfo == nil || !relayHostInfo.remote.IsValid() {
+		if relayHostInfo == nil || !relayHostInfo.GetRemote().IsValid() {
 			hl.Log(context.Background(), level, "Establish tunnel to relay target", "relay", relay.String())
 			f.Handshake(relay)
 			continue
@@ -104,7 +104,7 @@ func (rm *relayManager) StartRelays(f *Interface, vpnIp netip.Addr, hh *Handshak
 		existingRelay, ok := relayHostInfo.relayState.QueryRelayForByIp(vpnIp)
 		if !ok {
 			// No relays exist or requested yet.
-			if relayHostInfo.remote.IsValid() {
+			if relayHostInfo.GetRemote().IsValid() {
 				idx, err := AddRelay(rm.l, relayHostInfo, rm.hostmap, vpnIp, nil, TerminalType, Requested)
 				if err != nil {
 					hl.Info("Failed to add relay to hostmap", "relay", relay.String(), "error", err)
@@ -508,7 +508,7 @@ func (rm *relayManager) handleCreateRelayRequest(v cert.Version, h *HostInfo, f
 			f.Handshake(target)
 			return
 		}
-		if !peer.remote.IsValid() {
+		if !peer.GetRemote().IsValid() {
 			// Only create relays to peers for whom I have a direct connection
 			return
 		}