sdk

package
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: May 10, 2026 License: MIT Imports: 64 Imported by: 0

Documentation

Overview

Package sdk provides the core P2P networking library for Shurli.

This package is the primary SDK surface for Go consumers. External code should depend on the interfaces defined here (PeerNetwork, Resolver, ServiceManager, Authorizer) rather than concrete types where possible.

Architecture

The package is organized around these concepts:

  • Network: creates and manages a libp2p host with relay, NAT traversal, DHT discovery, and resource limits. Entry point: New.

  • Services: the plugin system. TCP proxy services (ExposeService) forward streams to local ports. Plugin services (RegisterHandler) process streams directly. All services are managed through ServiceRegistry.

  • Plugin Policies: transport-aware access control for plugins. By default, all plugins only operate over LAN and Direct connections. Relay transport is excluded unless explicitly allowed per-plugin. See PluginPolicy and DefaultPluginPolicy.

  • Events: the EventBus dispatches connection, service, and authorization events to registered handlers.

  • Naming: the NameResolver maps human-readable names to peer IDs, with support for fallback resolver chains.

  • Standalone: NewStandaloneHost and StandaloneResult.ResolveAndConnect provide a consolidated path for one-shot CLI commands that operate without a running daemon.

  • Bootstrap: BootstrapAndConnect handles DHT client-mode bootstrap, peer discovery, and relay circuit fallback.

  • Protocol IDs: ProtocolID and MustValidateProtocolIDs enforce valid protocol ID construction at init time.

Relay Separation

Relay server protocols (pairing, admin, MOTD, unseal) live in internal/relay and register directly on the libp2p host. They are architecturally separate from the plugin system in this package. Plugins are peer-to-peer only.

Thread Safety

All exported types are safe for concurrent use. The Network, ServiceRegistry, EventBus, and NameResolver use internal locking.

Grant header: binary token presentation on plugin stream open.

Every plugin stream (opened via OpenPluginStream / handled by plugin services) begins with a grant header. The header carries an optional macaroon capability token that the receiving node verifies cryptographically.

Wire format:

Byte 0:    version (0x01)
Byte 1:    flags   (0x01 = has token, 0x00 = no token)
Bytes 2-3: token length (uint16 big-endian, max 8192). Zero when no token.
Bytes 4-N: base64-encoded macaroon token (only if flags & 0x01)

4 bytes overhead when no token. Binary because this is every stream open.

NetIntel implements a lightweight presence announcement protocol for sharing network intelligence between peers.

Three-Layer Transport Architecture

Announcements propagate through three independent, co-existing layers:

  • Layer 1 (Direct Push): Each node pushes its own NodeAnnouncement to all directly connected peers every announceInterval. This is optimal for authorized pools where members are fully interconnected.

  • Layer 2 (Gossip Forwarding): When a node receives a NEW announcement (timestamp newer than cached), it forwards to gossipFanout random connected peers with an incremented hop counter. Max maxHops prevents infinite propagation. This extends reach beyond direct connections to ~fanout^maxHops unique peers per originator.

  • Layer 3 (GossipSub): Future addition when go-libp2p-pubsub supports go-libp2p >= v0.47.0. Will feed into the same handleAnnouncement entry point. Layer 2 can be disabled per-node when Layer 3 is active.

All three layers share the same message format (NodeAnnouncement), the same in-memory cache, and the same PeerFilter callback. The transport layer is fully decoupled from the intelligence layer.

Scaling Characteristics

Direct push overhead scales with connected peer count, not total network size:

20 peers  x 150 bytes = 3 KB per 5min tick
200 peers x 150 bytes = 30 KB per 5min tick
500 peers x 150 bytes = 75 KB per 5min tick

A single node's connection count is bounded by the DHT routing table (~200) plus the authorized peer set. Gossip forwarding adds at most gossipFanout messages per received announcement, bounded by maxHops.

Index

Constants

View Source
const (
	// MaxProtectedPeers caps the number of peers with active path protection (S4).
	MaxProtectedPeers = 10

	// MaxManagedRelayConns caps managed relay circuits (R4-F12).
	// Protection without managed conn costs nothing (just prevents cleanup).
	// Managed conns cost a relay circuit slot each.
	MaxManagedRelayConns = 5

	// DefaultConnGracePeriod is how long a new relay connection survives in
	// closeOnce before being eligible for cleanup (R8-C1). Configurable for tests.
	DefaultConnGracePeriod = 30 * time.Second
)
View Source
const (
	GradeA = "A"
	GradeB = "B"
	GradeC = "C"
	GradeD = "D"
	GradeF = "F"
)

Grade constants ordered from best to worst.

View Source
const DHTProtocolPrefix = "/shurli"

DHTProtocolPrefix is the default protocol prefix for the private shurli Kademlia DHT. This isolates shurli from the public IPFS Amino DHT (/ipfs/kad/1.0.0), giving us our own routing table at /shurli/kad/1.0.0. For namespace-specific prefixes, use DHTProtocolPrefixForNamespace.

View Source
const DefaultMaxProxyConns = 64

DefaultMaxProxyConns is the per-proxy connection limit (SEC-5). Protects both local TCP stack and remote peer's stream limits.

View Source
const DefaultTransport = TransportLAN | TransportDirect

DefaultTransport permits LAN and Direct connections. Relay is excluded. This is the default for ALL plugins: no data flows through relays unless explicitly allowed per-plugin.

View Source
const MDNSServiceName = "_shurli._udp"

MDNSServiceName is the DNS-SD service type used for LAN discovery. Fixed for all Shurli nodes. Network isolation is handled by the ConnectionGater (authorized_keys), not by mDNS service names.

View Source
const (
	// MaxHedgeFanOut is the maximum number of independent connection groups
	// to race across. Bounds goroutine + circuit reservation consumption.
	// Peers typically have 1-3 connections (direct + 1-2 relays).
	// Exported for use by the cancel protocol (sendMultiPathCancel).
	MaxHedgeFanOut = 3
)
View Source
const (
	// PresenceProtocol is the libp2p protocol ID for presence announcements.
	// Both direct push (Layer 1) and gossip forwarding (Layer 2) use this
	// same protocol. The Hops field distinguishes direct vs forwarded.
	PresenceProtocol = "/shurli/presence/1.0.0"
)
View Source
const ProtocolPrefix = "/shurli"

ProtocolPrefix is the base prefix for all Shurli application-layer protocols.

View Source
const RTTProbeProtocol = "/shurli/rtt-probe/1.0.0"

RTTProbeProtocol is the protocol ID used for traceroute RTT measurement.

View Source
const ServiceQueryProtocol = "/shurli/service-query/1.0.0"

ServiceQueryProtocol is the protocol ID for querying a peer's services.

Variables

View Source
var (
	// ErrServiceAlreadyRegistered is returned when trying to register a service
	// that already exists in the registry.
	ErrServiceAlreadyRegistered = errors.New("service already registered")

	// ErrServiceNotFound is returned when trying to unregister or access a service
	// that does not exist in the registry.
	ErrServiceNotFound = errors.New("service not found")

	// ErrNameNotFound is returned when a name cannot be resolved to a peer ID
	// and is not a valid peer ID itself.
	ErrNameNotFound = errors.New("name not found")

	// ErrResponseTooLarge is returned when a protocol response exceeds the
	// maximum allowed size.
	ErrResponseTooLarge = errors.New("response too large")
)
View Source
var DefaultSTUNServers = []string{
	"stun.l.google.com:19302",
	"stun.cloudflare.com:3478",
}

DefaultSTUNServers are well-known public STUN servers.

Functions

func AddRelayAddressesForPeerFunc

func AddRelayAddressesForPeerFunc(h host.Host, relayAddrs []string, target peer.ID) error

AddRelayAddressesForPeerFunc adds relay circuit addresses to the peerstore for a target peer. This is the standalone version that works with any host, matching the pattern from Network.AddRelayAddressesForPeer().

func BidirectionalProxy

func BidirectionalProxy(a, b HalfCloseConn, logPrefix string)

BidirectionalProxy copies data between two half-close-capable connections. It uses two goroutines for each direction, propagates half-close (CloseWrite) when one side finishes sending, and waits for both directions to complete. logPrefix identifies the connection in log messages (e.g., "ssh", "proxy").

func Blake3Sum

func Blake3Sum(data []byte) [32]byte

Blake3Sum computes the BLAKE3-256 hash of data.

func BootstrapAndConnect

func BootstrapAndConnect(ctx context.Context, h host.Host, net *Network, target peer.ID, cfg BootstrapConfig) error

BootstrapAndConnect bootstraps the DHT in client mode and connects to the target peer. It tries DHT discovery first, then falls back to relay circuit addresses. This is the library-level bootstrap for standalone commands and SDK consumers that operate without a daemon.

func BuildSTUNBindingRequest

func BuildSTUNBindingRequest(txID [12]byte) []byte

BuildSTUNBindingRequest creates a STUN Binding Request packet with the given transaction ID. Exported for testing.

func BuildSTUNBindingResponse

func BuildSTUNBindingResponse(txID [12]byte, ip net.IP, port int) []byte

BuildSTUNBindingResponse creates a STUN Binding Response with an XOR-MAPPED-ADDRESS attribute. Exported for testing.

func ComputeFingerprint

func ComputeFingerprint(a, b peer.ID) (emoji string, numeric string)

ComputeFingerprint computes a deterministic SAS fingerprint for a peer pair. Both peers compute the same fingerprint because both know both peer IDs. Returns both emoji and numeric representations.

func DHTProtocolPrefixForNamespace

func DHTProtocolPrefixForNamespace(namespace string) string

DHTProtocolPrefixForNamespace returns the DHT protocol prefix for a given network namespace. An empty namespace returns the default global prefix ("/shurli"). A non-empty namespace produces "/shurli/<namespace>" which results in the full DHT protocol "/shurli/<namespace>/kad/1.0.0", completely isolated from other namespaces at the protocol level.

func DialWithRetry

func DialWithRetry(dialFunc func() (ServiceConn, error), maxRetries int) func() (ServiceConn, error)

DialWithRetry wraps a dial function with exponential backoff retry. maxRetries is the number of retries after the first attempt (0 = no retry). Returns a new dial function that retries on failure.

func FingerprintPrefix

func FingerprintPrefix(a, b peer.ID) string

FingerprintPrefix returns the first 8 hex chars of the SHA-256 fingerprint for storage in the verified attribute.

func FormatBytes

func FormatBytes(b int64) string

FormatBytes formats a byte count for user-facing display (e.g. "1.2 GB", "500 MB").

func HedgedOpenStream

func HedgedOpenStream(ctx context.Context, n *Network, peerID peer.ID, serviceName string) (network.Stream, error)

HedgedOpenStream opens a stream to a peer, racing across independent connection groups when multiple paths exist. First successfully-negotiated stream wins, loser streams are Reset(). Zero overhead when only one connection group exists.

This is the TS-4 "always-on control signal hedging" primitive. It transparently hedges browse, download initiation, and other independent request-response operations across direct and relay paths.

Security: uses OpenPluginStreamOnConn which runs the full security pipeline (policy check, transport check, grant header). Never bypasses security.

func HumanizeError

func HumanizeError(err string) string

HumanizeError translates cryptic libp2p errors into actionable user-facing messages. Returns a human-friendly string. Falls back to truncateError for unrecognized patterns.

func InstrumentedBidirectionalProxy

func InstrumentedBidirectionalProxy(a, b HalfCloseConn, service string, metrics *Metrics)

InstrumentedBidirectionalProxy wraps BidirectionalProxy with metrics. When metrics is nil, it delegates directly to BidirectionalProxy.

func IsLANMultiaddr

func IsLANMultiaddr(addr ma.Multiaddr) bool

IsLANMultiaddr returns true if the multiaddr starts with a private IPv4 address (RFC 1918 / RFC 6598). Used to distinguish LAN addresses from public internet paths. Relay circuit addresses return false.

func LoadOrCreateIdentity

func LoadOrCreateIdentity(path, password string) (crypto.PrivKey, error)

LoadOrCreateIdentity loads an existing SHRL-encrypted identity or creates a new one.

func LogRelayDowngrade added in v0.4.1

func LogRelayDowngrade(conn network.Conn)

LogRelayDowngrade logs a warning when a relay circuit uses classical Noise instead of PQ Noise in opportunistic mode (F143, F144). Called by connLogger. Suppressed when PQ Noise is not registered (disabled mode) to avoid noise.

func MerkleRoot

func MerkleRoot(hashes [][32]byte) [32]byte

MerkleRoot computes the BLAKE3 Merkle root hash from a list of chunk hashes.

