natsense

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jul 13, 2026 License: MPL-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package natsense implements observed address consensus (spec Phase 2a: ≥3 distinct reporters). For tests or small networks, set MinAgreeingPeers to 1.

Index

Constants

View Source
const (
	NATUnknown      uint8 = 0
	NATFullCone     uint8 = 1
	NATRestricted   uint8 = 2
	NATPortRestrict uint8 = 3
	NATSymmetric    uint8 = 4
)

NAT type constants (mirror protocol.NAT* for convenience).

Variables

This section is empty.

Functions

This section is empty.

Types

type Sense

type Sense struct {
	// contains filtered or unexported fields
}

Sense tracks distinct peers that reported the same reflected UDP endpoint (wire bytes) and infers NAT type from both passive mapping observation and active probe results.

v4 and v6 state are maintained independently. InferNATType covers v4 only; use InferV6Reach for v6 reachability classification.

func NewSense

func NewSense(minAgreeingPeers int) *Sense

NewSense returns a consensus tracker. If minAgreeingPeers <= 0, default 3 is used.

func (*Sense) ClearMappingProbeV4 added in v0.2.9

func (s *Sense) ClearMappingProbeV4()

ClearMappingProbeV4 discards the cached v4 mapping probe result.

func (*Sense) ClearProbeResult added in v0.1.4

func (s *Sense) ClearProbeResult()

ClearProbeResult discards cached active-probe classification so the next probe result is used immediately after network changes. Deprecated: use ClearV4ProbeResult / ClearV6ProbeResult as needed.

func (*Sense) ClearV4ProbeResult added in v0.2.6

func (s *Sense) ClearV4ProbeResult()

ClearV4ProbeResult discards the cached v4 probe result.

func (*Sense) ClearV6ProbeResult added in v0.2.6

func (s *Sense) ClearV6ProbeResult()

ClearV6ProbeResult discards the cached v6 probe result.

func (*Sense) HasMultiPortEvidence added in v0.2.1

func (s *Sense) HasMultiPortEvidence() bool

HasMultiPortEvidence reports whether any single public IPv4 address has been observed with ≥2 distinct ports in the live (non-expired) vote window. Used to overrule probe-based FullCone classification when there is live evidence that a single NAT device is assigning different external ports per destination (Symmetric NAT behavior).

Ports are only compared within the same public IP: a multi-homed host whose two NICs have different public IPs will each show one stable port and must NOT be treated as Symmetric. v6 GUA ports are also excluded.

func (*Sense) InferNATType

func (s *Sense) InferNATType() uint8

InferNATType returns the best NAT classification for local operational use (punch strategy, session signalling). It is intentionally worst-case: symmetric detection considers ALL v4 paths — if any path shows symmetric behaviour the result is NATSymmetric, so the punch peer sprays ports. A false-positive symmetric here wastes spray bandwidth but never breaks connectivity; a false-negative would cause punch to miss.

Do NOT use this for publishing to the DHT — call PublishNatType() instead, which restricts symmetric detection to the STUN-confirmed path and uses the active mapping probe as the authoritative signal for that path.

Decision flow:

  1. Public bind (QUIC socket on a WAN IPv4) → NATFullCone.
  2. Symmetric (worst-case): passive all-path OR fresh mapping probe → NATSymmetric.
  3. Insufficient passive evidence → NATUnknown.
  4. Active probe result (RecordV4ProbeResult): reachable=true → NATFullCone (any peer can initiate; Full Cone or cloud NAT) reachable=false → NATRestricted
  5. No probe result yet → NATRestricted (conservative default).

func (*Sense) InferV6Reach added in v0.2.6

func (s *Sense) InferV6Reach() V6Reach

InferV6Reach returns the current best classification of local IPv6 reachability. Decision flow:

  1. GUA bind confirmed → Direct (globally routable, no NAT).
  2. Active v6 probe result available → Direct or Firewalled.
  3. No information → Unknown.

func (*Sense) InvalidateObservations added in v0.1.4

func (s *Sense) InvalidateObservations()

InvalidateObservations clears passive observed_addr votes after confirmed network changes so old mappings do not bias the next consensus cycle.

func (*Sense) IsBindPublic deprecated added in v0.1.4

func (s *Sense) IsBindPublic() bool

Deprecated: use IsV4BindPublic.

func (*Sense) IsV4BindPublic added in v0.2.6

func (s *Sense) IsV4BindPublic() bool

IsV4BindPublic reports whether the local socket is bound to a public WAN IPv4 address.

func (*Sense) IsV6GUABind added in v0.2.6

func (s *Sense) IsV6GUABind() bool

IsV6GUABind reports whether a local GUA was detected.

func (*Sense) MinAgreeing

func (s *Sense) MinAgreeing() int

MinAgreeing returns the configured threshold.

func (*Sense) PublishNatType added in v0.2.6

func (s *Sense) PublishNatType() uint8

