Documentation
¶
Overview ¶
Package api pkg/deployment/ar/api/api.go c4-net-discovery
Package api pkg/deployment/ar/api/bind_ingest.go c4-net-discovery
AR-bind-over-CXO ingest: the server side of the fan-in path where visors publish their AR bindings as a CXO feed instead of re-registering over a fresh dmsg stream on a timer (each with a full Noise handshake — the secp256k1 handshakeResponder that dominates AR CPU). The CXO aggregator (pkg/deployment/ar/regcxo) calls IngestBindFromCXO for each bind leaf it replicates.
This is purely ADDITIVE and dual-write: the HTTP POST /bind and the SUDPH UDP registration remain the authoritative path and keep writing the same store. The CXO ingest therefore never clobbers a fresh HTTP/UDP bind:
- When a record already exists (the common case — HTTP/UDP bound first), the CXO ingest is a keepalive: it re-writes the STORED VisorData to refresh its TTL, off a warm CXO connection instead of a fresh handshake. It writes back exactly what is there, so it can never overwrite a newer HTTP/UDP address (mirrors dmsg-discovery's equal-sequence keepalive, which refreshes off the stored entry, not the incoming one).
- When no record exists yet, only the address-POST types (STCPR/QUIC/WT) take a fresh insert, reconstructed from the visor's DECLARED addresses exactly as the HTTP bind handler does in the production dmsg-routed case (where the observed source is the dmsg bridge and the handler falls back to the declared PublicIP). SUDPH is keepalive-only: its stored address is the UDP-observed NAT-mapped endpoint, which the declared payload cannot reproduce, so a fresh SUDPH record is left to the UDP path.
Package api pkg/deployment/ar/api/charts.go
Package api pkg/deployment/ar/api/cxo_publisher.go c4-net-discovery
CXO publisher for the address-resolver's bindings, keyed by peer public key.
Until now the AR published no CXO feed at all: visors push their bindings in over CXO (pkg/deployment/ar/regcxo) and nothing comes back, so the ~1.5k warm CXO connections the AR already holds carry traffic in one direction only. This adds the read side, in the shape a per-dial address lookup wants — see pkg/deployment/ar/arfeed for the tree layout and why it is 256 always-present buckets rather than a flat per-peer level or a per-server batch.
Purely additive: HTTP GET /resolve stays authoritative and unchanged, and the feed is inert until something subscribes to or previews it.
Where the data comes from ¶
Two sources, because neither alone is both fresh and correct:
- Every write to the store marks (peer, type) dirty. The worker resolves the STORED record for that pair on the next flush and re-encodes the peer's bucket. Resolving rather than trusting the value passed to Bind matters: the redis store merges IPv4/IPv6 families across two separate binds, so only the stored record is the whole truth.
- A periodic full resync rebuilds the state from the per-type index. This is what removes peers whose binding TTL'd out — an expiry never calls DelBind, so an incremental-only publisher would keep stale addresses forever.
Both run on one worker goroutine, so the state map needs no lock, and the store writes never block on the publisher: marks go through a buffered channel and are dropped (counted) on overflow. A dropped mark costs at most one resync interval of staleness for that peer.
Package api pkg/deployment/ar/api/dial_assist_log.go c4-net-discovery
Rate accounting for the SUDPH reverse dial-assist, whose "peer is not connected" outcome used to be logged once per attempt.
On a /resolve of type sudph the AR also nudges the RECEIVER to dial the sender, so both sides punch at once. That nudge needs a live UDP control connection from the receiver; when the receiver is offline (or its AR record is stale) there is none and askToDialUDP returns ErrNotConnected. The sender's /resolve response has already been written at that point — nothing is lost but the simultaneous-open optimisation, and the sender still dials. It is a routine outcome of a network where visors come and go.
Measured on the live deployment it was also 94,752 log lines in ten minutes — ~9,475/min against 2,450/min for the entire transport-discovery and 38/min for the route-finder. That is a real disk and IO cost, and it buried every other address-resolver diagnostic.
A per-peer rate limit was rejected: the cardinality IS the signal here (thousands of distinct offline peers), so per-peer state would grow with the fleet while still emitting a line per newly-seen peer. A plain demotion to Debug was rejected too: it makes the condition invisible at the level operators actually run, and the RATE is what tells them whether something is wrong (a jump from ~9k/min to ~90k/min matters; the individual line does not). Aggregation keeps the rate, the distinct-peer count and the worst offenders, at one line per window; the individual event stays available at Debug for anyone who raises the level.
Package api pkg/deployment/ar/api/reach.go c4-net-discovery
The reach book: per peer, what is known about whether it can actually be transported, published as the reach feed (see arfeed/reach.go for the record and why each type is judged the way it is).
Three inputs, all local to the AR:
- probes — every stcpr / squicr / swtr binding is dialed back from here when it appears or its address changes, and again every probeEvery.
- the sudph UDP control connection — up or down, as the UDP loop sees it.
- the visor's own declaration, the "reach" leaf of its AR-bind feed.
A peer's record is dropped once it has neither a binding nor a fresh declaration. Everything is in memory: after a restart the probes re-run as bindings are refreshed and every visor's feed is re-filled, so the book rebuilds itself within one keepalive.
Package api pkg/deployment/ar/api/udp_resolve.go
Index ¶
- Variables
- type API
- func (a *API) BackfillDHTMirror(ctx context.Context, log logrus.FieldLogger)
- func (a *API) Charts() *charts.Page
- func (a *API) ChartsPage(w http.ResponseWriter, r *http.Request)
- func (a *API) Close()
- func (a *API) IngestBindFromCXO(ctx context.Context, reporter cipher.PubKey, tpType types.Type, ...)
- func (a *API) IngestReachFromCXO(reporter cipher.PubKey, d arfeed.ReachDecl)
- func (a *API) ListenUDP(listener net.Listener)
- func (a *API) SetBindingsCXOPublisher(p *BindingsCXOPublisher)
- func (a *API) SetDHTMirrors(stcpr, sudph dhtMirror)
- func (a *API) StartCharts(ctx context.Context, st charts.Store, log logrus.FieldLogger)
- func (a *API) StartReachCXOPublisher(dmsgC *dmsg.Client, sk cipher.SecKey) (*ReachCXOPublisher, error)
- type ArData
- type BindingsCXOPublisher
- func (p *BindingsCXOPublisher) Close() error
- func (p *BindingsCXOPublisher) Dropped() uint64
- func (p *BindingsCXOPublisher) FeedPK() cipher.PubKey
- func (p *BindingsCXOPublisher) LastError() error
- func (p *BindingsCXOPublisher) MarkDirty(t types.Type, pk cipher.PubKey)
- func (x *BindingsCXOPublisher) Publisher() *treestore.Publisher
- type Error
- type HealthCheckResponse
- type ReachCXOPublisher
- type SudphSessionsReport
Constants ¶
This section is empty.
Variables ¶
var ErrMissingNetworkType = errors.New("missing network type in request")
ErrMissingNetworkType is returned when there is no type in request.
var ErrNotConnected = errors.New("peer is not connected")
ErrNotConnected is returned when requested peer is not connected.
var WhitelistPKs = httpauth.NewWhitelist()
WhitelistPKs holds the keys allowed to call the bulk deregistration endpoint.
Functions ¶
This section is empty.
Types ¶
type API ¶
type API struct {
// Stats, when set, adds the process and traffic charts to the status page.
Stats *charts.ServiceStats
http.Handler
DmsgServers []string
// contains filtered or unexported fields
}
API represents the api of the address-resolver service.
func New ¶
func New(log *logging.Logger, s store.Store, nonceStore httpauth.NonceStore, enableMetrics bool, m armetrics.Metrics, dmsgAddr, publicUDPAddr string) *API
New creates a new api. publicUDPAddr is the externally-reachable host:port of the AR's SUDPH UDP listener (e.g. "ar.example.com:30178"); pass empty when not configured — clients that reach the AR over dmsghttp will then have no way to register SUDPH.
func (*API) BackfillDHTMirror ¶
func (a *API) BackfillDHTMirror(ctx context.Context, log logrus.FieldLogger)
BackfillDHTMirror walks AR's existing STCPR and SUDPH bindings and republishes each one through the configured mirrors so the DHT view converges on whatever AR's redis store already holds, rather than only ever reflecting binds that arrive after startup.
Best-effort: a single Resolve failure logs and skips that PK; a GetAll failure logs and skips that whole transport type. We do not abort startup — AR continues serving HTTP either way, and live Bind/DelBind traffic will gradually rebuild the mirror anyway.
func (*API) ChartsPage ¶ added in v1.3.100
func (a *API) ChartsPage(w http.ResponseWriter, r *http.Request)
ChartsPage serves the charts page, or 404 when charts are not running.
func (*API) IngestBindFromCXO ¶
func (a *API) IngestBindFromCXO(ctx context.Context, reporter cipher.PubKey, tpType types.Type, la addrresolver.LocalAddresses)
IngestBindFromCXO applies an AR binding received over the AR-bind-over-CXO feed. reporter is the feed's publisher PK (the visor); the binding is always stored UNDER reporter, so a visor can only ever register its OWN address — the ownership check the HTTP path enforces via httpauth is structural here.
See the package doc for the dual-write / no-clobber semantics.
func (*API) IngestReachFromCXO ¶ added in v1.3.98
IngestReachFromCXO applies a visor's reach declaration from its AR-bind feed.
func (*API) SetBindingsCXOPublisher ¶
func (a *API) SetBindingsCXOPublisher(p *BindingsCXOPublisher)
SetBindingsCXOPublisher installs (or clears, with nil) the publisher that mirrors this AR's bindings onto its CXO feed. Safe to call while serving.
func (*API) SetDHTMirrors ¶
func (a *API) SetDHTMirrors(stcpr, sudph dhtMirror)
SetDHTMirrors wires per-transport DHT mirrors. Stcpr and sudph are independent — passing nil for either skips mirroring on that path. Should be called once at startup before the listeners are accepting traffic; concurrent calls are not supported.
func (*API) StartCharts ¶ added in v1.3.100
StartCharts samples the resolver's bindings and lookups into st until ctx ends and serves them as the charts page.
func (*API) StartReachCXOPublisher ¶ added in v1.3.98
func (a *API) StartReachCXOPublisher(dmsgC *dmsg.Client, sk cipher.SecKey) (*ReachCXOPublisher, error)
StartReachCXOPublisher publishes the reach book as the reach feed on DmsgARReachCXOPort.
type ArData ¶
type ArData struct {
Sudph []string `json:"sudph,omitempty"`
Stcpr []string `json:"stcpr,omitempty"`
}
ArData has all the visors that have registered with sudph or stcpr transport
type BindingsCXOPublisher ¶
type BindingsCXOPublisher struct {
// contains filtered or unexported fields
}
BindingsCXOPublisher mirrors the address-resolver's bindings onto a CXO TreeStore feed keyed by peer public key.
func StartBindingsCXOPublisher ¶
func StartBindingsCXOPublisher(dmsgC *dmsg.Client, sk cipher.SecKey, st store.Store, logger logrus.FieldLogger) (*BindingsCXOPublisher, error)
StartBindingsCXOPublisher constructs the publisher backed by the given dmsg client and the AR's secret key, and runs an immediate full resync so the feed is complete before anything reads it. The allowlist is open: GET /resolve is a public read, so its CXO mirror inherits that.
Returns nil plus an error if the underlying treestore publisher cannot be created; callers log and continue without it, HTTP staying the source of truth.
func (*BindingsCXOPublisher) Close ¶
func (p *BindingsCXOPublisher) Close() error
Close stops the worker and the underlying publisher. Safe to call twice.
func (*BindingsCXOPublisher) Dropped ¶
func (p *BindingsCXOPublisher) Dropped() uint64
Dropped returns the cumulative count of dropped dirty marks.
func (*BindingsCXOPublisher) FeedPK ¶
func (p *BindingsCXOPublisher) FeedPK() cipher.PubKey
FeedPK returns the publisher's feed PK (the AR's own PK).
func (*BindingsCXOPublisher) LastError ¶
func (p *BindingsCXOPublisher) LastError() error
LastError returns the most recent publish error, or nil.
func (*BindingsCXOPublisher) MarkDirty ¶
func (p *BindingsCXOPublisher) MarkDirty(t types.Type, pk cipher.PubKey)
MarkDirty records that a peer's binding for one transport type changed and must be re-read from the store. Non-blocking and safe from any goroutine: this runs on the HTTP / UDP / CXO-ingest write paths.
func (*BindingsCXOPublisher) Publisher ¶ added in v1.3.98
func (x *BindingsCXOPublisher) Publisher() *treestore.Publisher
Publisher returns the underlying feed publisher, for introspection.
type Error ¶
type Error struct {
Error string `json:"error"`
}
Error is the object returned to the client when there's an error.
type HealthCheckResponse ¶
type HealthCheckResponse = httputil.HealthCheckResponse
HealthCheckResponse is the /health body every service shares.
type ReachCXOPublisher ¶ added in v1.3.98
type ReachCXOPublisher struct {
// contains filtered or unexported fields
}
ReachCXOPublisher is the reach feed's publisher, as the host tracks it.
func (*ReachCXOPublisher) Close ¶ added in v1.3.98
func (p *ReachCXOPublisher) Close() error
Close detaches the feed from the book and closes it.
func (*ReachCXOPublisher) Publisher ¶ added in v1.3.98
func (p *ReachCXOPublisher) Publisher() *treestore.Publisher
Publisher returns the underlying feed publisher.
type SudphSessionsReport ¶ added in v1.3.100
type SudphSessionsReport struct {
Accepted int64 `json:"accepted"`
Closed int64 `json:"closed"`
// InMemory are accepted sessions not yet collected, split by whether
// this API closed them and whether its wrapper is still reachable.
InMemory int `json:"in_memory"`
InMemoryClosed int `json:"in_memory_closed"`
InMemoryWrapperUp int `json:"in_memory_wrapper_reachable"`
// KCP counters are process wide, so they include the visor's own sessions.
// ListenerSessions is the size of the KCP listener's own session map, and
// ClosedInListener how many sessions this API closed are still in it.
ListenerSessions int `json:"kcp_listener_sessions"`
ClosedInListener int `json:"closed_in_listener_map"`
KCPCurrEstab uint64 `json:"kcp_curr_estab"`
KCPPassiveOpens uint64 `json:"kcp_passive_opens"`
KCPActiveOpens uint64 `json:"kcp_active_opens"`
SudphConnsBound int `json:"sudph_conns_bound"`
// ClosedAddrs lists, on ?closed=N, up to N closed sessions still in
// memory by address, to look up in a heap dump.
ClosedAddrs []string `json:"closed_addrs,omitempty"`
}
SudphSessionsReport counts the SUDPH sessions still in memory.