Tree construction:

  • Leaf: chunk hash (already BLAKE3)
  • Internal: BLAKE3(left || right)
  • Odd node: promoted to next level unchanged
  • Single hash: returned as-is (file with one chunk)
  • Empty list: zero hash

func MustValidateProtocolIDs

func MustValidateProtocolIDs(ids ...string)

MustValidateProtocolIDs validates a batch of protocol IDs at init time. Panics if any ID is malformed. Call from init() to catch typos at startup.

func OpenStreamOnConn

func OpenStreamOnConn(ctx context.Context, conn network.Conn, proto protocol.ID) (network.Stream, error)

OpenStreamOnConn opens a protocol-negotiated stream on a specific connection. This is the primitive needed for hedging across independent paths — it bypasses host.NewStream's automatic connection selection.

Uses go-multistream's eager SelectProtoOrFail for protocol negotiation. Sets a 10-second deadline for negotiation to prevent slowloris on stalled connections. The caller should extend/clear the deadline after this returns.

On negotiation failure, the raw stream is properly Reset() to prevent leaks (R2-C2).

func ParseByteSize

func ParseByteSize(s string) (int64, error)

ParseByteSize parses a human-readable byte size string into bytes. Supports: "unlimited" (returns -1), plain numbers, and suffixes KB, MB, GB, TB (case-insensitive, binary: 1MB = 1048576).

func ParseRelayAddrs

func ParseRelayAddrs(relayAddrs []string) ([]peer.AddrInfo, error)

ParseRelayAddrs parses relay multiaddrs into peer.AddrInfo slices. It deduplicates by peer ID and merges addresses for the same relay peer.

func PeerConnInfo

func PeerConnInfo(h host.Host, peerID peer.ID) (pathType string, addr string)

PeerConnInfo returns the path type ("DIRECT" or "RELAYED") and best remote address for an existing connection to a peer. Prefers direct connections.

func PeerIDFromKeyFile

func PeerIDFromKeyFile(path, password string) (peer.ID, error)

PeerIDFromKeyFile loads an encrypted key file and returns the derived peer ID.

func PingPeer

func PingPeer(ctx context.Context, h host.Host, peerID peer.ID, protocolID string, count int, interval time.Duration) <-chan PingResult

PingPeer sends count pings to peerID using the given ping-pong protocol. Results are delivered on the returned channel. The channel is closed when all pings are sent or the context is cancelled.

If count is 0, pings continuously until ctx is cancelled. The caller should read from the channel until it is closed.

func ProtocolID

func ProtocolID(name, version string) string

ProtocolID constructs a validated Shurli protocol identifier. Format: /shurli/<name>/<version> Panics on invalid input (empty, contains slash or whitespace). Use at init time for protocol constants; SDK consumers use this to register new protocols that are guaranteed well-formed.

func ProxyStreamToTCP

func ProxyStreamToTCP(stream network.Stream, tcpAddr string) error

ProxyStreamToTCP creates a bidirectional proxy between a libp2p stream and a local TCP service.

func ReadGrantHeader

func ReadGrantHeader(s network.Stream) (string, error)

ReadGrantHeader reads a grant token header from the stream. Returns the base64-encoded token, or empty string if no token was presented. Sets a 2-second deadline for the header read to prevent slowloris attacks, then clears the deadline so the rest of the stream is unaffected.

func RelayPeerFromAddr

func RelayPeerFromAddr(addr ma.Multiaddr) peer.ID

RelayPeerFromAddr extracts the relay peer ID from a circuit relay multiaddr. Returns empty peer.ID if the address is not a circuit relay address. Used by hasAnyActiveRelayGrant to check connections and peerstore addresses.

func RelayPeerFromAddrStr

func RelayPeerFromAddrStr(addrStr string) string

RelayPeerFromAddrStr extracts the relay peer ID string from a circuit relay multiaddr string. Returns empty string if the address is not a relay circuit.

func RelayServiceCID

func RelayServiceCID(namespace string) cid.Cid

RelayServiceCID returns a deterministic CID for relay discovery on DHT. Namespace-aware: different private networks get different CIDs.

func ResolveDNSSeeds

func ResolveDNSSeeds(ctx context.Context, domain string) []peer.AddrInfo

ResolveDNSSeeds queries _dnsaddr.<domain> TXT records for bootstrap peer multiaddrs. This follows the dnsaddr multiaddr convention used by IPFS bootstrap nodes.

TXT record format: dnsaddr=/ip4/203.0.113.50/tcp/7777/p2p/12D3KooW...

Returns parsed peer.AddrInfo slice. DNS failures are logged but not fatal; the caller falls through to the next bootstrap layer.

func StripNonLANAddrs

func StripNonLANAddrs(h host.Host, pid peer.ID, lanReg *LANRegistry)

StripNonLANAddrs removes all non-LAN, non-relay addresses from the peerstore for a given peer. Keeps only relay circuit addresses and addresses verified as LAN by the LANRegistry (mDNS-proven). When lanReg is nil, falls back to bare IsLANMultiaddr (private IPv4 check).

Using LANRegistry instead of IsLANMultiaddr aligns this function with connLogger's trust model (F17-U1): connLogger uses IsVerifiedLAN to decide which connections to close, so the peerstore filter must use the same source of truth. Without this, a private IPv4 address added by identify (not mDNS-verified) survives the strip but gets its connection closed by connLogger, causing unnecessary close-and-redial churn.

Collects keepers first, then does a single ClearAddrs + AddAddrs to minimize the window where the peer has no addresses in the peerstore.

func ValidateProtocolID

func ValidateProtocolID(id string) error

ValidateProtocolID checks whether id is a well-formed Shurli protocol ID. Returns nil if valid, or an error describing the problem.

func ValidateServiceName

func ValidateServiceName(name string) error

ValidateServiceName checks that a service name is safe for use in protocol IDs.

func WriteGrantHeader

func WriteGrantHeader(s network.Stream, tokenBase64 string) error

WriteGrantHeader writes a grant token header to the stream. If tokenBase64 is empty, writes a 4-byte "no token" header. Returns an error if the token exceeds the max length. Sets a 2-second write deadline to prevent blocking on a stuck remote peer, then clears the deadline so the rest of the stream is unaffected.

Types

type AuditLogger

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

AuditLogger writes structured audit events for security-relevant actions. All methods are nil-safe: calling any method on a nil *AuditLogger is a no-op. This allows callers to skip nil checks at every call site.

func NewAuditLogger

func NewAuditLogger(handler slog.Handler) *AuditLogger

NewAuditLogger creates an AuditLogger that writes to the given handler. All audit events are written under the "audit" group for easy filtering.

func (*AuditLogger) AuthChange

func (a *AuditLogger) AuthChange(action, peerID string)

AuthChange logs a peer authorization change (add or remove).

func (*AuditLogger) AuthDecision

func (a *AuditLogger) AuthDecision(peerID, direction, result string)

AuthDecision logs an authentication allow/deny decision.

func (*AuditLogger) DaemonAPIAccess

func (a *AuditLogger) DaemonAPIAccess(method, path string, status int)

DaemonAPIAccess logs an API request to the daemon.

func (*AuditLogger) ServiceACLDenied

func (a *AuditLogger) ServiceACLDenied(peerID, service string)

ServiceACLDenied logs a per-service access control denial.

type Authorizer

type Authorizer interface {
	IsAuthorized(p peer.ID) bool
}

Authorizer makes authorization decisions about peers. Implementations: file-based allowlist, database lookup, certificate chain, etc.

The concrete *auth.AuthorizedPeerGater satisfies this interface.

type BandwidthTracker

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

BandwidthTracker wraps libp2p's BandwidthCounter and bridges per-peer bandwidth stats to Prometheus metrics and the daemon API.

func NewBandwidthTracker

func NewBandwidthTracker(prom *Metrics) *BandwidthTracker

NewBandwidthTracker creates a tracker. Pass nil for prom to disable Prometheus publishing (stats are still queryable via PeerStats/Totals).

func (*BandwidthTracker) AllPeerStats

func (bt *BandwidthTracker) AllPeerStats() map[peer.ID]metrics.Stats

AllPeerStats returns bandwidth stats keyed by peer ID.

func (*BandwidthTracker) Counter

func (bt *BandwidthTracker) Counter() *metrics.BandwidthCounter

Counter returns the underlying BandwidthCounter for wiring into the libp2p host via libp2p.BandwidthReporter().

func (*BandwidthTracker) PeerStats

func (bt *BandwidthTracker) PeerStats(p peer.ID) metrics.Stats

PeerStats returns bandwidth stats for a single peer.

func (*BandwidthTracker) ProtocolStats

func (bt *BandwidthTracker) ProtocolStats(proto protocol.ID) metrics.Stats

ProtocolStats returns bandwidth stats for a single protocol.

func (*BandwidthTracker) PublishMetrics

func (bt *BandwidthTracker) PublishMetrics()

PublishMetrics scrapes the BandwidthCounter and updates Prometheus gauges. Safe to call when prom is nil (no-op).

func (*BandwidthTracker) Start

func (bt *BandwidthTracker) Start(ctx context.Context, interval time.Duration)

Start runs a background goroutine that publishes metrics every interval and trims idle peers from the counter. Stops when ctx is cancelled.

func (*BandwidthTracker) Totals

func (bt *BandwidthTracker) Totals() metrics.Stats

Totals returns aggregate bandwidth stats across all peers and protocols.

type BootstrapConfig

type BootstrapConfig struct {
	// Namespace is the DHT namespace (empty = global "/shurli/kad/1.0.0").
	Namespace string

	// BootstrapPeers are explicit bootstrap peer multiaddrs from config.
	// When empty, RelayAddrs are used as DHT bootstrap peers.
	BootstrapPeers []string

	// RelayAddrs are relay server multiaddrs used for circuit relay
	// fallback and (when BootstrapPeers is empty) DHT bootstrapping.
	RelayAddrs []string
}

BootstrapConfig configures standalone bootstrap for one-shot CLI commands (ping, traceroute) that create a temporary P2P host without a full daemon.

type Config

type Config struct {
	KeyFile        string
	KeyPassword    string                    // Password for SHRL-encrypted identity.key
	AuthorizedKeys string                    // Path to authorized_keys file (auto-creates gater if Gater is nil)
	Gater          *auth.AuthorizedPeerGater // Pre-created gater (for hot-reload support). Takes precedence over AuthorizedKeys.
	Config         *config.Config
	UserAgent      string // libp2p Identify user agent (e.g. "shurli/0.1.0")

	// Relay configuration (optional)
	EnableRelay        bool     // Enable relay support (AutoRelay + hole punching)
	RelayAddrs         []string // Relay server multiaddrs (e.g., "/ip4/1.2.3.4/tcp/7777/p2p/12D3Koo...")
	ForcePrivate       bool     // Force private reachability (required for relay reservations)
	EnableNATPortMap   bool     // Enable NAT port mapping
	EnableHolePunching bool     // Enable hole punching

	// Per-network ephemeral identity: when set, derives a namespace-specific
	// Ed25519 key from the master identity via HKDF. The node uses a different
	// peer ID on each namespace, preventing cross-network correlation.
	// Empty = global network (uses master identity, backward compatible).
	Namespace string

	// Extension points (optional, nil = use defaults)
	Resolver Resolver // Custom name resolver (nil = built-in local resolver)

	// Resource management
	ResourceLimitsEnabled bool // Enable libp2p resource manager (connection/stream/memory limits)

	// Observability
	Metrics          *Metrics          // Custom shurli metrics (nil = disabled). When non-nil, libp2p metrics are registered on Metrics.Registry.
	BandwidthTracker *BandwidthTracker // Per-peer bandwidth tracking (nil = disabled). Counter() wired into libp2p.BandwidthReporter().
}

Config for creating a new P2P network

type ConnGroup

type ConnGroup struct {
	Type  string         // "direct" or "relay-<relayPeerID>"
	Conns []network.Conn // connections in this group
}

ConnGroup represents a group of connections that share the same failure domain. Connections in the same group are NOT independent — hedging across them wastes resources for zero benefit.

Groups are classified by path type:

  • "direct": all non-relay connections (same physical network path)
  • "relay-<peerID>": connections through a specific relay server

TS-4 design: hedge picks ONE connection from each group.

func AllConnGroups

func AllConnGroups(h HostNetwork, peerID peer.ID, pp *PathProtector) []ConnGroup

AllConnGroups merges swarm connection groups with managed relay groups from PathProtector. This is the primary interface for hedging and cancel fan-out. ConnGroups stays as a pure swarm function for internal use (R8-D1).

func ConnGroups

func ConnGroups(h HostNetwork, peerID peer.ID) []ConnGroup

