Help us shape the future of Go! Take the 2026 Go Developer Survey

arfeed

package
v1.3.104 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: AGPL-3.0 Imports: 11 Imported by: 0

Documentation

Overview

Package arfeed pkg/deployment/ar/arfeed/arfeed.go c4-net-discovery

Wire shape of the address-resolver's CXO bindings feed — the tree layout, the record type and the codec, shared by the publisher (pkg/deployment/ar/api) and any reader. It depends on nothing but the VisorData type and the CXO frame helpers, so a visor can import it without dragging in the AR service's HTTP stack.

Why this tree is shaped the way it is

The feed exists to be read with CXO's Preview (pkg/cxo/node.Conn.Preview): fetch the publisher's latest Root over an already-open connection, walk ONLY the branches the lookup needs, and hold nothing afterwards. That pays off only if the tree is keyed by the thing being looked up. The dmsg-discovery clients-by-server feed is the counter-example — keyed by SERVER, so resolving one client means pulling that server's whole batch.

Three properties of treestore and registry.Refs decide the layout, and each one was measured rather than assumed:

  1. A level's children are individual objects, one network fetch each. So SCANNING a level of N children costs N round-trips.
  2. treestore encodes each level's children in sorted Name order (publisher.go sortedNames -> encodeNode), so a reader can address a child by INDEX and skip the scan entirely.
  3. Refs is a Merkle tree of degree 16. Addressing element i inside a level of more than 16 children walks the branch nodes preceding it, so a 256-wide level costs between 1 and ~16 EXTRA fetches depending on where the key lands. A level of at most 16 children is a single Refs node: one fetch, whatever the index.

Hence two levels of one hex character each, 16 wide apiece:

<pk-hex[2]>/<pk-hex[3]>    // FrameGzip(v1, JSON map pk-hex -> PeerBindings)

256 buckets, every one of them always published (empty ones included) so both levels stay dense and index-addressable. A lookup is then a FIXED handful of object fetches — schema registry, root TreeNode, root Refs node, level-1 entry, level-2 TreeNode, its Refs node, the bucket entry — at any network size and for any key. Widening to 4096 buckets later is one more level and three more fetches.

The offset matters

The bucket is named from hex[2:4], not hex[0:2]. A skywire public key is a COMPRESSED secp256k1 point, so its leading byte is always 0x02 or 0x03: bucketing on the first two hex characters puts the entire network into two buckets and silently turns each "one indexed fetch" into a whole-network download. Measured on the 1711 peers the production AR held on 2026-09-06: hex[0:2] gives 2 buckets of 886 and 825; hex[2:4] gives 255 occupied buckets with a maximum occupancy of 17. The offset skips the parity byte and starts at the first byte of the X coordinate, which is uniform.

Bucket occupancy is the tuning knob. At ~1.7k bound peers a bucket holds around seven records, a few hundred bytes gzipped, so a lookup transfers the target plus a handful of incidental neighbors. An order of magnitude more peers wants a third level — a wire-format change, which is what the leading version byte is for: an old reader rejects the frame cleanly and falls back to HTTP rather than misparsing it.

Package arfeed pkg/deployment/ar/arfeed/reach.go c4-net-discovery

Reachability: which visors can actually be transported, per transport type.

A binding at the address resolver says a visor LISTENS for a type. It does not say anyone can get through to it: a public visor can accept stcpr while its router drops inbound UDP, and a sudph binding outlives the UDP control connection the AR needs to make both sides punch at once. Autoconnect needs the difference, and it cannot learn it by waiting for a peer to succeed first — nothing would be tried.

The chicken-and-egg is broken per type by whatever can make the first attempt without a peer:

  • stcpr, squicr, swtr — a listener behind a router either admits inbound traffic or it does not, whoever sends it. The AR probes the bound address itself: a TCP connect for stcpr, and for the QUIC-based types one packet with an unknown QUIC version, which a QUIC listener always answers with a version negotiation. The result is the AR's, recorded as Open / Closed.
  • sudph — success is pairwise: it depends on the NAT behavior of BOTH sides, and it needs the receiver's live UDP control connection at the AR (Live) for the simultaneous open. Each visor measures its own NAT class with STUN (NAT); a pair is tried only when the two classes can punch.
  • webrtc — ICE, also pairwise; the same NAT-class rule applies.

Confirmations from real use sit on top: each visor states when it last ACCEPTED an inbound transport of each type (Inbound). A confirmation ranks a peer first; it never gates one, so nothing depends on a peer having succeeded before.

Index

Constants

