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
- Variables
- func AddRelayAddressesForPeerFunc(h host.Host, relayAddrs []string, target peer.ID) error
- func BidirectionalProxy(a, b HalfCloseConn, logPrefix string)
- func Blake3Sum(data []byte) [32]byte
- func BootstrapAndConnect(ctx context.Context, h host.Host, net *Network, target peer.ID, ...) error
- func BuildSTUNBindingRequest(txID [12]byte) []byte
- func BuildSTUNBindingResponse(txID [12]byte, ip net.IP, port int) []byte
- func ComputeFingerprint(a, b peer.ID) (emoji string, numeric string)
- func DHTProtocolPrefixForNamespace(namespace string) string
- func DialWithRetry(dialFunc func() (ServiceConn, error), maxRetries int) func() (ServiceConn, error)
- func FingerprintPrefix(a, b peer.ID) string
- func FormatBytes(b int64) string
- func HedgedOpenStream(ctx context.Context, n *Network, peerID peer.ID, serviceName string) (network.Stream, error)
- func HumanizeError(err string) string
- func InstrumentedBidirectionalProxy(a, b HalfCloseConn, service string, metrics *Metrics)
- func IsLANMultiaddr(addr ma.Multiaddr) bool
- func LoadOrCreateIdentity(path, password string) (crypto.PrivKey, error)
- func LogRelayDowngrade(conn network.Conn)
- func MerkleRoot(hashes [][32]byte) [32]byte
- func MustValidateProtocolIDs(ids ...string)
- func OpenStreamOnConn(ctx context.Context, conn network.Conn, proto protocol.ID) (network.Stream, error)
- func ParseByteSize(s string) (int64, error)
- func ParseRelayAddrs(relayAddrs []string) ([]peer.AddrInfo, error)
- func PeerConnInfo(h host.Host, peerID peer.ID) (pathType string, addr string)
- func PeerIDFromKeyFile(path, password string) (peer.ID, error)
- func PingPeer(ctx context.Context, h host.Host, peerID peer.ID, protocolID string, count int, ...) <-chan PingResult
- func ProtocolID(name, version string) string
- func ProxyStreamToTCP(stream network.Stream, tcpAddr string) error
- func ReadGrantHeader(s network.Stream) (string, error)
- func RelayPeerFromAddr(addr ma.Multiaddr) peer.ID
- func RelayPeerFromAddrStr(addrStr string) string
- func RelayServiceCID(namespace string) cid.Cid
- func ResolveDNSSeeds(ctx context.Context, domain string) []peer.AddrInfo
- func StripNonLANAddrs(h host.Host, pid peer.ID, lanReg *LANRegistry)
- func ValidateProtocolID(id string) error
- func ValidateServiceName(name string) error
- func WriteGrantHeader(s network.Stream, tokenBase64 string) error
- type AuditLogger
- type Authorizer
- type BandwidthTracker
- func (bt *BandwidthTracker) AllPeerStats() map[peer.ID]metrics.Stats
- func (bt *BandwidthTracker) Counter() *metrics.BandwidthCounter
- func (bt *BandwidthTracker) PeerStats(p peer.ID) metrics.Stats
- func (bt *BandwidthTracker) ProtocolStats(proto protocol.ID) metrics.Stats
- func (bt *BandwidthTracker) PublishMetrics()
- func (bt *BandwidthTracker) Start(ctx context.Context, interval time.Duration)
- func (bt *BandwidthTracker) Totals() metrics.Stats
- type BootstrapConfig
- type Config
- type ConnGroup
- type ConnectionRecorder
- type DialResult
- type Event
- type EventBus
- type EventHandler
- type EventType
- type GrantChecker
- type HalfCloseConn
- type HostNetwork
- type InterfaceInfo
- type InterfaceSummary
- type LANRegistry
- type MDNSDiscovery
- type ManagedPathInfo
- type ManagedPeer
- type ManagedPeerInfo
- type Metrics
- type NATType
- type NameResolver
- func (r *NameResolver) List() map[string]peer.ID
- func (r *NameResolver) LoadFromMap(names map[string]string) error
- func (r *NameResolver) Register(name string, peerID peer.ID) error
- func (r *NameResolver) ReplaceFromMap(names map[string]string) error
- func (r *NameResolver) Resolve(name string) (peer.ID, error)
- func (r *NameResolver) Unregister(name string)
- type NetIntel
- type Network
- func (n *Network) AddRelayAddressesForPeer(relayAddrs []string, targetPeerID peer.ID) error
- func (n *Network) ClearDialBackoffs(peers []peer.ID)
- func (n *Network) Close() error
- func (n *Network) ConnectToService(peerID peer.ID, serviceName string) (ServiceConn, error)
- func (n *Network) ConnectToServiceContext(ctx context.Context, peerID peer.ID, serviceName string) (ServiceConn, error)
- func (n *Network) Events() *EventBus
- func (n *Network) ExposeService(name, localAddress string, allowedPeers map[peer.ID]struct{}) error
- func (n *Network) GetLANRegistry() *LANRegistry
- func (n *Network) GetPathProtector() *PathProtector
- func (n *Network) HasVerifiedLANConn(id peer.ID) bool
- func (n *Network) Host() host.Host
- func (n *Network) ListNames() map[string]peer.ID
- func (n *Network) ListServices() []*Service
- func (n *Network) LoadNames(names map[string]string) error
- func (n *Network) OnEvent(handler EventHandler) func()
- func (n *Network) OpenPluginStream(ctx context.Context, peerID peer.ID, serviceName string) (network.Stream, error)
- func (n *Network) OpenPluginStreamOnConn(ctx context.Context, peerID peer.ID, serviceName string, conn network.Conn) (network.Stream, error)
- func (n *Network) PeerID() peer.ID
- func (n *Network) RegisterHandler(name string, handler StreamHandler, allowedPeers map[peer.ID]struct{}) error
- func (n *Network) RegisterHandlerRelayAllowed(name string, handler StreamHandler, allowedPeers map[peer.ID]struct{}) error
- func (n *Network) RegisterName(name string, peerID peer.ID) error
- func (n *Network) RegisterServiceQuery() error
- func (n *Network) ReplaceNames(names map[string]string) error
- func (n *Network) ResetBlackHoles()
- func (n *Network) ResolveName(name string) (peer.ID, error)
- func (n *Network) ServiceRegistry() *ServiceRegistry
- func (n *Network) SetPathProtector(pp *PathProtector)
- func (n *Network) UnexposeService(name string) error
- type NetworkChange
- type NetworkMonitor
- type NodeAnnouncement
- type NodeStateProvider
- type PQCConnInfo
- type PQCStatus
- type PathDialer
- type PathProtector
- func (pp *PathProtector) Close()
- func (pp *PathProtector) ForceUnprotectAll(pid peer.ID)
- func (pp *PathProtector) IsProtected(pid peer.ID) bool
- func (pp *PathProtector) ManagedConnsForCancel(peerID peer.ID) []network.Conn
- func (pp *PathProtector) ManagedGroups(peerID peer.ID) []ConnGroup
- func (pp *PathProtector) ManagedPaths() []ManagedPathInfo
- func (pp *PathProtector) Protect(pid peer.ID, tag string)
- func (pp *PathProtector) ProtectWithContext(ctx context.Context, pid peer.ID, tag string)
- func (pp *PathProtector) SetMetrics(m *Metrics)
- func (pp *PathProtector) Unprotect(pid peer.ID, tag string)
- type PathTracker
- type PathType
- type PeerAnnouncement
- type PeerFilter
- type PeerManager
- func (pm *PeerManager) Close()
- func (pm *PeerManager) CloseAllPeerConnections()
- func (pm *PeerManager) CloseStaleConnections(removedIPs []string)
- func (pm *PeerManager) GetLANRegistry() *LANRegistry
- func (pm *PeerManager) GetManagedPeers() []ManagedPeerInfo
- func (pm *PeerManager) OnNetworkChange()
- func (pm *PeerManager) OnWatchlistRemovedFunc() func(peer.ID)
- func (pm *PeerManager) ProbeAndUpgradeRelayed()
- func (pm *PeerManager) ReconnectPeer(pid peer.ID) bool
- func (pm *PeerManager) ResetBackoffsForNetworkChange()
- func (pm *PeerManager) ResetPeerBackoff(pid peer.ID)
- func (pm *PeerManager) SetConnGracePeriod(d time.Duration)
- func (pm *PeerManager) SetOnWatchlistRemoved(fn func(peer.ID))
- func (pm *PeerManager) SetPathProtector(pp *PathProtector)
- func (pm *PeerManager) SetWatchlist(peerIDs []peer.ID)
- func (pm *PeerManager) Start(ctx context.Context)
- func (pm *PeerManager) StripPrivateAddrs()
- func (pm *PeerManager) TriggerReconnect()
- type PeerNetwork
- type PeerPathInfo
- type PeerRelay
- type PeerRelayConfig
- type PingResult
- type PingStats
- type PluginPolicy
- type ProbeResult
- type ReachabilityGrade
- type RelayDiscovery
- func (rd *RelayDiscovery) Advertise(ctx context.Context, interval time.Duration)
- func (rd *RelayDiscovery) AllRelays() []peer.AddrInfo
- func (rd *RelayDiscovery) Discover(ctx context.Context, count int) []peer.AddrInfo
- func (rd *RelayDiscovery) PeerSource(ctx context.Context, numPeers int) <-chan peer.AddrInfo
- func (rd *RelayDiscovery) RelayAddrs() []string
- func (rd *RelayDiscovery) SetBudgetChecker(gc RelayGrantChecker)
- func (rd *RelayDiscovery) SetDHT(kdht *dht.IpfsDHT)
- func (rd *RelayDiscovery) SetHealth(rh *RelayHealth)
- func (rd *RelayDiscovery) SetHost(h host.Host)
- func (rd *RelayDiscovery) StartDiscoveryLoop(ctx context.Context, interval time.Duration)
- type RelayGrantChecker
- type RelayHealth
- func (rh *RelayHealth) ProbeAll(ctx context.Context) int
- func (rh *RelayHealth) Ranked() []RelayHealthScore
- func (rh *RelayHealth) RecordFailure(peerID peer.ID)
- func (rh *RelayHealth) RecordSuccess(peerID peer.ID, rttMs float64)
- func (rh *RelayHealth) RegisterRelay(peerID peer.ID, isStatic bool)
- func (rh *RelayHealth) Score(peerID peer.ID) float64
- func (rh *RelayHealth) Start(ctx context.Context, interval time.Duration)
- type RelayHealthScore
- type RelaySource
- type RemoteError
- type RemoteServiceInfo
- type Resolver
- type STUNProber
- type STUNResult
- type Service
- type ServiceConn
- type ServiceManager
- type ServiceRegistry
- func (r *ServiceRegistry) DialService(ctx context.Context, peerID peer.ID, protocolID string) (ServiceConn, error)
- func (r *ServiceRegistry) GetService(name string) (*Service, bool)
- func (r *ServiceRegistry) ListServices() []*Service
- func (r *ServiceRegistry) RegisterService(svc *Service) error
- func (r *ServiceRegistry) Seal()
- func (r *ServiceRegistry) SetGrantChecker(checker GrantChecker)
- func (r *ServiceRegistry) SetLANRegistry(lanReg *LANRegistry)
- func (r *ServiceRegistry) SetRelayGrantChecker(checker RelayGrantChecker)
- func (r *ServiceRegistry) SetTokenLookup(l TokenLookup)
- func (r *ServiceRegistry) SetTokenVerifier(v TokenVerifier)
- func (r *ServiceRegistry) UnregisterService(name string) error
- func (r *ServiceRegistry) Use(middleware ...StreamMiddleware)
- type StandaloneConfig
- type StandaloneResult
- type StaticRelaySource
- type StreamHandler
- type StreamMiddleware
- type TCPListener
- type TokenLookup
- type TokenVerifier
- type TraceHop
- type TraceResult
- type TransportType
Constants ¶
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 )
const ( GradeA = "A" GradeB = "B" GradeC = "C" GradeD = "D" GradeF = "F" )
Grade constants ordered from best to worst.
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.
const DefaultMaxProxyConns = 64
DefaultMaxProxyConns is the per-proxy connection limit (SEC-5). Protects both local TCP stack and remote peer's stream limits.
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.
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.
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 )
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" )
const ProtocolPrefix = "/shurli"
ProtocolPrefix is the base prefix for all Shurli application-layer protocols.
const RTTProbeProtocol = "/shurli/rtt-probe/1.0.0"
RTTProbeProtocol is the protocol ID used for traceroute RTT measurement.
const ServiceQueryProtocol = "/shurli/service-query/1.0.0"
ServiceQueryProtocol is the protocol ID for querying a peer's services.
Variables ¶
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") )
var DefaultSTUNServers = []string{
"stun.l.google.com:19302",
"stun.cloudflare.com:3478",
}
DefaultSTUNServers are well-known public STUN servers.
Functions ¶
func AddRelayAddressesForPeerFunc ¶
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 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 ¶
BuildSTUNBindingRequest creates a STUN Binding Request packet with the given transaction ID. Exported for testing.
func BuildSTUNBindingResponse ¶
BuildSTUNBindingResponse creates a STUN Binding Response with an XOR-MAPPED-ADDRESS attribute. Exported for testing.
func ComputeFingerprint ¶
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 ¶
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 ¶
FingerprintPrefix returns the first 8 hex chars of the SHA-256 fingerprint for storage in the verified attribute.
func FormatBytes ¶
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 ¶
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 ¶
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 ¶
LoadOrCreateIdentity loads an existing SHRL-encrypted identity or creates a new one.
func LogRelayDowngrade ¶ added in v0.4.1
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 ¶
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 ¶
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 ¶
ParseRelayAddrs parses relay multiaddrs into peer.AddrInfo slices. It deduplicates by peer ID and merges addresses for the same relay peer.
func PeerConnInfo ¶
PeerConnInfo returns the path type ("DIRECT" or "RELAYED") and best remote address for an existing connection to a peer. Prefers direct connections.
func PeerIDFromKeyFile ¶
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 ¶
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 ¶
ProxyStreamToTCP creates a bidirectional proxy between a libp2p stream and a local TCP service.
func ReadGrantHeader ¶
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 ¶
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 ¶
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 ¶
RelayServiceCID returns a deterministic CID for relay discovery on DHT. Namespace-aware: different private networks get different CIDs.
func ResolveDNSSeeds ¶
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 ¶
ValidateProtocolID checks whether id is a well-formed Shurli protocol ID. Returns nil if valid, or an error describing the problem.
func ValidateServiceName ¶
ValidateServiceName checks that a service name is safe for use in protocol IDs.
func WriteGrantHeader ¶
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 ¶
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 ¶
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 (*EventBus) Emit ¶
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 ¶
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 (*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 ¶
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 ¶
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.
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 ¶
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.
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 ¶
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.
type Network ¶
type Network struct {
// contains filtered or unexported fields
}
Network represents a P2P network instance
func (*Network) AddRelayAddressesForPeer ¶
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 ¶
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) ConnectToService ¶
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 ¶
Events returns the event bus for emitting events from external code (e.g., daemon, relay server). Not part of PeerNetwork interface.
func (*Network) ExposeService ¶
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 ¶
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) ListServices ¶
ListServices returns all registered services.
func (*Network) LoadNames ¶
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:
- Service registry lookup
- PeerAllowed policy check
- Transport policy check (BEFORE opening stream — better than OpenPluginStream's post-dial)
- Pouch token + relay grant checks
- Protocol negotiation via SelectProtoOrFail
- Grant header write
The conn parameter must belong to the target peer (verified).
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 ¶
RegisterName registers a local name mapping
func (*Network) RegisterServiceQuery ¶
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 ¶
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 ¶
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 ¶
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
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 ¶
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).
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.
type PeerAnnouncement ¶
type PeerAnnouncement struct {
PeerID peer.ID
Announcement NodeAnnouncement
ReceivedAt time.Time
}
PeerAnnouncement is a cached announcement from a remote peer.
type PeerFilter ¶
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) Enable ¶
Enable starts the circuit relay v2 service on this host. Returns nil if already enabled.
func (*PeerRelay) OnStateChange ¶
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 ¶
Discover queries the DHT for relay providers. Returns up to count peers.
func (*RelayDiscovery) PeerSource ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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).
Source Files
¶
- audit.go
- bandwidth.go
- bootstrap.go
- bytes.go
- connstream.go
- contracts.go
- dnsseed.go
- doc.go
- errors.go
- events.go
- gateway_linux.go
- grant_header.go
- hedged.go
- identity.go
- interfaces.go
- managed_conn.go
- mdns.go
- mdns_browse_native.go
- merkle.go
- metrics.go
- naming.go
- netintel.go
- netmonitor.go
- netmonitor_linux.go
- netmonitor_poll.go
- network.go
- pathdialer.go
- pathprotector.go
- pathtracker.go
- peermanager.go
- peerrelay.go
- ping.go
- plugin_policy.go
- pqc.go
- protocolid.go
- proxy.go
- reachability.go
- relay_utils.go
- relaydiscovery.go
- relayhealth.go
- service.go
- service_query.go
- standalone.go
- stunprober.go
- traceroute.go
- verify.go