ConnGroups classifies all connections to a peer into independent groups. Returns one group per independent failure domain. Direct connections are always a single group (same NIC, same cable, same switch — not independent). Relay connections are grouped by relay server peer ID.

Returns nil if no connections exist to the peer.

type ConnectionRecorder

type ConnectionRecorder func(peerID, pathType string, latencyMs float64)

ConnectionRecorder is called on each successful reconnection with the peer ID, path type ("DIRECT"/"RELAYED"), and latency in ms. This callback bridges the pkg/sdk -> internal/reputation boundary: serve_common.go wires it to PeerHistory.RecordConnection().

type DialResult

type DialResult struct {
	PathType PathType      `json:"path_type"`
	Duration time.Duration `json:"duration_ms"`
	Address  string        `json:"address"` // winning multiaddr
}

DialResult is the outcome of a successful PathDialer.DialPeer call.

type Event

type Event struct {
	Type        EventType
	PeerID      peer.ID // Relevant peer (zero value if not applicable)
	ServiceName string  // Relevant service (empty if not applicable)
	Detail      string  // Additional context (e.g. transfer ID)
}

Event carries details about a network event.

type EventBus

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

EventBus dispatches network events to registered handlers. Thread-safe; handlers are called synchronously in registration order.

func NewEventBus

func NewEventBus() *EventBus

NewEventBus creates a new event bus.

func (*EventBus) Emit

func (b *EventBus) Emit(e Event)

Emit dispatches an event to all registered handlers in registration order. Handlers are snapshot-copied before dispatch so they may safely call Subscribe/Unsubscribe without deadlocking. A panicking handler is recovered so it cannot crash the event bus or other handlers.

func (*EventBus) Subscribe

func (b *EventBus) Subscribe(handler EventHandler) func()

Subscribe registers a handler and returns a function to unsubscribe.

type EventHandler

type EventHandler func(Event)

EventHandler is a callback for network events. Handlers must be non-blocking; long work should be dispatched to a goroutine.

type EventType

type EventType int

EventType identifies the kind of network event.

const (
	EventPeerConnected     EventType = iota + 1 // A peer connected
	EventPeerDisconnected                       // A peer disconnected
	EventServiceRegistered                      // A service was registered
	EventServiceRemoved                         // A service was unregistered
	EventStreamOpened                           // An inbound stream was opened
	EventStreamClosed                           // A stream was closed
	EventAuthAllow                              // An inbound connection was allowed
	EventAuthDeny                               // An inbound connection was denied
	EventTransferPending                        // A transfer is awaiting approval (ask mode)
)

type GrantChecker

type GrantChecker func(peerID peer.ID, service string, transport TransportType) bool

GrantChecker is a function that checks whether a peer has a valid data access grant for a given service on the given transport. Injected by the daemon from the grant store. When set and returns true, the transport is allowed for that peer+service even if the plugin policy doesn't allow it. This is the node-level security boundary (C2 mitigation). Callers on the client side (pre-dial) that only want to know whether a usable grant exists for relay should pass TransportRelay.

type HalfCloseConn

type HalfCloseConn interface {
	io.ReadWriteCloser
	CloseWrite() error
}

HalfCloseConn is a connection that supports half-close (CloseWrite). Both ServiceConn (libp2p streams) and tcpHalfCloser (TCP connections) implement this.

type HostNetwork

type HostNetwork interface {
	Network() network.Network
}

HostNetwork is the interface satisfied by host.Host — used to avoid importing the full host package for ConnGroups.

type InterfaceInfo

type InterfaceInfo struct {
	Name       string   `json:"name"`
	IPv4Addrs  []string `json:"ipv4_addrs,omitempty"`
	IPv6Addrs  []string `json:"ipv6_addrs,omitempty"`
	IsLoopback bool     `json:"is_loopback"`
}

InterfaceInfo describes a single network interface with its global unicast addresses.

type InterfaceSummary

type InterfaceSummary struct {
	Interfaces      []InterfaceInfo `json:"interfaces"`
	HasGlobalIPv6   bool            `json:"has_global_ipv6"`
	HasGlobalIPv4   bool            `json:"has_global_ipv4"`
	GlobalIPv6Addrs []string        `json:"global_ipv6_addrs,omitempty"`
	GlobalIPv4Addrs []string        `json:"global_ipv4_addrs,omitempty"`

	// AllIPv4Addrs / AllIPv6Addrs are every unicast IP bound to a live
	// interface, excluding loopback and link-local. These are a superset
	// of the Global* lists — they additionally include RFC 1918, RFC 6598
	// CGNAT, and ULA IPv6. They exist so diffSummaries can compute an
	// authoritative Added/Removed delta that includes private-IPv4
	// transitions (e.g. one carrier-NAT RFC 1918 prefix to another, or
	// an RFC 6598 CGNAT prefix to a classic RFC 1918 LAN prefix). Without
	// these, `change.Removed` was blind to
	// private-IP interfaces vanishing, which left `CloseStaleConnections`
	// unable to kill conns bound to the dead interface via its authoritative
	// gate in the serve_common network-change handler.
	//
	// Reachability classification (`reachability.go`, `peerrelay.go`,
	// `network.go` IPv6 dialer/factory, `peermanager.go` IPv6 probe target
	// extraction) still reads the Global* fields, so publicly-routable
	// semantics are unchanged.
	AllIPv4Addrs []string `json:"all_ipv4_addrs,omitempty"`
	AllIPv6Addrs []string `json:"all_ipv6_addrs,omitempty"`

	// TunnelInterfaces lists names of active VPN/tunnel interfaces.
	// Used by diffSummaries to detect VPN activation/deactivation even
	// when global IPs don't change (VPN tunnels typically carry only
	// private IPv4, invisible to the global IP diff).
	TunnelInterfaces []string `json:"tunnel_interfaces,omitempty"`

	// DefaultGateway is the IPv4 default gateway address. Used by
	// diffSummaries to detect network switches between private-IPv4-only
	// networks (e.g., two different CGNAT carriers) where no global IP
	// changes occur. Platform-specific: parsed from route table.
	DefaultGateway string `json:"default_gateway,omitempty"`
}

InterfaceSummary is the result of DiscoverInterfaces. It provides a snapshot of all network interfaces with global unicast addresses and convenience flags for IPv4/IPv6 availability.

func DiscoverInterfaces

func DiscoverInterfaces() (*InterfaceSummary, error)

DiscoverInterfaces enumerates all network interfaces, filters for global unicast addresses, and returns a summary. Link-local, ULA, and private IPv4 addresses are excluded from the global lists but the interface itself is still reported (for debugging).

type LANRegistry

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

LANRegistry tracks peers and IPs confirmed as LAN-local via mDNS.

func NewLANRegistry

func NewLANRegistry() *LANRegistry

NewLANRegistry creates an empty registry.

func (*LANRegistry) Add

func (r *LANRegistry) Add(pid peer.ID, ips []string)

Add registers IPs discovered via mDNS for a peer. Called by mDNS discovery after filtering to LAN addresses. Each call refreshes the TTL and merges new IPs (previous IPs from the same peer are kept).

func (*LANRegistry) HasVerifiedLANConn

func (r *LANRegistry) HasVerifiedLANConn(h host.Host, pid peer.ID) bool

HasVerifiedLANConn returns true if the peer has at least one live non-relay connection whose remote IP is mDNS-verified. This is the authoritative "does this peer have a real LAN connection?" check for all trust-making code (RS gates, bandwidth budgets, transport policy). Bare RFC 1918 matches misclassify CGNAT, Docker, VPN, and multi-WAN routed-private subnets as LAN — only mDNS multicast reception proves link-local proximity.

func (*LANRegistry) IsVerifiedLAN

func (r *LANRegistry) IsVerifiedLAN(pid peer.ID, remoteAddr ma.Multiaddr) bool

IsVerifiedLAN returns true if the remote address of a connection matches an mDNS-verified LAN IP for the given peer. The entry must not be expired (within lanRegistryTTL of last mDNS discovery).

func (*LANRegistry) Remove

func (r *LANRegistry) Remove(pid peer.ID)

Remove deletes a peer from the registry (e.g., when deauthorized).

type MDNSDiscovery

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

MDNSDiscovery handles LAN peer discovery using mDNS (DNS-SD). Registers the service via zeroconf.RegisterProxy, then runs a periodic browse loop using platform-native APIs when available (dns_sd.h on macOS/Linux) or zeroconf as fallback.

Native browse cooperates with the system mDNS daemon via IPC (mDNSResponder on macOS, avahi on Linux) instead of competing for the multicast socket on port 5353.

Discovered peers have their addresses added to the peerstore; connection attempts go through the normal ConnectionGater.

func NewMDNSDiscovery

func NewMDNSDiscovery(h host.Host, m *Metrics, lanReg *LANRegistry) *MDNSDiscovery

NewMDNSDiscovery creates an mDNS discovery service. Metrics is optional (nil-safe).

func (*MDNSDiscovery) BrowseNow

func (md *MDNSDiscovery) BrowseNow()

BrowseNow triggers an immediate mDNS re-browse. Called after network changes to discover LAN peers without waiting for the next 30s cycle. Clears dedup timers since the network context has changed.

func (*MDNSDiscovery) Close

func (md *MDNSDiscovery) Close() error

Close stops the mDNS service and waits for in-flight connection attempts to finish.

func (*MDNSDiscovery) HandlePeerFound

func (md *MDNSDiscovery) HandlePeerFound(pi peer.AddrInfo)

HandlePeerFound is called when a peer is discovered via mDNS on the local network.

func (*MDNSDiscovery) SetPeerReconnector

func (md *MDNSDiscovery) SetPeerReconnector(pr interface{ ReconnectPeer(peer.ID) bool })

SetPeerReconnector wires the PeerManager so mDNS can re-trigger reconnect on upgrade failure. Called during daemon setup after both are created.

func (*MDNSDiscovery) Start

func (md *MDNSDiscovery) Start(ctx context.Context) error

Start begins mDNS advertising and periodic browsing on the local network.

type ManagedPathInfo

type ManagedPathInfo struct {
	PeerID      peer.ID
	RelayPeerID peer.ID
	RemoteAddr  string
	Created     time.Time
	Streams     int
	Dead        bool
}

ManagedPathInfo describes a managed relay connection for observability (R8-I1).

type ManagedPeer

type ManagedPeer struct {
	ID              peer.ID
	Connected       bool
	LastSeen        time.Time
	LastDialAttempt time.Time
	LastDialError   string
	ConsecFailures  int       // consecutive failures (resets on success or network change)
	BackoffUntil    time.Time // don't retry before this time
	ProbeUntil      time.Time // probe cooldown: reconnect loop skips this peer until expired
	// contains filtered or unexported fields
}

ManagedPeer tracks the lifecycle state of a single watched peer.

type ManagedPeerInfo

type ManagedPeerInfo struct {
	PeerID         string `json:"peer_id"`
	Connected      bool   `json:"connected"`
	LastSeen       string `json:"last_seen,omitempty"`
	LastDialError  string `json:"last_dial_error,omitempty"`
	ConsecFailures int    `json:"consec_failures"`
	BackoffUntil   string `json:"backoff_until,omitempty"`
}

ManagedPeerInfo is a read-only snapshot for the daemon API and status display.

type Metrics

