Sfoglia il codice sorgente

Immediate Lighthouse update after reconfig/reconnect (#1645)

John Maguire 6 mesi fa
parent
commit
e753e6e93c
3 ha cambiato i file con 102 aggiunte e 1 eliminazioni
  1. 75 0
      e2e/handshakes_test.go
  2. 10 0
      handshake_ix.go
  3. 17 1
      lighthouse.go

+ 75 - 0
e2e/handshakes_test.go

@@ -1369,6 +1369,81 @@ func TestV2NonPrimaryWithOffNetLighthouse(t *testing.T) {
 	theirControl.Stop()
 }
 
+func TestLighthouseUpdateOnReload(t *testing.T) {
+	ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
+
+	// Create the lighthouse
+	lhControl, lhVpnIpNet, lhUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "lh", "10.128.0.1/24", m{"lighthouse": m{"am_lighthouse": true}})
+
+	// Create a client with NO lighthouse configured and a long update interval.
+	// The initial SendUpdate at startup will be a no-op since no lighthouses are known.
+	myControl, myVpnIpNet, _, myConfig := newSimpleServer(cert.Version2, ca, caKey, "me", "10.128.0.2/24", m{
+		"lighthouse": m{
+			"interval": 600,
+			"local_allow_list": m{
+				"10.0.0.0/24": true,
+				"::/0":        false,
+			},
+		},
+	})
+
+	r := router.NewR(t, lhControl, myControl)
+	defer r.RenderFlow()
+
+	lhControl.Start()
+	myControl.Start()
+
+	// Drain any startup packets (there should be none meaningful)
+	r.FlushAll()
+
+	// Verify lighthouse has no knowledge of the client
+	assert.Nil(t, lhControl.QueryLighthouse(myVpnIpNet[0].Addr()))
+
+	// Build a new config that adds the lighthouse
+	newSettings := make(m)
+	for k, v := range myConfig.Settings {
+		newSettings[k] = v
+	}
+	newSettings["static_host_map"] = m{
+		lhVpnIpNet[0].Addr().String(): []any{lhUdpAddr.String()},
+	}
+	newSettings["lighthouse"] = m{
+		"hosts":    []any{lhVpnIpNet[0].Addr().String()},
+		"interval": 600,
+		"local_allow_list": m{
+			"10.0.0.0/24": true,
+			"::/0":        false,
+		},
+	}
+	newCfg, err := yaml.Marshal(newSettings)
+	require.NoError(t, err)
+
+	// Reload the config. The lighthouse.hosts change triggers TriggerUpdate,
+	// which wakes the update worker. It calls SendUpdate, initiating a
+	// handshake to the new lighthouse and caching the HostUpdateNotification.
+	require.NoError(t, myConfig.ReloadConfigString(string(newCfg)))
+
+	// Route until the lighthouse receives the HostUpdateNotification.
+	// This covers: handshake stage 1, stage 2, then the cached update.
+	done := make(chan struct{})
+	go func() {
+		r.RouteForAllUntilAfterMsgTypeTo(lhControl, header.LightHouse, 0)
+		close(done)
+	}()
+	select {
+	case <-done:
+	case <-time.After(5 * time.Second):
+		t.Fatal("timed out waiting for lighthouse update after config reload")
+	}
+
+	// Verify lighthouse now has the client's addresses
+	assert.NotNil(t, lhControl.QueryLighthouse(myVpnIpNet[0].Addr()))
+
+	r.RenderHostmaps("Final hostmaps", lhControl, myControl)
+	lhControl.Stop()
+	myControl.Stop()
+}
+
 func TestGoodHandshakeUnsafeDest(t *testing.T) {
 	unsafePrefix := "192.168.6.0/24"
 	ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})

+ 10 - 0
handshake_ix.go

@@ -462,6 +462,11 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
 
 	hostinfo.remotes.RefreshFromHandshake(vpnAddrs)
 
+	// Don't wait for UpdateWorker
+	if f.lightHouse.IsAnyLighthouseAddr(vpnAddrs) {
+		f.lightHouse.TriggerUpdate()
+	}
+
 	return
 }
 
@@ -674,5 +679,10 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
 	hostinfo.remotes.RefreshFromHandshake(vpnAddrs)
 	f.metricHandshakes.Update(duration)
 
+	// Don't wait for UpdateWorker
+	if f.lightHouse.IsAnyLighthouseAddr(vpnAddrs) {
+		f.lightHouse.TriggerUpdate()
+	}
+
 	return false
 }

+ 17 - 1
lighthouse.go

@@ -69,7 +69,8 @@ type LightHouse struct {
 	// Addr's of relays that can be used by peers to access me
 	relaysForMe atomic.Pointer[[]netip.Addr]
 
-	queryChan chan netip.Addr
+	updateTrigger chan struct{}
+	queryChan     chan netip.Addr
 
 	calculatedRemotes atomic.Pointer[bart.Table[[]*calculatedRemote]] // Maps VpnAddr to []*calculatedRemote
 
@@ -105,6 +106,7 @@ func NewLightHouseFromConfig(ctx context.Context, l *logrus.Logger, c *config.C,
 		nebulaPort:         nebulaPort,
 		punchConn:          pc,
 		punchy:             p,
+		updateTrigger:      make(chan struct{}, 1),
 		queryChan:          make(chan netip.Addr, c.GetUint32("handshakes.query_buffer", 64)),
 		l:                  l,
 	}
@@ -316,6 +318,7 @@ func (lh *LightHouse) reload(c *config.C, initial bool) error {
 		if !initial {
 			//NOTE: we are not tearing down existing lighthouse connections because they might be used for non lighthouse traffic
 			lh.l.Info("lighthouse.hosts has changed")
+			lh.TriggerUpdate()
 		}
 	}
 
@@ -841,11 +844,24 @@ func (lh *LightHouse) StartUpdateWorker() {
 				return
 			case <-clockSource.C:
 				continue
+			case <-lh.updateTrigger:
+				continue
 			}
 		}
 	}()
 }
 
+// TriggerUpdate requests an immediate lighthouse update. This is a non-blocking
+// operation intended to be called after a handshake completes with a lighthouse,
+// so the lighthouse has our current addresses without waiting for the next
+// periodic update.
+func (lh *LightHouse) TriggerUpdate() {
+	select {
+	case lh.updateTrigger <- struct{}{}:
+	default:
+	}
+}
+
 func (lh *LightHouse) SendUpdate() {
 	var v4 []*V4AddrPort
 	var v6 []*V6AddrPort