PublishNatType returns the NAT capability value to embed in a published DHT endpoint record. It differs from InferNATType in two ways:

  1. Symmetric detection is restricted to the STUN-confirmed v4 path (trustedV4IPLocked). Other paths (VPN, private) are excluded so that a stable public endpoint is not mis-classified as Symmetric because of an unrelated path's NAT behaviour.

  2. A fresh active mapping probe is authoritative for the STUN path: when one is available it overrides the passive vote analysis (which may be contaminated by UPnP session reuse). Without a fresh probe the passive analysis acts as a fallback.

  3. When passive evidence is sufficient but the active probe has not yet completed, InferNATType returns NATRestricted; PublishNatType returns NATUnknown so that peers attempt direct-dial optimistically.

func (*Sense) Record

func (s *Sense) Record(reporter a2al.NodeID, observed []byte)

Record adds one vote: reporter saw our endpoint as observed (wire encoding).

func (*Sense) RecordBindPublic deprecated added in v0.1.4

func (s *Sense) RecordBindPublic(isPublic bool)

Deprecated: use RecordV4BindPublic.

func (*Sense) RecordMappingProbeV4 added in v0.2.9

func (s *Sense) RecordMappingProbeV4(symmetric bool)

RecordMappingProbeV4 records the result of the active v4 NAT port-consistency probe. symmetric=true means no single external port held a strict majority across probe targets (Symmetric NAT behaviour); false means one port dominated.

func (*Sense) RecordProbeResult deprecated added in v0.1.4

func (s *Sense) RecordProbeResult(reachable bool)

Deprecated: use RecordV4ProbeResult.

func (*Sense) RecordV4BindPublic added in v0.2.6

func (s *Sense) RecordV4BindPublic(isPublic bool)

RecordV4BindPublic records whether the local UDP socket is bound to a public WAN IPv4 address. Called by host.RunNATProbe after inspecting the QUIC listen address.

func (*Sense) RecordV4ProbeResult added in v0.2.6

func (s *Sense) RecordV4ProbeResult(reachable bool)

RecordV4ProbeResult records the outcome of an active v4 reachability probe. reachable=true means ≥1 remote peer successfully echoed our claimed v4 address.

func (*Sense) RecordV6GUABind added in v0.2.6

func (s *Sense) RecordV6GUABind(hasGUA bool)

RecordV6GUABind records whether the local interface has an IPv6 Globally Unique Address (GUA). A GUA bind implies v6 direct reachability absent a stateful firewall. Called by host.RunNATProbe.

func (*Sense) RecordV6ProbeResult added in v0.2.6

func (s *Sense) RecordV6ProbeResult(reachable bool)

RecordV6ProbeResult records the outcome of an active v6 reachability probe. reachable=true means ≥1 remote peer echoed our v6 address (Direct); false means a stateful firewall is blocking unsolicited inbound v6.

func (*Sense) SetMinAgreeing

func (s *Sense) SetMinAgreeing(n int)

SetMinAgreeing updates the threshold (e.g. 1 for integration tests).

func (*Sense) TrustedUDP

func (s *Sense) TrustedUDP() (host string, port uint16, ok bool)

TrustedUDP returns host and port if some observed key has >= min distinct reporters whose votes are younger than voteTTL.

func (*Sense) TrustedUDPAll added in v0.2.6

func (s *Sense) TrustedUDPAll() []string

TrustedUDPAll returns all observed keys (host:port strings) that have reached the minimum-agreement threshold. Unlike TrustedUDP it does not stop at the first match, so callers receive both IPv4 and IPv6 consensus entries when a dual-stack node is observed from multiple reporters.

func (*Sense) TrustedWire

func (s *Sense) TrustedWire() ([]byte, bool)

TrustedWire returns the first trusted observed_addr wire form (6 or 18 bytes), for any address family. Deprecated: prefer TrustedWireV4 / TrustedWireV6 for family-specific probing.

func (*Sense) TrustedWireV4 added in v0.2.6

func (s *Sense) TrustedWireV4() ([]byte, bool)

TrustedWireV4 returns the wire-encoded trusted IPv4 external address, if any. Only v4 consensus entries are considered.

func (*Sense) TrustedWireV6 added in v0.2.6

func (s *Sense) TrustedWireV6() ([]byte, bool)

TrustedWireV6 returns the wire-encoded trusted IPv6 external address, if any. Only v6 consensus entries are considered.

type V6Reach added in v0.2.6

type V6Reach uint8

V6Reach classifies the local node's IPv6 reachability. The state space is simpler than v4 NAT types: v6 has globally routable addresses (Direct), is behind a stateful firewall (Firewalled), or we have not determined this yet (Unknown).

const (
	V6Unknown    V6Reach = 0
	V6Direct     V6Reach = 1 // GUA bind confirmed, or v6 probe echo received
	V6Firewalled V6Reach = 2 // v6 probe attempted but echo blocked
)

Jump to

Keyboard shortcuts

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