type Metrics struct {
	Registry *prometheus.Registry

	// Proxy metrics
	ProxyBytesTotal       *prometheus.CounterVec
	ProxyConnectionsTotal *prometheus.CounterVec
	ProxyActiveConns      *prometheus.GaugeVec
	ProxyDurationSeconds  *prometheus.HistogramVec

	// Auth metrics
	AuthDecisionsTotal *prometheus.CounterVec

	// Hole punch metrics (enhanced from existing holePunchTracer)
	HolePunchTotal           *prometheus.CounterVec
	HolePunchDurationSeconds *prometheus.HistogramVec

	// Daemon API metrics
	DaemonRequestsTotal          *prometheus.CounterVec
	DaemonRequestDurationSeconds *prometheus.HistogramVec

	// Path dial metrics
	PathDialTotal           *prometheus.CounterVec
	PathDialDurationSeconds *prometheus.HistogramVec

	// Connected peers (tracked by PathTracker)
	ConnectedPeers *prometheus.GaugeVec

	// Network change events (tracked by NetworkMonitor)
	NetworkChangeTotal *prometheus.CounterVec

	// STUN probe metrics
	STUNProbeTotal *prometheus.CounterVec

	// mDNS discovery metrics
	MDNSDiscoveredTotal *prometheus.CounterVec

	// PeerManager reconnection metrics
	PeerManagerReconnectTotal *prometheus.CounterVec

	// Network intelligence (presence) metrics
	NetIntelSentTotal     *prometheus.CounterVec
	NetIntelReceivedTotal *prometheus.CounterVec

	// Interface metrics
	InterfaceCount *prometheus.GaugeVec

	// Vault metrics (seal state, unseal attempts, lockout)
	VaultSealed            prometheus.Gauge
	VaultSealOpsTotal      *prometheus.CounterVec
	VaultUnsealTotal       *prometheus.CounterVec
	VaultUnsealLockedPeers prometheus.Gauge

	// Deposit metrics (invite lifecycle)
	DepositOpsTotal *prometheus.CounterVec
	DepositPending  prometheus.Gauge

	// Pairing metrics (relay-mediated pairing)
	PairingTotal *prometheus.CounterVec

	// Macaroon metrics (token verification)
	MacaroonVerifyTotal *prometheus.CounterVec

	// Admin socket metrics
	AdminRequestTotal           *prometheus.CounterVec
	AdminRequestDurationSeconds *prometheus.HistogramVec

	// ZKP metrics (Phase 7: anonymous relay authorization)
	ZKPProveTotal                 *prometheus.CounterVec
	ZKPProveDurationSeconds       *prometheus.HistogramVec
	ZKPVerifyTotal                *prometheus.CounterVec
	ZKPVerifyDurationSeconds      *prometheus.HistogramVec
	ZKPAuthTotal                  *prometheus.CounterVec
	ZKPTreeRebuildTotal           *prometheus.CounterVec
	ZKPTreeRebuildDurationSeconds *prometheus.HistogramVec
	ZKPTreeLeaves                 prometheus.Gauge
	ZKPChallengesPending          prometheus.Gauge

	// ZKP range proof metrics (Phase 7-C: private reputation)
	ZKPRangeProveTotal        *prometheus.CounterVec
	ZKPRangeProveDuration     *prometheus.HistogramVec
	ZKPRangeVerifyTotal       *prometheus.CounterVec
	ZKPRangeVerifyDuration    *prometheus.HistogramVec
	ZKPAnonAnnouncementsTotal *prometheus.CounterVec

	// Per-peer bandwidth (populated by BandwidthTracker)
	PeerBandwidthBytesTotal     *prometheus.GaugeVec // labels: peer, direction
	PeerBandwidthRate           *prometheus.GaugeVec // labels: peer, direction
	ProtocolBandwidthBytesTotal *prometheus.GaugeVec // labels: protocol, direction
	BandwidthBytesTotal         *prometheus.GaugeVec // labels: direction (aggregate)

	// Relay health (populated by RelayHealth)
	RelayHealthScore *prometheus.GaugeVec   // labels: peer, is_static
	RelayProbeTotal  *prometheus.CounterVec // labels: result

	// TS-5: Managed relay connection metrics (R8-I2)
	ManagedConnsActive           prometheus.Gauge
	ManagedConnsEstablishedTotal *prometheus.CounterVec // labels: (none)
	ManagedConnsFailedTotal      *prometheus.CounterVec // labels: (none)
	ManagedConnsClosedTotal      *prometheus.CounterVec // labels: reason (unprotect, dead, deauth, reaper, shutdown)

	// Build info
	BuildInfo *prometheus.GaugeVec
}

Metrics holds all custom shurli Prometheus metrics. Uses an isolated prometheus.Registry so shurli metrics don't collide with the global default registry. Each test gets its own Metrics instance.

func NewMetrics

func NewMetrics(version, goVersion string) *Metrics

NewMetrics creates a new Metrics instance with all collectors registered on an isolated registry. The version and goVersion are recorded as labels on the shurli_info gauge.

func (*Metrics) Handler

func (m *Metrics) Handler() http.Handler

Handler returns an http.Handler that serves the Prometheus metrics endpoint.

type NATType

type NATType string

NATType describes the type of NAT based on STUN probing results.

const (
	NATNone              NATType = "none"               // No NAT (public IP matches local)
	NATFullCone          NATType = "full-cone"          // Endpoint-independent mapping
	NATAddressRestricted NATType = "address-restricted" // Same mapping for all destinations
	NATPortRestricted    NATType = "port-restricted"    // Port differs per destination
	NATSymmetric         NATType = "symmetric"          // Different mapping per destination
	NATUnknown           NATType = "unknown"            // Could not determine
)

func (NATType) HolePunchable

func (n NATType) HolePunchable() bool

HolePunchable returns true if this NAT type is amenable to hole punching.

type NameResolver

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

NameResolver resolves names to peer IDs. An optional fallback Resolver is consulted when local names don't match.

func NewNameResolver

func NewNameResolver() *NameResolver

NewNameResolver creates a new name resolver

func (*NameResolver) List

func (r *NameResolver) List() map[string]peer.ID

List returns all registered name mappings

func (*NameResolver) LoadFromMap

func (r *NameResolver) LoadFromMap(names map[string]string) error

LoadFromMap loads name mappings from a map (additive - existing names preserved). Names are normalized: trimmed of whitespace and lowercased, consistent with Register().

func (*NameResolver) Register

func (r *NameResolver) Register(name string, peerID peer.ID) error

Register registers a name → peer ID mapping. Names are normalized: trimmed of whitespace and lowercased for consistent lookup.

func (*NameResolver) ReplaceFromMap

func (r *NameResolver) ReplaceFromMap(names map[string]string) error

ReplaceFromMap replaces all name mappings with the given map. Unlike LoadFromMap, this clears existing names first so that removed names don't persist in memory after a config reload.

func (*NameResolver) Resolve

func (r *NameResolver) Resolve(name string) (peer.ID, error)

Resolve resolves a name to a peer ID. Name is normalized (trimmed + lowercased) for direct map lookup. If the name is not found, tries to parse it as a direct peer ID.

func (*NameResolver) Unregister

func (r *NameResolver) Unregister(name string)

Unregister removes a name mapping. Name is normalized (trimmed + lowercased) to match stored keys.

type NetIntel

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

NetIntel manages the presence announcement protocol.

It is transport-agnostic: the cache and query methods work identically regardless of how announcements arrive. Currently Layer 1 (direct push) and Layer 2 (gossip forwarding) are built-in. Layer 3 (GossipSub) can be added as an additional transport by calling handleAnnouncement() from a GossipSub subscription handler.

func NewNetIntel

func NewNetIntel(h host.Host, m *Metrics, pf PeerFilter, sp NodeStateProvider, interval time.Duration) *NetIntel

NewNetIntel creates a NetIntel instance. The peerFilter and stateProvider callbacks are required. Metrics is optional (nil-safe). The interval parameter overrides the default announce interval (0 = use default).

func (*NetIntel) AnnounceNow

func (ni *NetIntel) AnnounceNow()

AnnounceNow triggers an immediate re-announcement to all connected peers. Non-blocking: if a publish is already pending, this is a no-op. Called by serve_common.go on network change events.

func (*NetIntel) Close

func (ni *NetIntel) Close()

Close stops all background goroutines and removes the stream handler.

func (*NetIntel) GetAllPeerState

func (ni *NetIntel) GetAllPeerState() []PeerAnnouncement

GetAllPeerState returns a snapshot of all cached peer announcements.

func (*NetIntel) GetPeerState

func (ni *NetIntel) GetPeerState(pid peer.ID) *PeerAnnouncement

GetPeerState returns a copy of the cached announcement for a single peer, or nil if not found or expired.

func (*NetIntel) Start

func (ni *NetIntel) Start(ctx context.Context)

Start registers the stream handler and spawns background goroutines for publishing, cleanup, and gossip forwarding.

type Network

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

Network represents a P2P network instance

func New

func New(cfg *Config) (*Network, error)

New creates a new P2P network instance

func (*Network) AddRelayAddressesForPeer

func (n *Network) AddRelayAddressesForPeer(relayAddrs []string, targetPeerID peer.ID) error

AddRelayAddressesForPeer adds relay circuit addresses for a target peer to the peerstore. This allows the client to reach the target peer through the configured relay servers.

func (*Network) ClearDialBackoffs

func (n *Network) ClearDialBackoffs(peers []peer.ID)

ClearDialBackoffs clears the swarm's per-peer dial backoff cache. Call after a network change: the previous "no route to host" failures are invalid because the new network may have different routing (e.g., VPN with local network sharing now allows LAN paths that previously failed). Without this, mDNS upgrade attempts are rejected by stale swarm backoffs.

func (*Network) Close

func (n *Network) Close() error

Close shuts down the network

func (*Network) ConnectToService

func (n *Network) ConnectToService(peerID peer.ID, serviceName string) (ServiceConn, error)

ConnectToService connects to a remote peer's service with a default 30s timeout.

func (*Network) ConnectToServiceContext

func (n *Network) ConnectToServiceContext(ctx context.Context, peerID peer.ID, serviceName string) (ServiceConn, error)

ConnectToServiceContext connects to a remote peer's service using the provided context.

func (*Network) Events

func (n *Network) Events() *EventBus

Events returns the event bus for emitting events from external code (e.g., daemon, relay server). Not part of PeerNetwork interface.

func (*Network) ExposeService

func (n *Network) ExposeService(name, localAddress string, allowedPeers map[peer.ID]struct{}) error

ExposeService exposes a local TCP service through the P2P network. If allowedPeers is nil, all authorized peers can access the service.

func (*Network) GetLANRegistry

func (n *Network) GetLANRegistry() *LANRegistry

GetLANRegistry returns the mDNS-verified LAN registry. Used by mDNS discovery to register verified addresses and by PeerManager for trust.

func (*Network) GetPathProtector

func (n *Network) GetPathProtector() *PathProtector

GetPathProtector returns the path protector for managed relay connections. Used by plugins to Protect/Unprotect during transfers and by cancel fan-out.

func (*Network) HasVerifiedLANConn

func (n *Network) HasVerifiedLANConn(id peer.ID) bool

HasVerifiedLANConn returns true if the peer has at least one live non-relay connection whose remote IP is mDNS-verified as being on the local LAN. Nil-safe convenience wrapper over LANRegistry.HasVerifiedLANConn; callers (service registry, plugin-context wiring) don't need to know about the registry type or host binding.

Returns false if the network is nil or the registry has not been wired.

func (*Network) Host

func (n *Network) Host() host.Host

Host returns the underlying libp2p host

func (*Network) ListNames

func (n *Network) ListNames() map[string]peer.ID

ListNames returns all registered name-to-peer-ID mappings.

func (*Network) ListServices

func (n *Network) ListServices() []*Service

ListServices returns all registered services.

func (*Network) LoadNames

func (n *Network) LoadNames(names map[string]string) error

LoadNames loads name-to-peer-ID mappings from a string map (e.g., from YAML config)

func (*Network) OnEvent

func (n *Network) OnEvent(handler EventHandler) func()

OnEvent registers an event handler. Returns a function to unsubscribe.

func (*Network) OpenPluginStream

func (n *Network) OpenPluginStream(ctx context.Context, peerID peer.ID, serviceName string) (network.Stream, error)

OpenPluginStream opens a stream to a remote peer for a registered plugin, enforcing the plugin's transport and peer policy.

If the plugin's policy forbids relay, the stream will not be opened over relay connections. If the peer is denied by the policy, the call fails immediately without a network round-trip.

This is the correct way to initiate outbound plugin streams. Do NOT use Host().NewStream() directly for plugin protocols.

func (*Network) OpenPluginStreamOnConn

func (n *Network) OpenPluginStreamOnConn(ctx context.Context, peerID peer.ID, serviceName string, conn network.Conn) (network.Stream, error)

OpenPluginStreamOnConn opens a protocol-negotiated stream on a SPECIFIC connection to a peer. This is the connection-pinned variant of OpenPluginStream, used for hedging across independent paths (TS-4).

