Files
ziti/controller/handler_mgmt/bind.go
Paul Lorenz 030e0483e2 Auto-GC stale router links. Fixes #4005
- adds a structured xlink.LinkKey ({DialerBinding, Protocol, DestId,
  ListenerBinding}) used by all staleness and GC checks, replacing the
  pre-rendered key string each side used to pass around
- adds the CheckStaleLinks ctrl message and the `ziti ops verify stale-links`
  CLI for operator-driven, two-sided staleness verification with optional --gc,
  which acts only when both endpoints agree
- implements auto-GC via the router.link.v1 gcMode (preserve/orphaned/changed):
  the router walks its xlinks after each Apply that mutates listeners, dialers,
  or gcMode and closes one-sided-stale entries under the configured mode
- reports staleness as a three-state verdict (unknown/notStale/stale) so a side
  that cannot judge a link abstains instead of guessing; a destination absent
  from the registry's listener snapshot means "listeners unknown", not "peer has
  none", which keeps a briefly unhealthy peer from losing its live links
- rebuilds the link surface only when a listener or dialer definition actually
  moves, so a gcMode-only change no longer rebinds every listen socket
- gates CheckStaleLinks on a new RouterStaleLinkCheck capability and explains
  each unqueryable endpoint, so a partial verdict distinguishes an offline peer
  from one too old to answer
- detects a peer re-advertising the address a link was dialed to from the
  dialing side, not just from the peer's own listener side
- pins every verdict to the link iteration it was computed against, faults that
  iteration rather than the current one, and takes the conditional removal's
  result as the decision, so a link re-dialed while the sweep was running is
  neither closed nor reported as collected
- returns whether LinkManager.Remove actually removed, rather than discarding
  the link table's iteration guard
- fails the CLI when the result stream ends before every link is reported, and
  drains results already buffered before declaring any missing, so neither a
  truncated run nor a random select outcome is read as a clean sweep
- rejects an unrecognized match mode rather than resolving it to changed, the
  broadest removal criterion

For #3743.
2026-08-31 22:10:09 -04:00

130 lines
5.0 KiB
Go

/*
Copyright NetFoundry Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package handler_mgmt
import (
"github.com/openziti/channel/v5"
"github.com/openziti/foundation/v2/concurrenz"
"github.com/openziti/ziti/v2/common/trace"
"github.com/openziti/ziti/v2/controller/env"
"github.com/openziti/ziti/v2/controller/network"
"github.com/openziti/ziti/v2/controller/xmgmt"
)
type BindHandler struct {
env *env.AppEnv
network *network.Network
xmgmts *concurrenz.CopyOnWriteSlice[xmgmt.Xmgmt]
}
func NewBindHandler(env *env.AppEnv, network *network.Network, xmgmts *concurrenz.CopyOnWriteSlice[xmgmt.Xmgmt]) channel.BindHandler {
return &BindHandler{
env: env,
network: network,
xmgmts: xmgmts,
}
}
func (bindHandler *BindHandler) BindChannel(binding channel.Binding) error {
inspectRequestHandler := newInspectHandler(bindHandler.network)
channel.AddReceiveHandlers(binding, &channel.AsyncFunctionReceiveAdapter{
Type: inspectRequestHandler.ContentType(),
Handler: inspectRequestHandler.HandleReceive,
})
validateCircuitsRequestHandler := newValidateCircuitsHandler(bindHandler.env)
channel.AddReceiveHandlers(binding, &channel.AsyncFunctionReceiveAdapter{
Type: validateCircuitsRequestHandler.ContentType(),
Handler: validateCircuitsRequestHandler.HandleReceive,
})
validateTerminatorsRequestHandler := newValidateTerminatorsHandler(bindHandler.network)
channel.AddReceiveHandlers(binding, &channel.AsyncFunctionReceiveAdapter{
Type: validateTerminatorsRequestHandler.ContentType(),
Handler: validateTerminatorsRequestHandler.HandleReceive,
})
validateLinksRequestHandler := newValidateRouterLinksHandler(bindHandler.network)
channel.AddReceiveHandlers(binding, &channel.AsyncFunctionReceiveAdapter{
Type: validateLinksRequestHandler.ContentType(),
Handler: validateLinksRequestHandler.HandleReceive,
})
validateStaleLinksRequestHandler := newValidateStaleLinksHandler(bindHandler.network)
channel.AddReceiveHandlers(binding, &channel.AsyncFunctionReceiveAdapter{
Type: validateStaleLinksRequestHandler.ContentType(),
Handler: validateStaleLinksRequestHandler.HandleReceive,
})
validateSdkTerminatorsRequestHandler := newValidateRouterSdkTerminatorsHandler(bindHandler.network)
channel.AddReceiveHandlers(binding, &channel.AsyncFunctionReceiveAdapter{
Type: validateSdkTerminatorsRequestHandler.ContentType(),
Handler: validateSdkTerminatorsRequestHandler.HandleReceive,
})
validateIdentityConnectionStatusesRequestHandler := newValidateIdentityConnectionStatusesHandler(bindHandler.env)
channel.AddReceiveHandlers(binding, &channel.AsyncFunctionReceiveAdapter{
Type: validateIdentityConnectionStatusesRequestHandler.ContentType(),
Handler: validateIdentityConnectionStatusesRequestHandler.HandleReceive,
})
validateRouterDataModelRequestHandler := newValidateRouterDataModelHandler(bindHandler.env)
channel.AddReceiveHandlers(binding, &channel.AsyncFunctionReceiveAdapter{
Type: validateRouterDataModelRequestHandler.ContentType(),
Handler: validateRouterDataModelRequestHandler.HandleReceive,
})
validateErtTerminatorsRequestHandler := newValidateRouterErtTerminatorsHandler(bindHandler.network)
channel.AddReceiveHandlers(binding, &channel.AsyncFunctionReceiveAdapter{
Type: validateErtTerminatorsRequestHandler.ContentType(),
Handler: validateErtTerminatorsRequestHandler.HandleReceive,
})
validateControllerDialersRequestHandler := newValidateControllerDialersHandler(bindHandler.network)
channel.AddReceiveHandlers(binding, &channel.AsyncFunctionReceiveAdapter{
Type: validateControllerDialersRequestHandler.ContentType(),
Handler: validateControllerDialersRequestHandler.HandleReceive,
})
tracesHandler := newStreamTracesHandler(bindHandler.network)
channel.AddReceiveHandlers(binding, tracesHandler)
binding.AddCloseHandler(tracesHandler)
eventsHandler := newStreamEventsHandler(bindHandler.network)
channel.AddReceiveHandlers(binding, eventsHandler)
binding.AddCloseHandler(eventsHandler)
channel.AddReceiveHandlers(binding, newTogglePipeTracesHandler(bindHandler.network))
binding.AddPeekHandler(trace.NewChannelPeekHandler(bindHandler.network.GetAppId(), binding.GetChannel(), bindHandler.network.GetTraceController()))
xmgmtDone := make(chan struct{})
for _, x := range bindHandler.xmgmts.Value() {
if err := x.BindChannel(binding); err != nil {
return err
}
if err := x.Run(binding.GetChannel(), xmgmtDone); err != nil {
return err
}
}
if len(bindHandler.xmgmts.Value()) > 0 {
binding.AddCloseHandler(newXmgmtCloseHandler(xmgmtDone))
}
return nil
}