View Source
const (
	TypeSTCPR  = "stcpr"
	TypeSUDPH  = "sudph"
	TypeQUIC   = "squicr"
	TypeWT     = "swtr"
	TypeWEBRTC = "webrtc"
)

Transport type names as they appear in a Reach record. They are the canonical wire strings of the types, so a record can be read without the transport package.

View Source
const (
	NATOpen           = "open"            // no NAT: public address on the interface
	NATFullCone       = "full-cone"       // any host may send once a mapping exists
	NATRestricted     = "restricted"      // only hosts the visor sent to (any port)
	NATPortRestricted = "port-restricted" // only the exact host:port the visor sent to
	NATSymmetric      = "symmetric"       // a new mapping per destination
	NATUDPFirewall    = "udp-firewall"    // public address, but filtered like port-restricted
	NATBlocked        = "blocked"         // no UDP at all
)

NAT classes, as measured by the visor's own STUN probe.

View Source
const AcceptsNone = "none"

AcceptsNone is the whole Accepts list of a visor taking no new automatic transports. Every type is then Unreachable to autoconnect, also on visors that predate it, so the visor also leaves Inbound out.

View Source
const BucketCount = 1 << (4 * Levels)

BucketCount is the number of buckets the publisher always materializes.

View Source
const Fanout = 16

Fanout is the number of children at each level — one hex character. It is deliberately not larger: registry.Refs has degree 16, so a level of at most 16 children is a single Refs node and an indexed fetch there costs one round-trip regardless of the index.

View Source
const InboundGranularity = time.Hour

InboundGranularity is the resolution Inbound times are published at. Coarse on purpose: a visor accepting transports all day must not rewrite its record every time it does.

View Source
const InboundRecent = 24 * time.Hour

InboundRecent is how long an accepted inbound transport counts as a confirmation.

View Source
const KeyOffset = 2

KeyOffset is the index of the first hex character of a peer's public key used to name its path. It is one byte in, not zero, because a compressed secp256k1 key's leading byte is always 0x02 or 0x03 — see the package doc.

View Source
const Levels = 2

Levels is the depth of the bucket tree. Each level is named by one hex character of the peer's public key.

View Source
const ReachBuckets = 16

ReachBuckets is the number of reach leaves: one per hex digit.

View Source
const ReachPathPrefix = "reach/"

ReachPathPrefix is the tree prefix of the reach feed. One leaf per bucket, reach/<h>, where <h> is the key's first hex digit after the 02/03 prefix.

View Source
const ReachVersion = 1

ReachVersion is the frame version of a reach bucket leaf.

View Source
const Version = 1

Version is the wire-format version byte of a bucket leaf body. Bump it on any breaking change to the bucket encoding OR to the tree layout; readers reject other values rather than misparsing them.

Variables

View Source
var ErrBadVersion = errors.New("arfeed: unsupported bucket frame version")

ErrBadVersion is returned by DecodeBucket for a frame whose version byte is not Version. Callers treat it as "I cannot read this", not as an error worth retrying.

Functions

func BucketPath

func BucketPath(pk cipher.PubKey) string

BucketPath returns the full tree path of pk's bucket, e.g. "a/3".

func BucketPathAt

func BucketPathAt(i int) string

BucketPathAt returns the path of the i'th bucket in the dense enumeration the publisher materializes, for 0 <= i < BucketCount.

func DecodeBucket

func DecodeBucket(blob []byte) (map[string]*PeerBindings, error)

DecodeBucket parses a bucket leaf body. An unknown version byte yields ErrBadVersion so a reader degrades to its fallback path instead of misparsing a newer wire shape.

func DecodeReachBucket added in v1.3.98

func DecodeReachBucket(blob []byte) (map[cipher.PubKey]*Reach, error)

DecodeReachBucket decodes one reach bucket leaf.

func EncodeBucket

func EncodeBucket(peers map[cipher.PubKey]*PeerBindings) ([]byte, error)

EncodeBucket serializes one bucket's peer records into a leaf body: a JSON object keyed by peer public key in sorted order (so unchanged content re-encodes to identical bytes), version-framed and gzipped.

func EncodeReachBucket added in v1.3.98

func EncodeReachBucket(peers map[cipher.PubKey]*Reach) ([]byte, error)

EncodeReachBucket encodes one reach bucket leaf. Keys are sorted so equal content encodes to equal bytes — the feed is content-addressed, and an unchanged bucket must cost nothing to republish.

func Indices

func Indices(pk cipher.PubKey) []int