Runs the SAME security pipeline as OpenPluginStream:

  1. Service registry lookup
  2. PeerAllowed policy check
  3. Transport policy check (BEFORE opening stream — better than OpenPluginStream's post-dial)
  4. Pouch token + relay grant checks
  5. Protocol negotiation via SelectProtoOrFail
  6. Grant header write

The conn parameter must belong to the target peer (verified).

func (*Network) PeerID

func (n *Network) PeerID() peer.ID

PeerID returns the peer ID of this network node

func (*Network) RegisterHandler

func (n *Network) RegisterHandler(name string, handler StreamHandler, allowedPeers map[peer.ID]struct{}) error

RegisterHandler registers a custom stream handler as a named service. This is the plugin registration path - unlike ExposeService (which proxies to a local TCP port), the handler processes streams directly.

All plugins get a default PluginPolicy (LAN + Direct only, relay excluded). To override, set a custom policy on the returned service via GetService + modify, or pass a pre-built Service to ServiceRegistry.RegisterService directly.

func (*Network) RegisterHandlerRelayAllowed

func (n *Network) RegisterHandlerRelayAllowed(name string, handler StreamHandler, allowedPeers map[peer.ID]struct{}) error

RegisterHandlerRelayAllowed registers a custom stream handler that permits relay transport in addition to LAN and Direct. Use this for plugins that need to work for relay-only peers (e.g., file transfer for NAT-to-NAT).

func (*Network) RegisterName

func (n *Network) RegisterName(name string, peerID peer.ID) error

RegisterName registers a local name mapping

func (*Network) RegisterServiceQuery

func (n *Network) RegisterServiceQuery() error

RegisterServiceQuery registers the service-query protocol handler, which allows remote peers to discover this node's enabled services. Relay transport is allowed since this only returns metadata (service names and protocols).

func (*Network) ReplaceNames

func (n *Network) ReplaceNames(names map[string]string) error

ReplaceNames replaces all name mappings with the given map (for config reload). Unlike LoadNames, removed names are cleared from memory.

func (*Network) ResetBlackHoles

func (n *Network) ResetBlackHoles()

ResetBlackHoles resets libp2p's UDP and IPv6 black hole detectors. Call after a network change: the previous black hole state is invalid because the new network may have different connectivity (e.g., switching from cellular CGNAT with no IPv6 to a WiFi network with full IPv6). Without this, the swarm refuses to dial IPv6/UDP addresses even when our raw probes confirm reachability.

func (*Network) ResolveName

func (n *Network) ResolveName(name string) (peer.ID, error)

ResolveName resolves a name to a peer ID

func (*Network) ServiceRegistry

func (n *Network) ServiceRegistry() *ServiceRegistry

ServiceRegistry returns the underlying ServiceRegistry for direct access. Plugins that need Use() or other ServiceManager methods use this.

func (*Network) SetPathProtector

func (n *Network) SetPathProtector(pp *PathProtector)

SetPathProtector sets the path protector. Called during daemon wiring after both Network and PeerManager are created (LANRegistry pattern).

func (*Network) UnexposeService

func (n *Network) UnexposeService(name string) error

UnexposeService removes a previously exposed service from the P2P network.

type NetworkChange

type NetworkChange struct {
	Added          []string // new global IPs
	Removed        []string // lost global IPs
	IPv6Changed    bool
	IPv4Changed    bool
	TunnelChanged  bool // VPN/tunnel interface appeared or disappeared
	GatewayChanged bool // default gateway IP changed (private IPv4 network switch)
}

NetworkChange describes what changed between two interface snapshots.

type NetworkMonitor

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

NetworkMonitor watches for network interface changes and calls onChange when global IP addresses are added or removed. The platform-specific implementation uses event-driven detection (macOS route socket, Linux Netlink) with polling as a fallback on other platforms.

func NewNetworkMonitor

func NewNetworkMonitor(onChange func(*NetworkChange), m *Metrics) *NetworkMonitor

NewNetworkMonitor creates a NetworkMonitor. Metrics is optional (nil-safe).

func (*NetworkMonitor) Run

func (nm *NetworkMonitor) Run(ctx context.Context)

Run blocks until the context is cancelled. It watches for network changes using platform-specific event sources and calls onChange when global IPs change. The initial interface snapshot is taken on start.

type NodeAnnouncement

type NodeAnnouncement struct {
	Version     int    `json:"v"`          // Protocol version (1)
	From        string `json:"from"`       // Originator peer ID (set by sender, preserved on forward)
	Grade       string `json:"grade"`      // Reachability grade: A/B/C/D/F
	NATType     string `json:"nat_type"`   // full-cone, address-restricted, port-restricted, symmetric, unknown
	HasIPv4     bool   `json:"ipv4"`       // Has global unicast IPv4
	HasIPv6     bool   `json:"ipv6"`       // Has global unicast IPv6
	BehindCGNAT bool   `json:"cgnat"`      // Behind carrier-grade NAT (RFC 6598)
	UptimeSec   int64  `json:"uptime_sec"` // Seconds since daemon start
	PeerCount   int    `json:"peer_count"` // Number of connected peers
	Timestamp   int64  `json:"ts"`         // Unix timestamp of announcement
	Hops        int    `json:"hops"`       // 0 = direct from originator, incremented on each forward

	// Anonymous mode (Phase 7): when true, From is empty and the announcement
	// is authenticated via a ZKP membership proof instead of peer identity.
	// Recipients verify the proof against the Merkle root to confirm the sender
	// is an authorized member without learning which member.
	AnonymousMode bool   `json:"anon,omitempty"`      // true = From is empty, ZKPProof is set
	ZKPProof      []byte `json:"zkp_proof,omitempty"` // serialized PLONK membership proof
}

NodeAnnouncement is the presence message exchanged between peers. ~150 bytes JSON. Contains ONLY aggregate capabilities, no private data (no IP addresses, no peer IDs of connected peers, no hostnames).

Wire format versioning:

v1 (current): JSON encoding via direct-push + gossip forwarding
v2 (future):  Protobuf encoding when gossip volume warrants it

Transport independence: this struct is encoded/decoded identically regardless of whether it arrives via direct stream, gossip forwarding, or GossipSub message.

type NodeStateProvider

type NodeStateProvider func() *NodeAnnouncement

NodeStateProvider builds the current NodeAnnouncement from runtime state. Called on every publish tick and on-demand via AnnounceNow().

Wired in serve_common.go to a closure that reads rt.ifSummary, rt.stunProber.Result(), and rt.startTime. This breaks the import boundary between pkg/sdk and the runtime structs.

type PQCConnInfo added in v0.4.1

type PQCConnInfo struct {
	PeerID    string `json:"peer_id"`
	Transport string `json:"transport"` // "quic", "tcp", "ws", "relay"
	Security  string `json:"security"`  // "/pq-noise/1", "/noise", "" (QUIC = TLS layer)
	CurveID   string `json:"curve_id"`  // e.g. "X25519MLKEM768", "X25519", "" (QUIC only)
	CurveCode uint16 `json:"curve_code"`
	PQ        bool   `json:"pq"` // true if post-quantum (either QUIC TLS or PQ Noise)
}

PQCConnInfo describes the security state of a single connection.

type PQCStatus added in v0.4.1

type PQCStatus struct {
	// Policy is the active PQC policy ("mandatory", "opportunistic", "disabled").
	Policy string `json:"policy"`

	// QUICPQCVerified is true when at least one QUIC connection negotiated
	// a post-quantum key exchange (X25519MLKEM768 or other PQ curve).
	QUICPQCVerified bool `json:"quic_pqc_verified"`

	// NoisePQCVerified is true when at least one TCP/WS connection negotiated
	// PQ Noise (/pq-noise/1) as the security protocol.
	NoisePQCVerified bool `json:"noise_pqc_verified"`

	// Connections breaks down the security state for every active connection.
	Connections []PQCConnInfo `json:"connections,omitempty"`
}

PQCStatus summarizes post-quantum cryptography state across all connections.

func InspectPQC added in v0.4.1

func InspectPQC(h host.Host) PQCStatus

InspectPQC examines all active connections on the host and returns PQC status. Detects both PQ QUIC (TLS X25519MLKEM768) and PQ Noise (/pq-noise/1 on TCP/WS).

type PathDialer

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

PathDialer connects to peers using parallel path racing. It launches DHT discovery and relay circuit attempts concurrently and returns as soon as the first path succeeds, cancelling the other.

func NewPathDialer

func NewPathDialer(h host.Host, kdht *dht.IpfsDHT, relaySource RelaySource, m *Metrics) *PathDialer

NewPathDialer creates a PathDialer. The DHT and metrics are optional (nil-safe). relaySource provides relay addresses; use &StaticRelaySource{Addrs: addrs} for a fixed list, or a RelayDiscovery for dynamic DHT-discovered relays.

func (*PathDialer) DialPeer

func (pd *PathDialer) DialPeer(ctx context.Context, peerID peer.ID) (*DialResult, error)

DialPeer connects to the target peer using parallel path racing. If already connected, it returns immediately with the current path type. Otherwise it races DHT discovery against relay circuit, returning the first successful connection.

type PathProtector

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

PathProtector prevents relay connection cleanup during active transfers and proactively establishes managed relay circuits as backup paths.

Design: 9 rounds of thought experiments, 111 findings. See project-performance-baseline-2026-03-15.md TS-5 section.

func NewPathProtector

func NewPathProtector(h host.Host, rs RelaySource, lr *LANRegistry, bw *BandwidthTracker) *PathProtector

NewPathProtector creates a PathProtector. Pass to both Network and PeerManager. Pattern matches LANRegistry: created externally, shared across components (D1).

func (*PathProtector) Close

func (pp *PathProtector) Close()

Close stops the PathProtector and cleans up all managed connections (R7-D2). Called from daemon shutdown chain after PeerManager.Close().

func (*PathProtector) ForceUnprotectAll

func (pp *PathProtector) ForceUnprotectAll(pid peer.ID)

ForceUnprotectAll removes all tags and closes all managed connections to a peer. Called on deauthorization (R7-C1).

func (*PathProtector) IsProtected

func (pp *PathProtector) IsProtected(pid peer.ID) bool

IsProtected returns whether a peer has any active protection tags. Called by PeerManager's closeOnce and startRelayCleanup guards (C1, C2, C4).

func (*PathProtector) ManagedConnsForCancel

func (pp *PathProtector) ManagedConnsForCancel(peerID peer.ID) []network.Conn

ManagedConnsForCancel returns managed connections as network.Conn for cancel fan-out (R5-C2). Returns nil if no managed conn exists.

func (*PathProtector) ManagedGroups

func (pp *PathProtector) ManagedGroups(peerID peer.ID) []ConnGroup

ManagedGroups returns managed connections as ConnGroups for hedging (R8-D2). Used by AllConnGroups to merge with swarm groups.

func (*PathProtector) ManagedPaths

func (pp *PathProtector) ManagedPaths() []ManagedPathInfo

ManagedPaths returns info about all managed connections for observability (R8-I1).

func (*PathProtector) Protect

func (pp *PathProtector) Protect(pid peer.ID, tag string)

Protect marks a peer as path-protected with the given tag. Multiple tags per peer are supported (I1). Background relay establishment triggers on first tag (0->1 transition, I3).

Uses time-based orphan detection in the reaper. For long-running operations, prefer ProtectWithContext which uses context-based liveness detection.

Security: compiled-in plugins are trusted (current phase). When Layer 2 WASM ships, Protect must become a host function requiring a capability token (S3).

func (*PathProtector) ProtectWithContext

func (pp *PathProtector) ProtectWithContext(ctx context.Context, pid peer.ID, tag string)

ProtectWithContext marks a peer as path-protected with context-based liveness. The reaper will NOT consider this tag orphaned as long as ctx is alive. When the goroutine exits (ctx cancelled via defer), the reaper can detect the leaked tag if Unprotect was missed. This prevents the reaper from killing managed relay circuits that are idle backups for long-running transfers.

func (*PathProtector) SetMetrics

func (pp *PathProtector) SetMetrics(m *Metrics)

SetMetrics sets the Prometheus metrics for managed conn lifecycle (R8-I2).

func (*PathProtector) Unprotect

func (pp *PathProtector) Unprotect(pid peer.ID, tag string)

Unprotect removes a protection tag from a peer. When the last tag is removed, the managed relay connection is closed (I4).

type PathTracker

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

PathTracker monitors peer connections via the libp2p event bus and maintains per-peer path information (type, transport, IP version).

func NewPathTracker

func NewPathTracker(h host.Host, m *Metrics) *PathTracker

NewPathTracker creates a PathTracker. Metrics is optional (nil-safe).

func (*PathTracker) GetPeerPath

func (pt *PathTracker) GetPeerPath(pid peer.ID) (*PeerPathInfo, bool)

GetPeerPath returns path info for a specific peer.

func (*PathTracker) ListPeerPaths

func (pt *PathTracker) ListPeerPaths() []*PeerPathInfo

ListPeerPaths returns path info for all tracked peers.

func (*PathTracker) Start

func (pt *PathTracker) Start(ctx context.Context)

Start subscribes to peer connectedness events and processes them until the context is cancelled. Call this in a goroutine.

func (*PathTracker) UpdateRTT

func (pt *PathTracker) UpdateRTT(pid peer.ID, rttMs float64)

UpdateRTT records the latest round-trip time for a peer.

type PathType

type PathType string

PathType describes how a peer connection was established.

const (
	PathDirect  PathType = "DIRECT"
	PathRelayed PathType = "RELAYED"
)

func ClassifyMultiaddr

func ClassifyMultiaddr(addr string) (pathType PathType, transport string, ipVersion string)

ClassifyMultiaddr determines path type and extracts transport and IP version from a multiaddr string. Used by PathTracker and status display.

type PeerAnnouncement

type PeerAnnouncement struct {
	PeerID       peer.ID
	Announcement NodeAnnouncement
	ReceivedAt   time.Time
}

PeerAnnouncement is a cached announcement from a remote peer.

type PeerFilter

type PeerFilter func(peer.ID) bool

PeerFilter decides whether a peer should receive announcements and whether incoming announcements from a peer should be cached.

Current wiring (serve_common.go): gater.IsAuthorized() for the authorized-peers phase. When public network mode lands, this callback gets a second branch for open-network peers. No change to NetIntel.

type PeerManager

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

PeerManager maintains connections to watched peers using background reconnection with exponential backoff. It subscribes to the libp2p event bus for connect/disconnect events and delegates actual dialing to PathDialer (which races DHT vs relay paths).

The watchlist is populated from authorized_keys via the gater. Only watched peers are reconnected; other connected peers (relay, bootstrap, DHT) are ignored.

func NewPeerManager

func NewPeerManager(h host.Host, pd *PathDialer, m *Metrics, onReconnect ConnectionRecorder, lanReg *LANRegistry) *PeerManager

NewPeerManager creates a PeerManager. The onReconnect callback is optional (nil-safe) and fires on each successful background reconnection.

func (*PeerManager) Close

func (pm *PeerManager) Close()

Close stops all background goroutines and waits for them to finish.

func (*PeerManager) CloseAllPeerConnections

func (pm *PeerManager) CloseAllPeerConnections()

CloseAllPeerConnections closes ALL connections (direct AND relay circuits) to watched peers. Called during network changes to eliminate zombie connections of every type.

After a WiFi switch, BOTH direct QUIC connections and relay circuits become zombies. Direct connections die because the local interface changed. Relay circuits die because the underlying transport to the relay server was on the old interface — the circuit stream errors but libp2p may not detect it immediately (IsClosed returns false until the next read/write).

CloseStaleConnections' IP matching misses both classes:

  • Direct zombies when returning to the same network (IP comes back)
  • Relay circuit zombies (local IP is the relay server's, not the old interface)

Closing everything is safe: the network change handler reconnects to relays immediately (step 9), mDNS re-establishes LAN connections in 1-2s, and PeerManager's deferred reconnect handles remaining peers via DHT/relay.

func (*PeerManager) CloseStaleConnections

func (pm *PeerManager) CloseStaleConnections(removedIPs []string)

CloseStaleConnections closes connections to watched peers whose local address is no longer present on any active interface. When a network interface disappears (WiFi switch, USB LAN unplug), connections bound to that interface are dead but libp2p may not detect it for minutes (TCP keepalive timeout). Closing them immediately lets the reconnect loop redial via the new active interface.

IPv6 addresses go through DAD (Duplicate Address Detection) after a network change. During DAD (100-500ms), the address is "tentative" and invisible to net.Interfaces(). Connections using those addresses are still valid but would be killed by a naive "not in current IPs" check. To avoid this, IPv6 connections that are not explicitly in the removedIPs set are given the benefit of the doubt (TCP keepalive will catch truly dead ones). IPv4 has no DAD delay, so missing IPv4 addresses mean the interface is truly gone.

func (*PeerManager) GetLANRegistry

func (pm *PeerManager) GetLANRegistry() *LANRegistry

LANRegistry returns the mDNS-verified LAN registry for use by mDNS discovery and the gater's LAN dial filter.

func (*PeerManager) GetManagedPeers

func (pm *PeerManager) GetManagedPeers() []ManagedPeerInfo

GetManagedPeers returns a snapshot of all watched peers and their state.

func (*PeerManager) OnNetworkChange

func (pm *PeerManager) OnNetworkChange()

OnNetworkChange resets all backoff timers and triggers an immediate reconnect cycle. Used by the auth-reload path where deferral is not needed. The network change handler in serve_common.go calls ResetBackoffsForNetworkChange and TriggerReconnect separately with a grace period between them.

func (*PeerManager) OnWatchlistRemovedFunc

func (pm *PeerManager) OnWatchlistRemovedFunc() func(peer.ID)

OnWatchlistRemovedFunc returns the current watchlist-removed callback (for chaining).

func (*PeerManager) ProbeAndUpgradeRelayed

func (pm *PeerManager) ProbeAndUpgradeRelayed()

ProbeAndUpgradeRelayed checks if any watched peers currently connected via relay have a direct IPv6 path available through any local interface. For each candidate, a raw TCP probe confirms reachability before closing the relay connection. Called after network changes to exploit secondary interfaces (e.g., USB LAN with public IPv6) that the OS default route doesn't prefer but that can reach the peer directly.

RFC 6724 source address selection ensures the probe uses the correct interface automatically: if only one interface has global IPv6, the kernel routes through it regardless of the default gateway priority.

func (*PeerManager) ReconnectPeer

func (pm *PeerManager) ReconnectPeer(pid peer.ID) bool

ReconnectPeer clears internal backoff state for a single peer and triggers an immediate reconnect cycle. This is the manual escape hatch for AI agents and operators to recover a peer from backoff state without waiting for it to expire naturally. Returns true if the peer was in the watchlist.

func (*PeerManager) ResetBackoffsForNetworkChange

func (pm *PeerManager) ResetBackoffsForNetworkChange()

ResetBackoffsForNetworkChange clears backoff state for all watched peers without triggering a reconnect cycle. Called immediately on network change so that mDNS and other subsystems can dial without hitting stale backoffs. The reconnect trigger is deferred separately to give mDNS priority.

func (*PeerManager) ResetPeerBackoff

func (pm *PeerManager) ResetPeerBackoff(pid peer.ID)

ResetPeerBackoff clears the backoff state for a single peer, allowing the PeerManager's reconnect loop to immediately attempt reconnection. Used by ConnectToPeer after a dial failure to give the peer a fresh chance (e.g. after relay budget was refilled for the remote peer).

func (*PeerManager) SetConnGracePeriod

func (pm *PeerManager) SetConnGracePeriod(d time.Duration)

SetConnGracePeriod overrides the per-connection grace period for closeOnce. Used in tests (R9-D2). Production default: DefaultConnGracePeriod (30s).

func (*PeerManager) SetOnWatchlistRemoved

func (pm *PeerManager) SetOnWatchlistRemoved(fn func(peer.ID))

SetOnWatchlistRemoved registers a callback fired for each peer removed from the watchlist. Used by PathProtector for deauth cleanup (R7-D1).

func (*PeerManager) SetPathProtector

func (pm *PeerManager) SetPathProtector(pp *PathProtector)

SetPathProtector sets the path protector for guard checks in cleanup code. Called during daemon wiring after both PeerManager and PathProtector are created.

func (*PeerManager) SetWatchlist

func (pm *PeerManager) SetWatchlist(peerIDs []peer.ID)

SetWatchlist updates which peers PeerManager should maintain connections to. Typically called with gater.GetAuthorizedPeerIDs(). Peers removed from the watchlist are no longer tracked; new peers are checked for current connectedness.

func (*PeerManager) Start

func (pm *PeerManager) Start(ctx context.Context)

Start begins the event listener and reconnection loop. Call SetWatchlist before Start to populate the peer list.

func (*PeerManager) StripPrivateAddrs

func (pm *PeerManager) StripPrivateAddrs()

StripPrivateAddrs removes private/LAN addresses from the peerstore for all watched peers. Called during network changes BEFORE triggering reconnect or mDNS browse. This prevents the swarm dial worker from caching stale LAN address failures that poison concurrent direct dials (see libp2p-overrides.md section 6: dial worker deduplication race). mDNS re-populates LAN addresses from fresh discovery after the strip.

func (*PeerManager) TriggerReconnect

func (pm *PeerManager) TriggerReconnect()

TriggerReconnect sends a non-blocking signal to the reconnect loop.

type PeerNetwork

type PeerNetwork interface {
	// PeerID returns this node's peer ID.
	PeerID() peer.ID

	// ExposeService registers a local TCP service for remote peers.
	// Pass nil for allowedPeers to allow all authorized peers.
	ExposeService(name, localAddress string, allowedPeers map[peer.ID]struct{}) error

	// UnexposeService removes a previously exposed service.
	UnexposeService(name string) error

	// ListServices returns all registered services.
	ListServices() []*Service

	// ConnectToService connects to a remote peer's named service (30s timeout).
	ConnectToService(peerID peer.ID, serviceName string) (ServiceConn, error)

	// ConnectToServiceContext connects to a remote peer's named service.
	ConnectToServiceContext(ctx context.Context, peerID peer.ID, serviceName string) (ServiceConn, error)

	// ResolveName resolves a human-readable name to a peer ID.
	ResolveName(name string) (peer.ID, error)

	// RegisterName registers a local name-to-peer mapping.
	RegisterName(name string, peerID peer.ID) error

	// OnEvent registers an event handler. Returns a deregistration function.
	OnEvent(handler EventHandler) func()

	// Close shuts down the network.
	Close() error
}

PeerNetwork is the high-level interface for interacting with a Shurli P2P network. Third-party code and SDKs should depend on this interface, not the concrete Network struct.

The concrete *Network satisfies this interface.

type PeerPathInfo

type PeerPathInfo struct {
	PeerID      string   `json:"peer_id"`
	PathType    PathType `json:"path_type"`    // DIRECT or RELAYED
	Address     string   `json:"address"`      // current multiaddr
	ConnectedAt string   `json:"connected_at"` // RFC 3339
	Transport   string   `json:"transport"`    // quic, tcp, websocket
	IPVersion   string   `json:"ip_version"`   // ipv4, ipv6
	LastRTTMs   float64  `json:"last_rtt_ms,omitempty"`
}

PeerPathInfo describes the current path to a connected peer.

type PeerRelay

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

PeerRelay enables a peer with a public IP to act as a circuit relay for other authorized peers. This reduces dependence on the central VPS relay by letting any peer with a routable address serve as an alternative.

Security: The host-level ConnectionGater (authorized_keys peer ID allowlist) is the access control for peer relays. Only peers that pass the gater can connect, and therefore only they can make reservations or create circuits. No separate circuit ACL is applied because all connected peers are already vetted by the gater. If connection gating is disabled (no authorized_keys), this relay is open to any DHT peer - this is by design for open networks.

func NewPeerRelay

func NewPeerRelay(h host.Host, m *Metrics, cfg PeerRelayConfig) *PeerRelay

NewPeerRelay creates a PeerRelay. The relay is not enabled until Enable() is called. Pass a zero-value config to use defaults. Metrics is optional (nil-safe).

func (*PeerRelay) AutoDetect

func (pr *PeerRelay) AutoDetect(summary *InterfaceSummary)

AutoDetect enables or disables the relay based on config and network state. "true" = always on, "false" = always off, "auto" = enable if public IP detected.

func (*PeerRelay) Disable

func (pr *PeerRelay) Disable()

Disable stops the circuit relay service.

func (*PeerRelay) Enable

func (pr *PeerRelay) Enable() error

Enable starts the circuit relay v2 service on this host. Returns nil if already enabled.

func (*PeerRelay) Enabled

func (pr *PeerRelay) Enabled() bool

Enabled returns true if the relay is currently active.

func (*PeerRelay) OnStateChange

func (pr *PeerRelay) OnStateChange(fn func(enabled bool))

OnStateChange registers a callback invoked when the relay is enabled or disabled. Used by relay discovery to start/stop DHT advertisement.

type PeerRelayConfig

type PeerRelayConfig struct {
	// Enabled: "auto" (default/empty), "true", or "false".
	Enabled string

	// Resource limits (zero values use defaults).
	MaxReservations        int
	MaxCircuits            int
	MaxReservationsPerPeer int
	MaxReservationsPerIP   int
	MaxReservationsPerASN  int
	BufferSize             int
	CircuitDuration        time.Duration
	CircuitDataLimit       int64 // bytes per direction
}

PeerRelayConfig controls peer relay behavior.

func DefaultPeerRelayConfig

func DefaultPeerRelayConfig() PeerRelayConfig

DefaultPeerRelayConfig returns the default peer relay configuration.

type PingResult

type PingResult struct {
	Seq    int     `json:"seq"`
	PeerID string  `json:"peer_id"`
	RttMs  float64 `json:"rtt_ms"`
	Path   string  `json:"path"`  // "DIRECT" or "RELAYED"
	Error  string  `json:"error"` // empty on success
}

PingResult holds the result of a single ping to a peer.

type PingStats

type PingStats struct {
	Sent     int     `json:"sent"`
	Received int     `json:"received"`
	Lost     int     `json:"lost"`
	LossPct  float64 `json:"loss_pct"`
	MinMs    float64 `json:"min_ms"`
	AvgMs    float64 `json:"avg_ms"`
	MaxMs    float64 `json:"max_ms"`
}

PingStats holds aggregate statistics for a ping session.

func ComputePingStats

func ComputePingStats(results []PingResult) PingStats

ComputePingStats computes aggregate statistics from a slice of ping results.

type PluginPolicy

type PluginPolicy struct {
	// AllowedTransports is a bitmask of permitted connection types.
	// Default: TransportLAN | TransportDirect (relay excluded).
	AllowedTransports TransportType

	// AllowPeers restricts the plugin to only these peers.
	// nil = all authorized peers allowed (subject to DenyPeers).
	AllowPeers map[peer.ID]struct{}

	// DenyPeers blocks these peers from using this plugin.
	// Checked before AllowPeers (deny takes precedence).
	DenyPeers map[peer.ID]struct{}
}

PluginPolicy defines transport restrictions and peer access control for a plugin registered through the ServiceRegistry.

By design, plugins are peer-to-peer only. Relay server protocols live in internal/relay and register directly on the host, completely outside the plugin system. This separation is architectural and non-negotiable.

func DefaultPluginPolicy

func DefaultPluginPolicy() *PluginPolicy

DefaultPluginPolicy returns a policy that allows LAN + Direct only, with no peer restrictions. This is applied to every plugin registered via RegisterHandler unless overridden.

func (*PluginPolicy) PeerAllowed

func (p *PluginPolicy) PeerAllowed(id peer.ID) bool

PeerAllowed returns true if the given peer is permitted by this policy. Deny list is checked first (deny wins over allow).

func (*PluginPolicy) RelayAllowed

func (p *PluginPolicy) RelayAllowed() bool

RelayAllowed returns true if the policy permits relay connections.

func (*PluginPolicy) TransportAllowed

func (p *PluginPolicy) TransportAllowed(t TransportType) bool

TransportAllowed returns true if the given transport type is permitted.

type ProbeResult

type ProbeResult struct {
	ServerAddr   string        `json:"server_addr"`
	ExternalAddr string        `json:"external_addr,omitempty"`
	ExternalIP   string        `json:"external_ip,omitempty"`
	ExternalPort int           `json:"external_port,omitempty"`
	Latency      time.Duration `json:"latency_ms"`
	Error        string        `json:"error,omitempty"`
}

ProbeResult is the outcome of a single STUN server probe.

type ReachabilityGrade

type ReachabilityGrade struct {
	Grade       string `json:"grade"`
	Label       string `json:"label"`
	Description string `json:"description"`
}

ReachabilityGrade summarizes how reachable this node is from the internet.

func ComputeReachabilityGrade

func ComputeReachabilityGrade(ifaces *InterfaceSummary, stun *STUNResult) ReachabilityGrade

ComputeReachabilityGrade determines the reachability grade from interface discovery and STUN probe results. Either parameter may be nil.

Grade scale:

A  Excellent  Public IPv6 detected
B  Good       Public IPv4 or hole-punchable NAT (full-cone / address-restricted)
C  Fair       Port-restricted NAT
D  Poor       Symmetric NAT / CGNAT
F  Offline    No connectivity detected

type RelayDiscovery

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

RelayDiscovery discovers relay peers via DHT and combines them with static relay addresses from config.

func NewRelayDiscovery

func NewRelayDiscovery(staticRelays []peer.AddrInfo, namespace string, m *Metrics) *RelayDiscovery

NewRelayDiscovery creates a RelayDiscovery with static relays. DHT discovery is enabled later via SetDHT after host construction.

func (*RelayDiscovery) Advertise

func (rd *RelayDiscovery) Advertise(ctx context.Context, interval time.Duration)

Advertise announces this node as a relay provider on the DHT. Should be called when PeerRelay enables (via OnStateChange callback).

func (*RelayDiscovery) AllRelays

func (rd *RelayDiscovery) AllRelays() []peer.AddrInfo

AllRelays returns static relays followed by DHT-discovered relays.

func (*RelayDiscovery) Discover

func (rd *RelayDiscovery) Discover(ctx context.Context, count int) []peer.AddrInfo

Discover queries the DHT for relay providers. Returns up to count peers.

func (*RelayDiscovery) PeerSource

func (rd *RelayDiscovery) PeerSource(ctx context.Context, numPeers int) <-chan peer.AddrInfo

PeerSource returns a function compatible with libp2p's autorelay.PeerSource. The returned channel yields relay peers for AutoRelay to use. Safe to call before DHT is set (returns static relays only until DHT is available).

func (*RelayDiscovery) RelayAddrs

func (rd *RelayDiscovery) RelayAddrs() []string

RelayAddrs implements RelaySource. Returns multiaddr strings for all known relays (static + DHT discovered). Relays are ranked by a composite score combining health (latency + success rate) and budget availability (remaining session bytes). High-budget, healthy relays appear first.

func (*RelayDiscovery) SetBudgetChecker

func (rd *RelayDiscovery) SetBudgetChecker(gc RelayGrantChecker)

SetBudgetChecker provides a grant checker for budget-aware relay ranking. When set, RelayAddrs factors remaining session budget into relay scoring so high-budget relays are preferred over low-budget seed relays.

func (*RelayDiscovery) SetDHT

func (rd *RelayDiscovery) SetDHT(kdht *dht.IpfsDHT)

SetDHT provides the DHT for relay discovery. Called after DHT creation.

func (*RelayDiscovery) SetHealth

func (rd *RelayDiscovery) SetHealth(rh *RelayHealth)

SetHealth provides a health tracker for relay scoring. When set, RelayAddrs returns addresses ranked by health score (best first).

func (*RelayDiscovery) SetHost

func (rd *RelayDiscovery) SetHost(h host.Host)

SetHost provides the host for DHT operations.

func (*RelayDiscovery) StartDiscoveryLoop

func (rd *RelayDiscovery) StartDiscoveryLoop(ctx context.Context, interval time.Duration)

StartDiscoveryLoop runs periodic relay discovery in the background.

type RelayGrantChecker

type RelayGrantChecker interface {
	// GrantStatus returns grant info for a relay. ok=false if no cached/valid grant.
	GrantStatus(relayID peer.ID) (remaining time.Duration, budget int64, sessionDuration time.Duration, ok bool)
	// HasSufficientBudget checks if the session budget can handle fileSize.
	HasSufficientBudget(relayID peer.ID, fileSize int64, direction string) bool
	// TrackCircuitBytes increments the byte counter for a relay circuit.
	TrackCircuitBytes(relayID peer.ID, direction string, n int64)
	// ResetCircuitCounters resets per-circuit byte counters (new circuit).
	ResetCircuitCounters(relayID peer.ID)
}

RelayGrantChecker provides relay grant information for transfer decisions. Implemented by grants.GrantCache via structural typing (no import needed).

type RelayHealth

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

RelayHealth tracks relay peer health using EWMA scoring. Probes relays periodically and scores them on success rate, RTT, and freshness.

func NewRelayHealth

func NewRelayHealth(h host.Host, m *Metrics) *RelayHealth

NewRelayHealth creates a new relay health tracker.

func (*RelayHealth) ProbeAll

func (rh *RelayHealth) ProbeAll(ctx context.Context) int

ProbeAll pings all registered relays and updates their scores. Returns the number of successful probes.

func (*RelayHealth) Ranked

func (rh *RelayHealth) Ranked() []RelayHealthScore

Ranked returns all relay health scores sorted highest-first.

func (*RelayHealth) RecordFailure

func (rh *RelayHealth) RecordFailure(peerID peer.ID)

RecordFailure records a failed probe.

func (*RelayHealth) RecordSuccess

func (rh *RelayHealth) RecordSuccess(peerID peer.ID, rttMs float64)

RecordSuccess records a successful probe with the measured RTT.

func (*RelayHealth) RegisterRelay

func (rh *RelayHealth) RegisterRelay(peerID peer.ID, isStatic bool)

RegisterRelay adds a relay to the health tracker. Safe to call multiple times.

func (*RelayHealth) Score

func (rh *RelayHealth) Score(peerID peer.ID) float64

Score returns the health score for a relay. Returns defaultScore for unknown relays.

func (*RelayHealth) Start

func (rh *RelayHealth) Start(ctx context.Context, interval time.Duration)

Start runs periodic health probes in the background.

type RelayHealthScore

type RelayHealthScore struct {
	PeerID      peer.ID   `json:"peer_id"`
	Score       float64   `json:"score"`        // 0.0 (dead) to 1.0 (perfect)
	RTTMs       float64   `json:"rtt_ms"`       // EWMA of ping RTT
	SuccessRate float64   `json:"success_rate"` // EWMA of probe success (0-1)
	LastProbe   time.Time `json:"last_probe"`
	LastSuccess time.Time `json:"last_success"`
	ProbeCount  int       `json:"probe_count"`
	IsStatic    bool      `json:"is_static"`
}

RelayHealthScore tracks the health of a single relay peer.

type RelaySource

type RelaySource interface {
	RelayAddrs() []string
}

RelaySource provides relay addresses to PathDialer. Implementations return the current set of relay multiaddrs.

type RemoteError

type RemoteError struct {
	Message string
}

RemoteError wraps an error message returned by a remote peer.

func (*RemoteError) Error

func (e *RemoteError) Error() string

type RemoteServiceInfo

type RemoteServiceInfo struct {
	Name     string `json:"name"`
	Protocol string `json:"protocol"`
	Enabled  bool   `json:"enabled"`
}

RemoteServiceInfo is the public-safe subset of a service returned to remote peers. LocalAddress is deliberately omitted (security: never expose internal topology).

func QueryPeerServices

func QueryPeerServices(s network.Stream) ([]RemoteServiceInfo, error)

QueryPeerServices queries a remote peer's services via the service-query protocol.

type Resolver

type Resolver interface {
	Resolve(name string) (peer.ID, error)
}

Resolver resolves human-readable names to peer IDs. Implementations can chain: local config -> DNS -> DHT -> blockchain.

The concrete *NameResolver satisfies this interface.

type STUNProber

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

STUNProber discovers external address mappings via STUN (RFC 5389 Binding Request). It probes multiple servers to determine the external IP:port and NAT type.

func NewSTUNProber

func NewSTUNProber(servers []string, m *Metrics) *STUNProber

NewSTUNProber creates a STUNProber. If servers is empty, defaults are used. Metrics is optional (nil-safe).

func (*STUNProber) Probe

func (sp *STUNProber) Probe(ctx context.Context) (*STUNResult, error)

Probe sends STUN Binding Requests to all configured servers concurrently and determines NAT type from the results.

func (*STUNProber) Result

func (sp *STUNProber) Result() *STUNResult

Result returns the most recent probe result (thread-safe).

type STUNResult

type STUNResult struct {
	Probes        []ProbeResult `json:"probes"`
	NATType       NATType       `json:"nat_type"`
	ExternalAddrs []string      `json:"external_addrs"`
	ProbedAt      time.Time     `json:"probed_at"`
	BehindCGNAT   bool          `json:"behind_cgnat,omitempty"`
	CGNATNote     string        `json:"cgnat_note,omitempty"`
}

STUNResult is the aggregate result of probing multiple STUN servers.

func (*STUNResult) DetectCGNAT

func (r *STUNResult) DetectCGNAT(forceCGNAT bool)

DetectCGNAT checks for carrier-grade NAT. It first respects the force flag (for carriers using RFC 1918 addresses where auto-detection is impossible), then checks local interfaces for RFC 6598 CGNAT addresses (100.64.0.0/10).

Mobile carriers using 172.16-31.x.x for CGNAT cannot be distinguished from regular home networks. Users on those carriers should set force_cgnat: true in their config.

type Service

type Service struct {
	Name         string               // Service name (e.g., "ssh", "file-transfer")
	Protocol     string               // libp2p protocol ID (e.g., "/shurli/ssh/1.0.0")
	LocalAddress string               // TCP proxy target (e.g., "localhost:22"). Mutually exclusive with Handler.
	Handler      StreamHandler        // Custom stream handler for plugins. Mutually exclusive with LocalAddress.
	Enabled      bool                 // Whether this service is enabled
	AllowedPeers map[peer.ID]struct{} // Per-service ACL (nil = all authorized peers allowed). Used by TCP proxy path.
	Policy       *PluginPolicy        // Transport + peer restrictions (nil = no policy, backward compat for TCP proxies).
}

Service represents a service that can be exposed over the P2P network. Two modes are supported:

  • TCP proxy: set LocalAddress to proxy streams to a local TCP service
  • Custom handler: set Handler to process streams directly (for plugins)

LocalAddress and Handler are mutually exclusive. If both are set, Handler takes precedence.

type ServiceConn

type ServiceConn interface {
	io.ReadWriteCloser
	CloseWrite() error
}

ServiceConn represents a connection to a remote service

type ServiceManager

type ServiceManager interface {
	RegisterService(svc *Service) error
	UnregisterService(name string) error
	GetService(name string) (*Service, bool)
	ListServices() []*Service
	DialService(ctx context.Context, peerID peer.ID, protocolID string) (ServiceConn, error)

	// Use adds stream middleware. Middleware wraps every stream handler
	// in the order added (first added = outermost wrapper).
	Use(middleware ...StreamMiddleware)
}

ServiceManager manages service registration and dialing.

The concrete *ServiceRegistry satisfies this interface.

type ServiceRegistry

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

ServiceRegistry manages service registration and connections.

The callback fields (grantChecker, tokenVerifier, tokenLookup) follow a set-once-at-startup contract: they are configured via their Set* methods during daemon initialization, before any streams are handled. After setup, call Seal() to enforce the contract. The stream handler hot path reads callbacks without acquiring mu to avoid per-stream lock overhead. This is safe because the fields are never modified after Seal().

func NewServiceRegistry

func NewServiceRegistry(h host.Host, metrics *Metrics) *ServiceRegistry

NewServiceRegistry creates a new service registry. Pass nil for metrics to disable instrumentation.

func (*ServiceRegistry) DialService

func (r *ServiceRegistry) DialService(ctx context.Context, peerID peer.ID, protocolID string) (ServiceConn, error)

DialService connects to a remote peer's service. If the protocol matches a locally registered service with a PluginPolicy, the policy's transport restrictions are enforced.

func (*ServiceRegistry) GetService

func (r *ServiceRegistry) GetService(name string) (*Service, bool)

GetService retrieves a registered service by name

func (*ServiceRegistry) ListServices

func (r *ServiceRegistry) ListServices() []*Service

ListServices returns all registered services

func (*ServiceRegistry) RegisterService

func (r *ServiceRegistry) RegisterService(svc *Service) error

RegisterService registers a new service and sets up its stream handler

func (*ServiceRegistry) Seal

func (r *ServiceRegistry) Seal()

Seal marks the registry as fully configured. After Seal(), calling any Set* method panics. Call this after daemon initialization completes, before streams are handled. This enforces the set-once-at-startup contract.

func (*ServiceRegistry) SetGrantChecker

func (r *ServiceRegistry) SetGrantChecker(checker GrantChecker)

SetGrantChecker sets the grant checker function used for relay authorization. When a peer has a valid grant, relay transport is allowed for that peer+service even if the plugin's default policy is LAN+Direct only. Must be called before Seal().

func (*ServiceRegistry) SetLANRegistry

func (r *ServiceRegistry) SetLANRegistry(lanReg *LANRegistry)

SetLANRegistry wires the mDNS-verified LAN registry so that plugin-policy transport classification uses verified-LAN detection instead of bare RFC 1918 matching. Routed-private IPs (Starlink CGNAT, Docker, VPN, or multi-WAN routed-private subnets) correctly classify as Direct when the peer has no mDNS-verified connection. Must be called before Seal().

func (*ServiceRegistry) SetRelayGrantChecker

func (r *ServiceRegistry) SetRelayGrantChecker(checker RelayGrantChecker)

SetRelayGrantChecker sets the relay grant cache checker for outbound streams. When set and any relay has an active grant receipt, relay transport is allowed for outbound plugin streams. Bridges relay-side grants to client-side policy. Must be called before Seal().

func (*ServiceRegistry) SetTokenLookup

func (r *ServiceRegistry) SetTokenLookup(l TokenLookup)

SetTokenLookup sets the function used to retrieve grant tokens from the GrantPouch for outbound plugin streams (Phase B). Must be called before Seal().

func (*ServiceRegistry) SetTokenVerifier

func (r *ServiceRegistry) SetTokenVerifier(v TokenVerifier)

SetTokenVerifier sets the function used to verify presented grant tokens on inbound plugin streams (Phase B). The verifier decodes the base64 token and checks the macaroon HMAC chain + caveats. Must be called before Seal().

func (*ServiceRegistry) UnregisterService

func (r *ServiceRegistry) UnregisterService(name string) error

UnregisterService removes a service and its stream handler.

func (*ServiceRegistry) Use

func (r *ServiceRegistry) Use(middleware ...StreamMiddleware)

Use adds stream middleware that wraps every inbound stream handler. Middleware is applied in the order added (first added = outermost wrapper).

type StandaloneConfig

type StandaloneConfig struct {
	// ConfigPath is the explicit config file path (empty = auto-detect).
	ConfigPath string

	// Password is the identity key password (from session token or prompt).
	Password string

	// UserAgent is the libp2p user agent string (e.g., "shurli/1.0.0").
	UserAgent string
}

StandaloneConfig holds parameters for creating a standalone P2P host for one-shot CLI commands (ping, traceroute, proxy) that operate without a running daemon.

type StandaloneResult

type StandaloneResult struct {
	// Network is the P2P network instance. Caller must defer Network.Close().
	Network *Network

	// NodeConfig is the loaded and resolved node configuration.
	NodeConfig *config.NodeConfig

	// ConfigDir is the directory containing the config file,
	// useful for resolving relative paths.
	ConfigDir string
}

StandaloneResult holds the outputs of NewStandaloneHost.

func NewStandaloneHost

func NewStandaloneHost(cfg StandaloneConfig) (*StandaloneResult, error)

NewStandaloneHost creates a P2P Network from a config file for standalone CLI commands. It loads configuration, creates the libp2p host, and loads peer names. The caller is responsible for closing the returned Network.

func (*StandaloneResult) ResolveAndConnect

func (r *StandaloneResult) ResolveAndConnect(ctx context.Context, target string) (peer.ID, error)

ResolveAndConnect resolves a target name to a peer ID, bootstraps the DHT, and connects to the target. This consolidates the repeated boilerplate in standalone CLI commands (ping, traceroute) into a single library call.

After this returns, the caller can immediately use the host to communicate with the target peer. The caller is still responsible for closing the Network.

type StaticRelaySource

type StaticRelaySource struct {
	Addrs []string
}

StaticRelaySource wraps a fixed relay address list for backward compatibility.

func (*StaticRelaySource) RelayAddrs

func (s *StaticRelaySource) RelayAddrs() []string

RelayAddrs returns the static relay address list.

type StreamHandler

type StreamHandler func(serviceName string, s network.Stream)

StreamHandler processes an inbound libp2p stream for a named service.

func HandleServiceQuery

func HandleServiceQuery(registry *ServiceRegistry) StreamHandler

HandleServiceQuery returns a stream handler that responds with this node's enabled services. Only service name and protocol are exposed. Local addresses are never sent to remote peers.

type StreamMiddleware

type StreamMiddleware func(next StreamHandler) StreamHandler

StreamMiddleware wraps a stream handler to add cross-cutting behavior (compression, bandwidth limiting, progress tracking, audit trails).

The handler receives the raw libp2p stream and the service name. Call next to continue the chain; skip it to short-circuit.

type TCPListener

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

TCPListener creates a local TCP listener that forwards connections to a P2P service. Tracks active connections for graceful shutdown (F9) and limits concurrent connections via netutil.LimitListener (SEC-5).

func NewTCPListener

func NewTCPListener(localAddr string, dialFunc func() (ServiceConn, error)) (*TCPListener, error)

NewTCPListener creates a new TCP listener for a P2P service. Binds to localAddr and wraps with a connection limiter (SEC-5).

func (*TCPListener) ActiveConns

func (l *TCPListener) ActiveConns() int

ActiveConns returns the number of active proxy connections.

func (*TCPListener) Addr

func (l *TCPListener) Addr() net.Addr

Addr returns the listener's network address.

func (*TCPListener) Close

func (l *TCPListener) Close() error

Close closes the TCP listener (stops accepting new connections).

func (*TCPListener) GracefulClose

func (l *TCPListener) GracefulClose(timeout time.Duration)

GracefulClose closes the listener, sets a deadline on active connections, and waits for all handleConnection goroutines to finish (F9).

func (*TCPListener) Serve

func (l *TCPListener) Serve() error

Serve accepts connections and forwards them to the P2P service.

type TokenLookup

type TokenLookup func(peerID peer.ID, service string) string

TokenLookup retrieves a grant token from the GrantPouch for outbound presentation. Returns the base64-encoded token, or empty string if no token is available for the given peer and service.

type TokenVerifier

type TokenVerifier func(tokenBase64 string, peerID peer.ID, service string, transport TransportType) bool

TokenVerifier verifies a presented grant token (base64-encoded macaroon). Returns true if the token is valid for the given peer, service, and current stream transport. Injected by the daemon. Must do constant-time work on all code paths (valid, invalid, malformed) for D1 timing oracle mitigation.

type TraceHop

type TraceHop struct {
	Hop     int     `json:"hop"`
	PeerID  string  `json:"peer_id"`
	Name    string  `json:"name,omitempty"`    // friendly name if known
	Address string  `json:"address,omitempty"` // multiaddr of this hop
	RttMs   float64 `json:"rtt_ms"`
	Error   string  `json:"error,omitempty"`
}

TraceHop represents a single hop in a P2P traceroute.

type TraceResult

type TraceResult struct {
	Target   string     `json:"target"`
	TargetID string     `json:"target_id"`
	Path     string     `json:"path"` // "DIRECT" or "RELAYED"
	Hops     []TraceHop `json:"hops"`
}

TraceResult holds the full traceroute output.

func TracePeer

func TracePeer(ctx context.Context, h host.Host, targetPeerID peer.ID) (*TraceResult, error)

TracePeer traces the network path to a peer.

libp2p doesn't support TTL-based tracing, so this determines the path by inspecting connection metadata: is the connection direct or relayed? If relayed, it measures RTT to the relay and to the target separately to show per-hop latency - the information that actually matters for debugging.

type TransportType

type TransportType int

TransportType classifies how a peer connection is established. Used as a bitmask to express which transports a plugin permits.

const (
	// TransportLAN is a connection over the local network (private/link-local IP).
	TransportLAN TransportType = 1 << iota

	// TransportDirect is a connection over the public internet (non-relay, non-LAN).
	TransportDirect

	// TransportRelay is a connection mediated through a relay (p2p-circuit).
	TransportRelay
)

func ClassifyTransport

func ClassifyTransport(s network.Stream) TransportType

ClassifyTransport determines the transport type of a libp2p stream.

  • Limited connections (Stat().Limited) are classified as TransportRelay.
  • Private, loopback, or link-local IPs are classified as TransportLAN.
  • Everything else is TransportDirect.

Note: two LAN machines connected via public IPv6 will be classified as TransportDirect here. Use VerifiedTransport for trust-making decisions — it catches mDNS-verified LAN peers regardless of the stream's IP family, and correctly handles routed-private addresses (CGNAT, Docker, VPN, multi-WAN cross-links) that this bare-mask check misclassifies.

func VerifiedTransport

func VerifiedTransport(s network.Stream, hasVerifiedLANConn func(peer.ID) bool) TransportType

VerifiedTransport classifies a stream using mDNS-verified LAN detection.

Precedence:

  • Limited stream (relay circuit) -> TransportRelay
  • Loopback or link-local remote -> TransportLAN (cannot traverse routers)
  • hasVerifiedLANConn returns true for the peer -> TransportLAN
  • Otherwise -> TransportDirect

This is the correct classifier for any trust-making decision (transport policy enforcement, erasure coding, bandwidth budgets). Unlike ClassifyTransport, it does NOT classify routed private IPv4 as LAN just because it matches RFC 1918 — bare-mask misclassifies Starlink CGNAT (10.1.x.x), Docker bridges (172.17-21.x.x), VPN tunnels, and multi-WAN routed-private subnets as LAN. Only mDNS-verified connections count for private-range addresses; mDNS multicast is link-local and cannot traverse routers, so its reception is the only reliable proof of LAN proximity.

Loopback (127.0.0.0/8, ::1) and link-local (169.254.0.0/16, fe80::/10) are treated as LAN without verification — by definition they cannot cross a router, so they are not in the bare-RFC1918 false-positive trap.

A nil hasVerifiedLANConn still classifies loopback and link-local as LAN; for routable private addresses it falls back to TransportDirect (conservative: treats every non-verified peer as WAN).

Directories

Path Synopsis
Package pqnoise implements a post-quantum Noise security transport for libp2p.
Package pqnoise implements a post-quantum Noise security transport for libp2p.

Jump to

Keyboard shortcuts

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