Indices returns the per-level child POSITIONS of pk's bucket — what a reader hands to an indexed child fetch instead of scanning the level for a name. Valid only because the publisher keeps every level dense and treestore writes children in sorted name order. A malformed segment yields -1, which an indexed fetch treats as a miss.

func PunchCompatible added in v1.3.98

func PunchCompatible(a, b string) bool

PunchCompatible reports whether two NAT classes can hole-punch to each other with a simultaneous open. An empty class is unknown and assumed compatible; blocked is compatible with nothing.

A symmetric NAT picks a new external port per destination, so its peer cannot know where to send. That is fatal only when the peer also filters by port: another symmetric NAT, or a port-restricted one (a UDP firewall filters the same way). Open, full-cone and address-restricted peers admit it.

func ReachBucket added in v1.3.98

func ReachBucket(pk cipher.PubKey) int

ReachBucket returns the bucket index of a key: its first hex digit after the 02/03 compression prefix, which is uniformly distributed.

func ReachPath added in v1.3.98

func ReachPath(bucket int) string

ReachPath returns the tree path of a reach bucket.

func Segments

func Segments(pk cipher.PubKey) []string

Segments returns the per-level names of pk's bucket, outermost first.

Types

type PeerBindings

type PeerBindings struct {
	STCPR *addrresolver.VisorData `json:"stcpr,omitempty"`
	SUDPH *addrresolver.VisorData `json:"sudph,omitempty"`
	QUIC  *addrresolver.VisorData `json:"squicr,omitempty"`
	WT    *addrresolver.VisorData `json:"swtr,omitempty"`
}

PeerBindings is one peer's address-resolver record: whichever of the four bindable transport types the AR currently holds for it. Fields are pointers so an absent binding is absent from the JSON rather than an empty object, and the field order is fixed by the struct so a re-encode of unchanged content produces identical bytes — a wire no-op on CXO's content-addressed store.

func (*PeerBindings) Empty

func (b *PeerBindings) Empty() bool

Empty reports whether the record carries no binding at all, in which case the publisher drops the peer from its bucket.

func (*PeerBindings) Get

Get returns the binding for one transport type, or nil.

func (*PeerBindings) Set

Set installs the binding for one transport type; a nil value clears it. Reports whether the type was a bindable one.

type Reach added in v1.3.98

type Reach struct {
	ReachDecl
	// Open lists the types whose bound address answered the AR's probe.
	Open []string `json:"open,omitempty"`
	// Closed lists the types whose bound address did not.
	Closed []string `json:"closed,omitempty"`
	// Live is true while the visor holds a sudph UDP control connection at
	// the AR — without it the AR cannot ask it to punch.
	Live bool `json:"udp,omitempty"`
	// Bound lists the types the visor has a binding for at the AR.
	Bound []string `json:"bound,omitempty"`
}

Reach is one peer's record in the reach feed: the visor's own declaration plus what the AR itself observed.

func (*Reach) InboundWithin added in v1.3.98

func (r *Reach) InboundWithin(t string, now time.Time) bool

InboundWithin reports whether the peer accepted an inbound transport of the type within InboundRecent of now.

func (*Reach) Verdict added in v1.3.98

func (r *Reach) Verdict(t, selfNAT string, now time.Time) Verdict

Verdict says how likely a transport of type t from a visor with NAT class selfNAT to this peer is to work. r nil means the reach feed has no record for the peer at all.

type ReachDecl added in v1.3.98

type ReachDecl struct {
	// NAT is the visor's STUN-measured NAT class; empty when not measured.
	NAT string `json:"nat,omitempty"`
	// Accepts lists the types the visor serves inbound.
	Accepts []string `json:"acc,omitempty"`
	// Inbound maps a type to the time (unix seconds, rounded down to
	// InboundGranularity) the visor last accepted an inbound transport of it.
	Inbound map[string]int64 `json:"in,omitempty"`
}

ReachDecl is what a visor states about itself, published as the "reach" leaf of its AR-bind feed.

type Verdict added in v1.3.98

type Verdict int

Verdict is how likely a transport of one type to a peer is to work. Ordered: a higher verdict is tried first.

const (
	// Unreachable means the attempt is known to fail; it is not tried.
	Unreachable Verdict = iota
	// Untested means nothing is known either way — an old visor that states
	// nothing, or a probe that has not run yet. Tried within a small budget.
	Untested
	// Capable means everything measurable says it should work.
	Capable
	// Confirmed means it has worked: the AR's probe got through, or the peer
	// recently accepted an inbound transport of this type.
	Confirmed
)

func (Verdict) String added in v1.3.98

func (v Verdict) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL