Documentation
¶
Overview ¶
Package ssu2 provides SSU2-specific implementations for the Noise Protocol Framework supporting I2P's SSU2 transport protocol with UDP-based connections and NAT traversal.
Resource Management ¶
This package creates managers with background goroutines for cleanup and maintenance:
- HolePunchCoordinator (cleanup every 30s)
- PeerTestManager (cleanup every 60s)
- RelayManager (cleanup timer-based)
IMPORTANT: Callers MUST call Stop() on these managers when done to prevent goroutine leaks. Failure to call Stop() will cause background goroutines to run until process termination.
Index ¶
- Constants
- Variables
- func BuildPeerTestSignedData(bobHash data.Hash, aliceHash *data.Hash, version uint8, ...) ([]byte, error)
- func BuildRelayRequestSignedData(bobHash, charlieHash data.Hash, nonce, relayTag, timestamp uint32, ...) ([]byte, error)
- func BuildRelayResponseSignedData(bobHash data.Hash, nonce, timestamp uint32, version uint8, charliePort uint16, ...) ([]byte, error)
- func CompareNATTypes(nat1, nat2 NATType) int
- func DecodePathChallenge(block *SSU2Block) (uint64, error)
- func DecodePathResponse(block *SSU2Block) (uint64, error)
- func DescribeNATCapabilities(natType NATType) string
- func ExtractExternalAddress(result *TestResult) *net.UDPAddr
- func ExtractExternalPort(result *TestResult) uint16
- func HasPublicIP(natType NATType) bool
- func IsAddressConsistent(addr1, addr2 *net.UDPAddr) bool
- func IsDirectlyReachable(result *TestResult) bool
- func IsIPConsistent(addr1, addr2 *net.UDPAddr) bool
- func IsPortConsistent(addr1, addr2 *net.UDPAddr) bool
- func IsReachableViaRelay(result *TestResult) bool
- func IsSymmetricNAT(natType NATType) bool
- func IsValidSourcePort(addr *net.UDPAddr) bool
- func NonceConnectionIDs(nonce uint32) (dest, src uint64)
- func RequiresRelay(natType NATType) bool
- func SignPeerTest(privateKey ed25519.PrivateKey, bobHash data.Hash, aliceHash *data.Hash, ...) ([]byte, error)
- func SignRelayRequest(privateKey ed25519.PrivateKey, bobHash, charlieHash data.Hash, ...) ([]byte, error)
- func SignRelayResponse(privateKey ed25519.PrivateKey, bobHash data.Hash, nonce, timestamp uint32, ...) ([]byte, error)
- func ValidateTestResult(result *TestResult) error
- func VerifyPeerTestSignature(publicKey ed25519.PublicKey, signature []byte, bobHash data.Hash, ...) (bool, error)
- func VerifyRelayRequestSignature(publicKey ed25519.PublicKey, signature []byte, bobHash, charlieHash data.Hash, ...) (bool, error)
- func VerifyRelayResponseSignature(publicKey ed25519.PublicKey, signature []byte, bobHash data.Hash, ...) (bool, error)
- type CongestionControllerAccessor
- type HolePunchAttempt
- type HolePunchCoordinator
- func (hpc *HolePunchCoordinator) CleanupExpired()
- func (hpc *HolePunchCoordinator) CompleteHolePunch(sessionID uint64) error
- func (hpc *HolePunchCoordinator) FailHolePunch(sessionID uint64, reason error) error
- func (hpc *HolePunchCoordinator) GetAttempt(sessionID uint64) *HolePunchAttempt
- func (hpc *HolePunchCoordinator) GetStats() map[string]int
- func (hpc *HolePunchCoordinator) HandleHolePunch(sessionID uint64, fromAddr *net.UDPAddr, block *RelayIntroBlock, ...) error
- func (hpc *HolePunchCoordinator) InitiateHolePunch(remoteAddr, introducerAddr *net.UDPAddr, relayTag uint32) (uint64, error)
- func (hpc *HolePunchCoordinator) ProcessHolePunchResponse(sessionID uint64, addr *net.UDPAddr, block *RelayIntroBlock, ...) error
- func (hpc *HolePunchCoordinator) RemoveAttempt(sessionID uint64)
- func (hpc *HolePunchCoordinator) RetryHolePunch(sessionID uint64) error
- func (hpc *HolePunchCoordinator) SendHolePunch(sessionID uint64, targetAddr *net.UDPAddr) error
- func (hpc *HolePunchCoordinator) SetAttemptStartTime(sessionID uint64, t time.Time)
- func (hpc *HolePunchCoordinator) SetAttemptState(sessionID uint64, state HolePunchState)
- func (hpc *HolePunchCoordinator) Stop()
- type HolePunchState
- type IntroducerInfo
- type IntroducerRegistry
- func (ir *IntroducerRegistry) AddIntroducer(info *RegisteredIntroducer) error
- func (ir *IntroducerRegistry) GetCount() int
- func (ir *IntroducerRegistry) GetIntroducers() []*RegisteredIntroducer
- func (ir *IntroducerRegistry) GetMaxCount() int
- func (ir *IntroducerRegistry) RemoveIntroducer(addr *net.UDPAddr)
- func (ir *IntroducerRegistry) SelectBestIntroducers(count int) []*RegisteredIntroducer
- func (ir *IntroducerRegistry) UpdateLastSeen(addr *net.UDPAddr)
- type ListenerRef
- type NATType
- type PathChallenge
- type PathChallengeState
- type PathValidationConn
- type PathValidator
- func (pv *PathValidator) CleanupExpired() int
- func (pv *PathValidator) CompleteMTUProbe(challengeID uint64)
- func (pv *PathValidator) FailPath(challengeID uint64, reason error)
- func (pv *PathValidator) GetChallenge(challengeID uint64) (*PathChallenge, bool)
- func (pv *PathValidator) GetDiscoveredMTU() int
- func (pv *PathValidator) HandlePathChallenge(block *SSU2Block, fromAddr *net.UDPAddr) error
- func (pv *PathValidator) HandlePathResponse(block *SSU2Block, fromAddr *net.UDPAddr) error
- func (pv *PathValidator) InitiateMTUProbe(addr *net.UDPAddr, size int) (uint64, error)
- func (pv *PathValidator) InitiatePathValidation(newAddr *net.UDPAddr) (uint64, error)
- func (pv *PathValidator) RunPMTUD(ctx context.Context, addr *net.UDPAddr, low, high int) int
- func (pv *PathValidator) SendPathChallenge(challengeID uint64, addr *net.UDPAddr) error
- func (pv *PathValidator) SendPathResponse(challengeID uint64, addr *net.UDPAddr) error
- func (pv *PathValidator) SetCongestionController(cc CongestionControllerAccessor)
- func (pv *PathValidator) SetTokenCache(tc TokenCacheAccessor)
- func (pv *PathValidator) Stop()
- func (pv *PathValidator) ValidatePath(challengeID uint64) error
- type PeerTest
- type PeerTestBlock
- type PeerTestManager
- func (ptm *PeerTestManager) CleanupExpired()
- func (ptm *PeerTestManager) CompleteTest(nonce uint32, result *TestResult) error
- func (ptm *PeerTestManager) CreateRelayTest(nonce uint32, aliceAddr, charlieAddr *net.UDPAddr) (uint32, error)
- func (ptm *PeerTestManager) CreateResponderTest(nonce uint32, aliceAddr, bobAddr *net.UDPAddr) error
- func (ptm *PeerTestManager) FailTest(nonce uint32, reason error) error
- func (ptm *PeerTestManager) GetListener() ListenerRef
- func (ptm *PeerTestManager) GetResult(addr *net.UDPAddr) *TestResult
- func (ptm *PeerTestManager) GetResultsMap() map[string]*TestResult
- func (ptm *PeerTestManager) GetStats() map[string]int
- func (ptm *PeerTestManager) GetTest(nonce uint32) *PeerTest
- func (ptm *PeerTestManager) GetTestsMap() map[uint32]*PeerTest
- func (ptm *PeerTestManager) InitiatePeerTest(bobAddr *net.UDPAddr) (uint32, error)
- func (ptm *PeerTestManager) RemoveTest(nonce uint32)
- func (ptm *PeerTestManager) SetAliceAddr(nonce uint32, addr *net.UDPAddr) error
- func (ptm *PeerTestManager) SetRawResult(key string, result *TestResult)
- func (ptm *PeerTestManager) SetTestAliceAddr(nonce uint32, addr *net.UDPAddr)
- func (ptm *PeerTestManager) SetTestStartTime(nonce uint32, t time.Time)
- func (ptm *PeerTestManager) Stop()
- func (ptm *PeerTestManager) UpdateState(nonce uint32, state PeerTestState) error
- type PeerTestMessageCode
- type PeerTestRole
- type PeerTestState
- type PendingSession
- type PendingSessionRegistry
- type RegisteredIntroducer
- type RelayIntroBlock
- type RelayManager
- func (rm *RelayManager) AddPendingSession(sessionID uint64, remoteAddr, introducerAddr *net.UDPAddr, relayTag uint32) error
- func (rm *RelayManager) AllocateRelayTag(addr *net.UDPAddr) (uint32, error)
- func (rm *RelayManager) CleanupExpired()
- func (rm *RelayManager) ExpireAllIntroducers()
- func (rm *RelayManager) GetAllIntroducers() []*IntroducerInfo
- func (rm *RelayManager) GetIntroducers() []*IntroducerInfo
- func (rm *RelayManager) GetListener() ListenerRef
- func (rm *RelayManager) GetPendingSession(sessionID uint64) *PendingSession
- func (rm *RelayManager) GetPendingSessionsMap() map[uint64]*PendingSession
- func (rm *RelayManager) GetRelayTag(tag uint32) *RelayTag
- func (rm *RelayManager) GetRelayTagsMap() map[uint32]*RelayTag
- func (rm *RelayManager) GetStats() map[string]int
- func (rm *RelayManager) IncrementRetries(sessionID uint64) int
- func (rm *RelayManager) RegisterIntroducer(addr *net.UDPAddr, routerHash data.Hash, relayTag uint32) error
- func (rm *RelayManager) RemoveIntroducer(addr *net.UDPAddr)
- func (rm *RelayManager) RemovePendingSession(sessionID uint64)
- func (rm *RelayManager) SetIntroducerExpiry(idx int, t time.Time)
- func (rm *RelayManager) SetRelayTagExpiry(tag uint32, t time.Time)
- func (rm *RelayManager) Start()
- func (rm *RelayManager) Stop()
- func (rm *RelayManager) ValidateRelayTag(tag uint32, addr *net.UDPAddr) bool
- type RelayRequestBlock
- type RelayResponseBlock
- type RelayTag
- type RelayTagBlock
- type RelayTagRequestBlock
- type SSU2Block
- func EncodePathChallenge(challengeID uint64) *SSU2Block
- func EncodePathChallengeWithPadding(challengeID uint64, probeSize int) *SSU2Block
- func EncodePathResponse(challengeID uint64) *SSU2Block
- func EncodePeerTestBlock(block *PeerTestBlock) (*SSU2Block, error)
- func EncodeRelayIntro(intro *RelayIntroBlock) (*SSU2Block, error)
- func EncodeRelayRequest(req *RelayRequestBlock) (*SSU2Block, error)
- func EncodeRelayResponse(resp *RelayResponseBlock) (*SSU2Block, error)
- func EncodeRelayTag(tag *RelayTagBlock) (*SSU2Block, error)
- func EncodeRelayTagRequest(req *RelayTagRequestBlock) (*SSU2Block, error)
- type SSU2Packet
- type TestResult
- type TokenCacheAccessor
Constants ¶
const ( // PathValidationTimeout is how long to wait for path response. // BUG-L04: 10 seconds chosen per I2P SSU2 spec §Connection Migration. // Shorter than hole punch (30s) and peer test (60s) because path // validation occurs mid-connection and requires tighter feedback. PathValidationTimeout = 10 * time.Second // PathValidationCleanupInterval is how often to clean up expired challenges. // BUG-L04: 30 seconds = 3× PathValidationTimeout, balancing cleanup cost // against memory pressure from abandoned challenges (added in BUG-M07 fix). PathValidationCleanupInterval = 30 * time.Second )
Path validation timeouts
const ( // MinMTU is the minimum MTU for SSU2 (spec-defined floor). MinMTU = 1280 // MaxMTU is the upper bound for MTU probing. MaxMTU = 1500 // MTUProbeStep is the size increment between probe steps. MTUProbeStep = 20 )
MTU probing constants (G-5).
const ( // RelayRequestPrologue is prepended to signed data for relay requests. // 16 bytes, not null-terminated. RelayRequestPrologue = "RelayRequestData" // RelayAgreementPrologue is prepended to signed data for relay responses. // 16 bytes, not null-terminated. RelayAgreementPrologue = "RelayAgreementOK" )
Relay signature prologues per SSU2 spec §Relay Request and §Relay Response.
const ( BlockTypeRelayRequest = wire.BlockTypeRelayRequest BlockTypeRelayResponse = wire.BlockTypeRelayResponse BlockTypeRelayIntro = wire.BlockTypeRelayIntro BlockTypePeerTest = wire.BlockTypePeerTest BlockTypeAddress = wire.BlockTypeAddress BlockTypePathChallenge = wire.BlockTypePathChallenge BlockTypePathResponse = wire.BlockTypePathResponse BlockTypeRelayTag = wire.BlockTypeRelayTag BlockTypeNewToken = wire.BlockTypeNewToken BlockTypeRelayTagRequest = wire.BlockTypeRelayTagRequest )
Block type constants needed by path types
const HolePunchSessionTimeout = 30 * time.Second
HolePunchSessionTimeout is the maximum lifetime of a pending hole-punch session. Shared by HolePunchCoordinator and RelayManager cleanup loops so both sides age out sessions at the same rate. L-04 fix: single definition replaces the two duplicated 30*time.Second literals.
const MaxClockSkew = 5 * time.Minute
MaxClockSkew is the maximum allowed age (or future offset) of a signed timestamp. Blocks outside this window are rejected to prevent replay attacks.
const PeerTestPrologue = "PeerTestValidate"
PeerTestPrologue is prepended to signed data for peer test messages. 16 bytes, not null-terminated, per SSU2 spec §Peer Test.
const RelayResponseTokenTTL = 10 * time.Second
RelayResponseTokenTTL is the maximum lifetime for a relay response token. Per SSU2 spec, "The token must be used immediately by Alice in the Session Request." We enforce a 10-second window to allow for network latency while still requiring near-immediate use.
Variables ¶
var NewSSU2Block = wire.NewSSU2Block
Constructor alias
Functions ¶
func BuildPeerTestSignedData ¶
func BuildPeerTestSignedData( bobHash data.Hash, aliceHash *data.Hash, version uint8, nonce, timestamp uint32, alicePort uint16, aliceIP net.IP, ) ([]byte, error)
BuildPeerTestSignedData constructs the data to be signed for a peer test message.
Per SSU2 spec §Peer Test, the signed data is:
- prologue: 16 bytes "PeerTestValidate"
- bhash: Bob's 32-byte router hash
- ahash: Alice's 32-byte router hash (only for messages 3 and 4)
- ver: 1 byte
- nonce: 4 bytes
- timestamp: 4 bytes
- asz: 1 byte (6 or 18)
- AlicePort: 2 bytes
- AliceIP: (asz-2) bytes
Set aliceHash to nil for messages 1 and 2.
func BuildRelayRequestSignedData ¶
func BuildRelayRequestSignedData( bobHash, charlieHash data.Hash, nonce, relayTag, timestamp uint32, version uint8, alicePort uint16, aliceIP net.IP, ) ([]byte, error)
BuildRelayRequestSignedData constructs the data to be signed for a relay request.
Per SSU2 spec §Relay Request, the signed data is:
- prologue: 16 bytes "RelayRequestData"
- bhash: Bob's 32-byte router hash
- chash: Charlie's 32-byte router hash
- nonce: 4 bytes
- relay tag: 4 bytes
- timestamp: 4 bytes
- ver: 1 byte
- asz: 1 byte (6 for IPv4, 18 for IPv6)
- AlicePort: 2 bytes
- AliceIP: (asz-2) bytes
func BuildRelayResponseSignedData ¶
func BuildRelayResponseSignedData( bobHash data.Hash, nonce, timestamp uint32, version uint8, charliePort uint16, charlieIP net.IP, ) ([]byte, error)
BuildRelayResponseSignedData constructs the data to be signed for a relay response.
Per SSU2 spec §Relay Response, the signed data is:
- prologue: 16 bytes "RelayAgreementOK"
- bhash: Bob's 32-byte router hash
- nonce: 4 bytes
- timestamp: 4 bytes
- ver: 1 byte
- csz: 1 byte (0, 6, or 18)
- CharliePort: 2 bytes (not present if csz is 0)
- CharlieIP: (csz-2) bytes (not present if csz is 0)
func CompareNATTypes ¶
CompareNATTypes determines if one NAT type is more restrictive than another.
Returns:
- -1 if nat1 is less restrictive than nat2
- 0 if equal restrictiveness
- +1 if nat1 is more restrictive than nat2
Restrictiveness order (least to most): NATNone < NATCone < NATRestricted < NATPortRestricted < NATSymmetric NATUnknown is incomparable (returns 0)
func DecodePathChallenge ¶
DecodePathChallenge decodes a Path Challenge block.
Parameters:
- block: SSU2Block with Type 18
Returns:
- uint64: The challenge ID
- error: If decoding fails
func DecodePathResponse ¶
DecodePathResponse decodes a Path Response block.
Parameters:
- block: SSU2Block with Type 19
Returns:
- uint64: The challenge ID
- error: If decoding fails
func DescribeNATCapabilities ¶
DescribeNATCapabilities returns a human-readable description of what connectivity is possible with the given NAT type.
This helps users understand the implications of their NAT type.
func ExtractExternalAddress ¶
func ExtractExternalAddress(result *TestResult) *net.UDPAddr
ExtractExternalAddress gets the external address from a test result. Returns the ExternalAddr field, or nil if result is nil.
This is a convenience function for safe access to external address.
func ExtractExternalPort ¶
func ExtractExternalPort(result *TestResult) uint16
ExtractExternalPort gets the external port from a test result. Returns the ExternalPort field, or 0 if result is nil.
This is a convenience function for safe access to external port.
func HasPublicIP ¶
HasPublicIP checks if the NAT type indicates a public IP. No NAT or full cone NAT typically indicates public accessibility.
Returns true if NAT type is NATNone or NATCone.
func IsAddressConsistent ¶
IsAddressConsistent checks if two addresses are completely equal. This combines IP and port consistency checking.
Returns true if both addresses exist and are equal, false otherwise (including nil addresses).
func IsDirectlyReachable ¶
func IsDirectlyReachable(result *TestResult) bool
IsDirectlyReachable checks if a peer is directly reachable based on test results.
A peer is considered directly reachable if the direct probe succeeded, indicating no restrictive NAT/firewall blocking incoming connections.
Returns true if result exists and direct probe succeeded.
func IsIPConsistent ¶
IsIPConsistent checks if two addresses use the same IP. This is used to detect multiple NATs or proxies in the path.
Returns true if both addresses exist and have equal IPs, false otherwise (including nil addresses).
func IsPortConsistent ¶
IsPortConsistent checks if two addresses use the same port. This is used to determine if NAT preserves port mappings.
Returns true if both addresses exist and have the same port, false otherwise (including nil addresses).
func IsReachableViaRelay ¶
func IsReachableViaRelay(result *TestResult) bool
IsReachableViaRelay checks if a peer is reachable via relay based on test results.
A peer is reachable via relay if the relayed probe succeeded, indicating the relay mechanism can establish connectivity.
Returns true if result exists and relayed probe succeeded.
func IsSymmetricNAT ¶
IsSymmetricNAT checks if the NAT type is symmetric. Symmetric NAT is the most restrictive type, requiring sophisticated traversal techniques.
Returns true if NAT type is NATSymmetric.
func IsValidSourcePort ¶
IsValidSourcePort checks if a UDP address has a valid (non-reserved) source port.
Port 0 is reserved by IANA and must not appear in peer test messages. Accepting port 0 could allow crafted packets to bypass connectivity checks or cause subtle failures in NAT traversal logic.
Returns true if addr is non-nil and port is in the range [1, 65535].
func NonceConnectionIDs ¶
NonceConnectionIDs derives deterministic connection IDs from a 4-byte nonce per spec: dest = (uint64(nonce) << 32) | uint64(nonce), src = ^dest. Used for out-of-session PeerTest (messages 5-7) and HolePunch packets.
func RequiresRelay ¶
RequiresRelay checks if the NAT type requires relay assistance for incoming connections.
Symmetric and port-restricted NATs typically require relay or hole punching for peer-to-peer connectivity.
Returns true if NAT type is symmetric or port-restricted.
func SignPeerTest ¶
func SignPeerTest( privateKey ed25519.PrivateKey, bobHash data.Hash, aliceHash *data.Hash, version uint8, nonce, timestamp uint32, alicePort uint16, aliceIP net.IP, ) ([]byte, error)
SignPeerTest signs peer test data using the signer's Ed25519 private key. For messages 1/2, aliceHash should be nil. For messages 3/4, aliceHash must be provided.
func SignRelayRequest ¶
func SignRelayRequest( privateKey ed25519.PrivateKey, bobHash, charlieHash data.Hash, nonce, relayTag, timestamp uint32, version uint8, alicePort uint16, aliceIP net.IP, ) ([]byte, error)
SignRelayRequest signs a relay request using Alice's Ed25519 private key.
func SignRelayResponse ¶
func SignRelayResponse( privateKey ed25519.PrivateKey, bobHash data.Hash, nonce, timestamp uint32, version uint8, charliePort uint16, charlieIP net.IP, ) ([]byte, error)
SignRelayResponse signs a relay response using the signer's Ed25519 private key. For accepted responses (code 0), Charlie signs. For Bob rejections (code 1-63), Bob signs.
func ValidateTestResult ¶
func ValidateTestResult(result *TestResult) error
ValidateTestResult checks if a TestResult has valid data.
A valid result must have: - At least one probe attempted (direct or relayed) - External address if any probe succeeded - Consistency flags properly set
Returns error if result is invalid, nil otherwise.
func VerifyPeerTestSignature ¶
func VerifyPeerTestSignature( publicKey ed25519.PublicKey, signature []byte, bobHash data.Hash, aliceHash *data.Hash, version uint8, nonce, timestamp uint32, alicePort uint16, aliceIP net.IP, ) (bool, error)
VerifyPeerTestSignature verifies a peer test signature. For messages 1/2, aliceHash should be nil. For messages 3/4, aliceHash must be provided.
func VerifyRelayRequestSignature ¶
func VerifyRelayRequestSignature( publicKey ed25519.PublicKey, signature []byte, bobHash, charlieHash data.Hash, nonce, relayTag, timestamp uint32, version uint8, alicePort uint16, aliceIP net.IP, ) (bool, error)
VerifyRelayRequestSignature verifies a relay request signature using Alice's Ed25519 public key.
func VerifyRelayResponseSignature ¶
func VerifyRelayResponseSignature( publicKey ed25519.PublicKey, signature []byte, bobHash data.Hash, nonce, timestamp uint32, version uint8, charliePort uint16, charlieIP net.IP, ) (bool, error)
VerifyRelayResponseSignature verifies a relay response signature. For accepted responses (code 0), use Charlie's public key. For Bob rejections (code 1-63), use Bob's public key.
Types ¶
type CongestionControllerAccessor ¶
type CongestionControllerAccessor interface {
Reset()
}
CongestionControllerAccessor provides congestion reset. Implemented by *ssu2/reliability.CongestionController.
type HolePunchAttempt ¶
type HolePunchAttempt struct {
// SessionID uniquely identifies this attempt
SessionID uint64
// RemoteAddr is the target peer's UDP address
RemoteAddr *net.UDPAddr
// Introducer is the introducer facilitating the hole punch
Introducer *net.UDPAddr
// State is the current state of the attempt
State HolePunchState
// StartTime is when the attempt was initiated
StartTime time.Time
// Retries is the number of retry attempts made
Retries int
// RelayTag is the tag for relay communication
RelayTag uint32
// FailureReason stores the error passed to FailHolePunch (H-03 fix).
// Nil if the attempt succeeded or has not yet failed.
FailureReason error
}
HolePunchAttempt represents an active hole punch operation.
type HolePunchCoordinator ¶
type HolePunchCoordinator struct {
// contains filtered or unexported fields
}
HolePunchCoordinator coordinates UDP hole punching for NAT traversal. It manages hole punch attempts with state tracking, retries, and timeout handling.
The HolePunch message (type 11) uses the same wire format as RelayIntro:
[Flag:1][SenderHash:32][Nonce:4][RelayTag:4][Timestamp:4][Ver:1][Asz:1][Port:2][IP:asz-2]
See RelayIntroBlock in relay_blocks.go for the encoder/decoder.
Design rationale: - Session IDs are cryptographically random 64-bit values for security - Maximum 3 retry attempts per I2P convention - 30-second timeout per attempt (I2P spec recommendation) - State machine: Requested → Sent → Waiting → Success/Failed - Signature verification is MANDATORY per SSU2 spec and must be provided at construction
Thread Safety: All public methods are thread-safe.
func NewHolePunchCoordinator ¶
func NewHolePunchCoordinator(manager PendingSessionRegistry, verifyFn func(block *RelayIntroBlock, signerKey ed25519.PublicKey) error) (*HolePunchCoordinator, error)
NewHolePunchCoordinator creates a new HolePunchCoordinator.
L-3 fix: Returns an error instead of panicking on nil verifyFn, following Go constructor conventions. Per SSU2 spec §Hole Punch, all messages must be cryptographically authenticated, so a nil verifier is a programming error.
Parameters:
- manager: The PendingSessionRegistry to coordinate with (typically *RelayManager)
- verifyFn: Function to verify HolePunch message signatures (MUST NOT be nil)
Returns a new HolePunchCoordinator, or an error if verifyFn is nil.
func (*HolePunchCoordinator) CleanupExpired ¶
func (hpc *HolePunchCoordinator) CleanupExpired()
CleanupExpired removes expired hole punch attempts. Attempts are considered expired after 30 seconds per I2P spec.
func (*HolePunchCoordinator) CompleteHolePunch ¶
func (hpc *HolePunchCoordinator) CompleteHolePunch(sessionID uint64) error
CompleteHolePunch marks a hole punch attempt as successfully completed.
Parameters:
- sessionID: Session identifier
Returns error if session not found.
func (*HolePunchCoordinator) FailHolePunch ¶
func (hpc *HolePunchCoordinator) FailHolePunch(sessionID uint64, reason error) error
FailHolePunch marks a hole punch attempt as failed with a reason.
Parameters:
- sessionID: Session identifier
- reason: Error explaining failure
Returns error if session not found.
func (*HolePunchCoordinator) GetAttempt ¶
func (hpc *HolePunchCoordinator) GetAttempt(sessionID uint64) *HolePunchAttempt
GetAttempt retrieves hole punch attempt information.
Parameters:
- sessionID: Session identifier
Returns attempt info, or nil if not found.
func (*HolePunchCoordinator) GetStats ¶
func (hpc *HolePunchCoordinator) GetStats() map[string]int
GetStats returns statistics about active hole punch attempts.
Returns a map with attempt counts by state.
func (*HolePunchCoordinator) HandleHolePunch ¶
func (hpc *HolePunchCoordinator) HandleHolePunch(sessionID uint64, fromAddr *net.UDPAddr, block *RelayIntroBlock, signerKey ed25519.PublicKey) error
HandleHolePunch processes an incoming hole punch packet from a remote peer. Per SSU2 spec §Hole Punch, the message's signature MUST be verified before processing. The block parameter MUST NOT be nil - signature verification is mandatory per the SSU2 specification. If VerifyHolePunchSignature is not set, the message is rejected to prevent unauthenticated state transitions.
Parameters:
- sessionID: Session identifier from the packet
- fromAddr: Address the packet came from
- block: The decoded RelayIntro-format block (MUST NOT be nil)
- signerKey: Ed25519 public key of the message signer
Returns error if session not found, block is nil, or signature verification fails. BUG-M03 fix: Clarified that block parameter cannot be nil.
func (*HolePunchCoordinator) InitiateHolePunch ¶
func (hpc *HolePunchCoordinator) InitiateHolePunch(remoteAddr, introducerAddr *net.UDPAddr, relayTag uint32) (uint64, error)
InitiateHolePunch starts a new hole punch attempt to reach a remote peer.
Design rationale: - Uses introducer to coordinate hole punch with target peer - Generates cryptographically random session ID - Registers pending session with RelayManager - 30-second timeout per I2P spec
Parameters:
- remoteAddr: Target peer's UDP address
- introducerAddr: Introducer's UDP address
- relayTag: Tag for relay communication
Returns session ID on success, error otherwise.
func (*HolePunchCoordinator) ProcessHolePunchResponse ¶
func (hpc *HolePunchCoordinator) ProcessHolePunchResponse(sessionID uint64, addr *net.UDPAddr, block *RelayIntroBlock, signerKey ed25519.PublicKey) error
ProcessHolePunchResponse processes a response to a hole punch attempt. Per SSU2 spec §Hole Punch, the response's signature MUST be verified. The block parameter MUST NOT be nil - signature verification is mandatory.
Parameters:
- sessionID: Session identifier
- addr: Address that responded
- block: The decoded RelayIntro-format block (MUST NOT be nil)
- signerKey: Ed25519 public key of the message signer
Returns error if session not found, block is nil, or signature verification fails. BUG-M03 fix: Clarified that block parameter cannot be nil.
func (*HolePunchCoordinator) RemoveAttempt ¶
func (hpc *HolePunchCoordinator) RemoveAttempt(sessionID uint64)
RemoveAttempt removes a hole punch attempt from tracking.
Parameters:
- sessionID: Session identifier
func (*HolePunchCoordinator) RetryHolePunch ¶
func (hpc *HolePunchCoordinator) RetryHolePunch(sessionID uint64) error
RetryHolePunch retries a failed hole punch attempt.
Parameters:
- sessionID: Session identifier
Returns error if session not found or max retries exceeded.
func (*HolePunchCoordinator) SendHolePunch ¶
func (hpc *HolePunchCoordinator) SendHolePunch(sessionID uint64, targetAddr *net.UDPAddr) error
SendHolePunch sends a hole punch packet to the target address.
Parameters:
- sessionID: Session identifier
- targetAddr: Target peer's UDP address
Returns error if session not found or send fails.
func (*HolePunchCoordinator) SetAttemptStartTime ¶
func (hpc *HolePunchCoordinator) SetAttemptStartTime(sessionID uint64, t time.Time)
SetAttemptStartTime sets the StartTime of an attempt (test helper).
func (*HolePunchCoordinator) SetAttemptState ¶
func (hpc *HolePunchCoordinator) SetAttemptState(sessionID uint64, state HolePunchState)
SetAttemptState sets the State of an attempt (test helper).
func (*HolePunchCoordinator) Stop ¶
func (hpc *HolePunchCoordinator) Stop()
Stop halts the background cleanup goroutine. Call when the coordinator is no longer needed to avoid goroutine leaks. Safe to call multiple times.
type HolePunchState ¶
type HolePunchState int
HolePunchState represents the state of a hole punch attempt.
const ( // HolePunchRequested indicates hole punch has been requested HolePunchRequested HolePunchState = iota // HolePunchSent indicates hole punch packet has been sent HolePunchSent // HolePunchWaiting indicates waiting for response HolePunchWaiting // HolePunchSuccess indicates hole punch succeeded HolePunchSuccess // HolePunchFailed indicates hole punch failed HolePunchFailed )
func (HolePunchState) String ¶
func (s HolePunchState) String() string
String returns human-readable state name.
type IntroducerInfo ¶
type IntroducerInfo struct {
// Addr is the UDP address of the introducer
Addr *net.UDPAddr
// RouterHash is the I2P router identity hash
RouterHash data.Hash
// RelayTag is the tag assigned by the introducer
RelayTag uint32
// ExpiresAt is when this introducer registration expires
ExpiresAt time.Time
}
IntroducerInfo represents an available introducer for NAT traversal.
type IntroducerRegistry ¶
type IntroducerRegistry struct {
// contains filtered or unexported fields
}
IntroducerRegistry maintains a list of introducers for publishing in RouterInfo. Introducers help peers behind NAT establish connections through relay mechanisms.
Design rationale: - Maximum 3 introducers per I2P specification - Introducers are sorted by last seen time for freshness - Thread-safe for concurrent access - Defensive copies prevent external mutation
Thread Safety: All public methods are thread-safe.
func NewIntroducerRegistry ¶
func NewIntroducerRegistry(maxCount int) *IntroducerRegistry
NewIntroducerRegistry creates a new IntroducerRegistry with the specified maximum count.
Parameters:
- maxCount: Maximum number of introducers to maintain (typically 3 per I2P spec)
Returns a new IntroducerRegistry.
func (*IntroducerRegistry) AddIntroducer ¶
func (ir *IntroducerRegistry) AddIntroducer(info *RegisteredIntroducer) error
AddIntroducer registers a new introducer or updates an existing one.
Design rationale: - Updates existing introducer if address matches - Removes oldest introducer when at capacity - Validates all required fields
Parameters:
- info: Introducer information to register
Returns error if validation fails.
func (*IntroducerRegistry) GetCount ¶
func (ir *IntroducerRegistry) GetCount() int
GetCount returns the current number of registered introducers.
func (*IntroducerRegistry) GetIntroducers ¶
func (ir *IntroducerRegistry) GetIntroducers() []*RegisteredIntroducer
GetIntroducers returns all registered introducers.
Returns a defensive copy of the introducer list.
func (*IntroducerRegistry) GetMaxCount ¶
func (ir *IntroducerRegistry) GetMaxCount() int
GetMaxCount returns the maximum number of introducers allowed.
func (*IntroducerRegistry) RemoveIntroducer ¶
func (ir *IntroducerRegistry) RemoveIntroducer(addr *net.UDPAddr)
RemoveIntroducer removes an introducer by address. Note: This method uses swap-and-truncate for O(1) removal, which does NOT preserve the order of remaining introducers. If the removed introducer is not the last element, the last introducer will be moved to the removed position.
Parameters:
- addr: UDP address of the introducer to remove
BUG-L03 fix: Documented order instability in removal operation.
func (*IntroducerRegistry) SelectBestIntroducers ¶
func (ir *IntroducerRegistry) SelectBestIntroducers(count int) []*RegisteredIntroducer
SelectBestIntroducers selects the best introducers based on recency.
Design rationale: - Returns introducers sorted by most recently seen - Returns up to count introducers - Ensures fresh introducers are prioritized
Parameters:
- count: Maximum number of introducers to select
Returns selected introducers (up to count).
func (*IntroducerRegistry) UpdateLastSeen ¶
func (ir *IntroducerRegistry) UpdateLastSeen(addr *net.UDPAddr)
UpdateLastSeen updates the last seen time for an introducer.
Parameters:
- addr: UDP address of the introducer
type ListenerRef ¶
type ListenerRef interface {
GetAddr() string
}
ListenerRef is an opaque reference to an SSU2Listener. It is stored by path types but not directly called. L-02 fix: GetAddr provides a minimal method so that wrong-type arguments are caught at compile time rather than silently accepted as interface{}.
type NATType ¶
type NATType int
NATType represents the type of NAT detected.
const ( // NATUnknown indicates NAT type is not yet determined NATUnknown NATType = iota // NATNone indicates no NAT (public IP) NATNone // NATCone indicates full cone NAT NATCone // NATRestricted indicates restricted cone NAT NATRestricted // NATPortRestricted indicates port-restricted cone NAT NATPortRestricted // NATSymmetric indicates symmetric NAT NATSymmetric )
func DetermineNATType ¶
func DetermineNATType(result *TestResult) NATType
DetermineNATType analyzes test results to determine NAT type.
Logic per I2P specification:
- Both probes succeed + consistent port/IP = No NAT or Full Cone
- Direct fails + relayed succeeds = Symmetric or Port-Restricted
- Port inconsistent = Symmetric NAT
- IP inconsistent = Multiple NATs or proxy
Parameters:
- result: Test result with probe outcomes
Returns determined NAT type.
func SelectBestNATType ¶
SelectBestNATType chooses the less restrictive NAT type from two options.
This is useful when multiple test results suggest different NAT types - we prefer the less restrictive interpretation to enable more connectivity options.
Returns the less restrictive NAT type, or nat1 if equal. BUG-012 fix: If one type is NATUnknown, prefer the known type.
func SelectWorstNATType ¶
SelectWorstNATType chooses the more restrictive NAT type from two options.
This is useful for conservative NAT detection - assuming the worst case ensures relay mechanisms are properly engaged.
Returns the more restrictive NAT type, or nat1 if equal. BUG-012 fix: If one type is NATUnknown, prefer the known type.
type PathChallenge ¶
type PathChallenge struct {
// ChallengeID uniquely identifies this validation (8 bytes)
ChallengeID uint64
// NewAddr is the new UDP address being validated
NewAddr *net.UDPAddr
// Timestamp is when the challenge was created
Timestamp time.Time
// State tracks the validation progress
State PathChallengeState
// ProbeSize is the total packet size this challenge probes (G-5).
// 0 for non-MTU challenges.
ProbeSize int
// contains filtered or unexported fields
}
PathChallenge represents an active path validation attempt.
type PathChallengeState ¶
type PathChallengeState int
PathChallengeState represents the state of a path validation attempt.
const ( // ChallengeSent indicates we sent a challenge, awaiting response ChallengeSent PathChallengeState = iota // ChallengeReceived indicates we received a challenge, need to respond ChallengeReceived // ChallengeValidated indicates successful bidirectional validation ChallengeValidated // ChallengeFailed indicates validation failed (timeout or error) ChallengeFailed )
func (PathChallengeState) String ¶
func (s PathChallengeState) String() string
String returns a human-readable representation of the challenge state.
type PathValidationConn ¶
type PathValidationConn interface {
// SendToAddress sends a block to a specific UDP address
SendToAddress(block *SSU2Block, addr *net.UDPAddr) error
// GetRemoteAddr returns the current remote address
GetRemoteAddr() *net.UDPAddr
// SetRemoteAddr updates the remote address after successful validation
SetRemoteAddr(addr *net.UDPAddr) error
}
PathValidationConn defines the interface for sending path validation messages. This interface is implemented by SSU2Conn to allow testing with mocks.
type PathValidator ¶
type PathValidator struct {
// contains filtered or unexported fields
}
PathValidator implements connection migration with path validation.
Path validation allows an SSU2 connection to migrate to a new UDP path (different IP address or port) while maintaining security and preventing amplification attacks. This is useful for:
- IP address changes (network switch, VPN, mobile roaming)
- Port changes (NAT rebinding)
- Failover to backup paths
- Load balancing across multiple paths
The validation protocol uses Path Challenge (Type 18) and Path Response (Type 19) blocks to verify bidirectional connectivity on the new path before migration.
Design rationale: - Cryptographic challenge IDs prevent spoofing - Timeout-based cleanup prevents resource leaks - Thread-safe for concurrent path validations - Follows ssu2.rst specification for path validation
func NewPathValidator ¶
func NewPathValidator(conn PathValidationConn) *PathValidator
NewPathValidator creates a new path validator for a connection.
BUG-M07 fix: Now starts a background cleanup goroutine. Callers MUST call Stop() when done to prevent goroutine leaks (see package-level documentation).
Parameters:
- conn: The connection to manage path validation for
Returns an initialized validator.
func (*PathValidator) CleanupExpired ¶
func (pv *PathValidator) CleanupExpired() int
CleanupExpired removes expired path validation challenges.
Challenges are expired if they're older than PathValidationTimeout and not in a terminal state (Validated or Failed).
Returns the number of challenges cleaned up.
func (*PathValidator) CompleteMTUProbe ¶
func (pv *PathValidator) CompleteMTUProbe(challengeID uint64)
CompleteMTUProbe is called when a Path Response is received for an MTU probe challenge. Updates discoveredMTU if this probe was larger than the previously discovered value (G-5).
func (*PathValidator) FailPath ¶
func (pv *PathValidator) FailPath(challengeID uint64, reason error)
FailPath marks a path validation as failed.
Parameters:
- challengeID: The challenge ID to fail
- reason: Error describing why validation failed
func (*PathValidator) GetChallenge ¶
func (pv *PathValidator) GetChallenge(challengeID uint64) (*PathChallenge, bool)
GetChallenge returns information about a specific challenge.
Parameters:
- challengeID: The challenge ID to look up
Returns:
- *PathChallenge: Challenge information (defensive copy)
- bool: Whether the challenge exists
func (*PathValidator) GetDiscoveredMTU ¶
func (pv *PathValidator) GetDiscoveredMTU() int
GetDiscoveredMTU returns the largest validated MTU from probing, or 0 if no MTU probe has completed (G-5).
func (*PathValidator) HandlePathChallenge ¶
func (pv *PathValidator) HandlePathChallenge(block *SSU2Block, fromAddr *net.UDPAddr) error
HandlePathChallenge processes a received Path Challenge block.
When we receive a challenge, we:
- Record it as ChallengeReceived
- Send a Path Response with the same challenge ID
Parameters:
- block: The received Path Challenge block
- fromAddr: The UDP address it came from
Returns error if decoding or response fails.
func (*PathValidator) HandlePathResponse ¶
func (pv *PathValidator) HandlePathResponse(block *SSU2Block, fromAddr *net.UDPAddr) error
HandlePathResponse processes a received Path Response block.
When we receive a response:
- Verify it matches a pending challenge
- Mark the challenge as validated
- Complete the path migration if validation succeeds
Parameters:
- block: The received Path Response block
- fromAddr: The UDP address it came from
Returns error if validation fails.
func (*PathValidator) InitiateMTUProbe ¶
InitiateMTUProbe starts an MTU probe by sending a Path Challenge padded to the given size. If a Path Response is received for this challenge, the discovered MTU is updated (G-5).
func (*PathValidator) InitiatePathValidation ¶
func (pv *PathValidator) InitiatePathValidation(newAddr *net.UDPAddr) (uint64, error)
InitiatePathValidation starts path validation for a new address.
This sends a Path Challenge (Type 18) block to the new address with a cryptographically random challenge ID. The peer must respond with a Path Response (Type 19) containing the same challenge ID.
Parameters:
- newAddr: The new UDP address to validate
Returns:
- uint64: The challenge ID for tracking this validation
- error: If challenge creation or sending fails
func (*PathValidator) RunPMTUD ¶
RunPMTUD performs Path MTU Discovery using binary search between low and high. Each step sends a padded Path Challenge and waits for a response. On success the discovered MTU is updated; on timeout (no response within PathValidationTimeout) the probe size is reduced. The final discovered MTU is returned, or MinMTU if no probe succeeded (G-4).
Parameters:
- addr: the remote address to probe
- low: minimum probe size (typically MinMTU, 1280)
- high: maximum probe size (e.g. MaxPacketSizeIPv4 or MaxPacketSizeIPv6)
RunPMTUD blocks until the search completes or ctx is cancelled. BUG-L02 fix: Refactored to reduce cyclomatic complexity by extracting probe logic.
func (*PathValidator) SendPathChallenge ¶
func (pv *PathValidator) SendPathChallenge(challengeID uint64, addr *net.UDPAddr) error
SendPathChallenge sends a Path Challenge block to the specified address.
Parameters:
- challengeID: The 8-byte challenge identifier
- addr: The UDP address to send to
Returns error if encoding or sending fails.
func (*PathValidator) SendPathResponse ¶
func (pv *PathValidator) SendPathResponse(challengeID uint64, addr *net.UDPAddr) error
SendPathResponse sends a Path Response block to the specified address.
Parameters:
- challengeID: The 8-byte challenge identifier from the Path Challenge
- addr: The UDP address to send to
Returns error if encoding or sending fails.
func (*PathValidator) SetCongestionController ¶
func (pv *PathValidator) SetCongestionController(cc CongestionControllerAccessor)
SetCongestionController sets the congestion controller to reset on path migration (G-7).
func (*PathValidator) SetTokenCache ¶
func (pv *PathValidator) SetTokenCache(tc TokenCacheAccessor)
SetTokenCache sets the token cache used for invalidation when a path migrates. Per spec, tokens are bound to an IP:port and must be invalidated on address change.
func (*PathValidator) Stop ¶
func (pv *PathValidator) Stop()
Stop halts the background cleanup goroutine. Call when the validator is no longer needed to avoid goroutine leaks. Safe to call multiple times. BUG-M07 fix: Added to mirror RelayManager/HolePunchCoordinator pattern.
func (*PathValidator) ValidatePath ¶
func (pv *PathValidator) ValidatePath(challengeID uint64) error
ValidatePath completes the path validation and migrates the connection.
This should be called after receiving a valid Path Response. It updates the connection's remote address to the validated path.
Parameters:
- challengeID: The challenge ID that was validated
Returns error if migration fails.
type PeerTest ¶
type PeerTest struct {
// Nonce uniquely identifies this test
Nonce uint32
// DestConnectionID is the nonce-derived destination connection ID
// for out-of-session PeerTest packets (messages 5-7).
DestConnectionID uint64
// SrcConnectionID is the nonce-derived source connection ID
// for out-of-session PeerTest packets (messages 5-7).
SrcConnectionID uint64
// Role is this peer's role in the test
Role PeerTestRole
// State is the current test state
State PeerTestState
// AliceAddr is the initiator's address
AliceAddr *net.UDPAddr
// BobAddr is the relay's address
BobAddr *net.UDPAddr
// CharlieAddr is the responder's address
CharlieAddr *net.UDPAddr
// StartTime is when the test was initiated
StartTime time.Time
// Deadline is when the overall test must complete (60 s per I2P spec).
// M-04 fix: replaced Timeouts []time.Time (7 entries, only [0] ever set)
// with a single Deadline field to eliminate the misleading dead state.
Deadline time.Time
// NATType is the determined NAT type
NATType NATType
// Reachable indicates if peer is directly reachable
Reachable bool
// ExternalAddr is the detected external address
ExternalAddr *net.UDPAddr
// FailureReason stores the error passed to FailTest (M-4 fix).
FailureReason error
}
PeerTest represents an active peer test operation.
type PeerTestBlock ¶
type PeerTestBlock struct {
// MessageCode identifies which of the 7 messages this is (1 byte)
MessageCode PeerTestMessageCode
// Code is the status/reason code (1 byte)
Code uint8
// Flag is reserved for future use (1 byte, must be 0)
Flag uint8
// RouterHash is the 32-byte hash (only for messages 2 and 4)
RouterHash *data.Hash
// Version is the peer test protocol version (should be 2)
Version uint8
// Nonce uniquely identifies the test session (4 bytes)
Nonce uint32
// Timestamp is seconds since epoch (4 bytes)
Timestamp uint32
// AlicePort is Alice's port number
AlicePort uint16
// AliceIP is Alice's IP address (4 bytes IPv4 or 16 bytes IPv6)
AliceIP []byte
// Signature is the Ed25519 (or other) signature (variable length)
// Optional for messages 5-7.
Signature []byte
}
PeerTestBlock represents a peer test message (Type 10, Block 10). Wire format per SSU2 spec:
Byte 0: msg (message number 1-7) Byte 1: code (status/reason code) Byte 2: flag (unused, set to 0) Bytes 3-34: router hash (32 bytes, only for messages 2 and 4) Next 1 byte: ver (protocol version, should be 2) Next 4 bytes: nonce (big-endian) Next 4 bytes: timestamp (seconds since epoch, big-endian) Next 1 byte: asz (endpoint size: 6 for IPv4, 18 for IPv6) Next 2 bytes: AlicePort (big-endian) Next asz-2: AliceIP (4 or 16 bytes) Remaining: signature (variable length; optional for messages 5-7)
func DecodePeerTestBlock ¶
func DecodePeerTestBlock(ssu2Block *SSU2Block) (*PeerTestBlock, error)
DecodePeerTestBlock decodes a PeerTest block from wire format per the SSU2 spec.
func (*PeerTestBlock) ValidateSourceAddress ¶
func (b *PeerTestBlock) ValidateSourceAddress(sourceAddr *net.UDPAddr) error
ValidateSourceAddress verifies that the IP/port embedded in a PeerTest block matches the actual UDP source address. Per the spec, Charlie must verify that the claimed Alice address matches the packet source to prevent amplification attacks.
type PeerTestManager ¶
type PeerTestManager struct {
// contains filtered or unexported fields
}
PeerTestManager manages the seven-message NAT traversal testing protocol. It coordinates peer tests to determine NAT type and external reachability.
Design rationale: - Nonce generation uses crypto/rand for security - State machine tracks test progression through 7 messages - Results cached by remote address for efficiency - Thread-safe for concurrent test operations
Protocol flow: 1. Alice → Bob: Request (InitiatePeerTest) 2. Bob → Charlie: Relay request 3. Charlie → Bob: Relay response 4. Bob → Alice: Result 5. Charlie → Alice: Probe 6. Alice → Charlie: Reply 7. Charlie → Alice: Confirmation
Thread Safety: All public methods are thread-safe.
func NewPeerTestManager ¶
func NewPeerTestManager(listener ListenerRef) *PeerTestManager
NewPeerTestManager creates a new PeerTestManager.
Parameters:
- listener: The SSU2Listener to manage peer tests for
Returns a new PeerTestManager with empty state.
func NewPeerTestManagerWithFields ¶
func NewPeerTestManagerWithFields(listener ListenerRef, tests map[uint32]*PeerTest, results map[string]*TestResult) *PeerTestManager
NewPeerTestManagerWithFields creates a PeerTestManager with pre-populated fields. Intended for integration tests only — does NOT start the background cleanup goroutine. Long-running tests must call CleanupExpired manually or use NewPeerTestManager instead. H-01 fix: pendingResults is always initialised to prevent nil-map panic in CompleteTest.
func (*PeerTestManager) CleanupExpired ¶
func (ptm *PeerTestManager) CleanupExpired()
CleanupExpired removes tests that have exceeded their timeout.
func (*PeerTestManager) CompleteTest ¶
func (ptm *PeerTestManager) CompleteTest(nonce uint32, result *TestResult) error
CompleteTest marks a test as complete and stores the result.
Parameters:
- nonce: Test nonce
- result: Test result to store
Returns error if test not found.
func (*PeerTestManager) CreateRelayTest ¶
func (ptm *PeerTestManager) CreateRelayTest(nonce uint32, aliceAddr, charlieAddr *net.UDPAddr) (uint32, error)
CreateRelayTest creates a relay test when Bob receives request from Alice.
Bob acts as relay between Alice (initiator) and Charlie (responder).
Parameters:
- nonce: Test nonce from Alice
- aliceAddr: Alice's address
- charlieAddr: Charlie's address
Returns nonce on success, error otherwise.
func (*PeerTestManager) CreateResponderTest ¶
func (ptm *PeerTestManager) CreateResponderTest(nonce uint32, aliceAddr, bobAddr *net.UDPAddr) error
CreateResponderTest creates a responder test when Charlie receives relay from Bob.
Charlie acts as responder to probe Alice.
Parameters:
- nonce: Test nonce
- aliceAddr: Alice's address
- bobAddr: Bob's address
Returns error on failure.
func (*PeerTestManager) FailTest ¶
func (ptm *PeerTestManager) FailTest(nonce uint32, reason error) error
FailTest marks a test as failed.
Parameters:
- nonce: Test nonce
- reason: Error explaining failure
Returns error if test not found.
func (*PeerTestManager) GetListener ¶
func (ptm *PeerTestManager) GetListener() ListenerRef
GetListener returns the listener reference (for testing).
func (*PeerTestManager) GetResult ¶
func (ptm *PeerTestManager) GetResult(addr *net.UDPAddr) *TestResult
GetResult retrieves cached test result for an address.
Parameters:
- addr: Remote address
Returns result copy, or nil if not found.
func (*PeerTestManager) GetResultsMap ¶
func (ptm *PeerTestManager) GetResultsMap() map[string]*TestResult
GetResultsMap returns a shallow copy of the results map under lock (for testing only).
WARNING (M-3): The returned map values are pointers into the manager's internal state. Callers MUST NOT modify the returned *TestResult values concurrently with any other manager operation. This method is intended for read-only inspection in tests. For production use, prefer GetResult(addr).
func (*PeerTestManager) GetStats ¶
func (ptm *PeerTestManager) GetStats() map[string]int
GetStats returns statistics about active tests.
func (*PeerTestManager) GetTest ¶
func (ptm *PeerTestManager) GetTest(nonce uint32) *PeerTest
GetTest retrieves peer test information by nonce.
Parameters:
- nonce: Test nonce
Returns test copy, or nil if not found.
Performance Note (BUG-L05): This method returns a defensive copy to prevent external mutation of internal state. This involves 4-5 allocations per call (UDPAddr copies + struct copy). The current approach prioritizes safety over performance and is acceptable for typical usage patterns. If profiling reveals this is a hot path causing GC pressure, consider alternatives like read-only views or sync.Pool, but only after demonstrating actual performance impact.
func (*PeerTestManager) GetTestsMap ¶
func (ptm *PeerTestManager) GetTestsMap() map[uint32]*PeerTest
GetTestsMap returns a shallow copy of the tests map under lock (for testing only).
WARNING (M-3): The returned map values are pointers into the manager's internal state. Callers MUST NOT modify the returned *PeerTest values concurrently with any other manager operation. This method is intended for read-only inspection in tests. For production use, prefer GetTest(nonce).
func (*PeerTestManager) InitiatePeerTest ¶
func (ptm *PeerTestManager) InitiatePeerTest(bobAddr *net.UDPAddr) (uint32, error)
InitiatePeerTest starts a new peer test as Alice (initiator).
Design rationale: - Generates cryptographically random nonce for test identification - Creates test record with 60-second timeout per I2P spec - Returns nonce for tracking test progress
Parameters:
- bobAddr: Address of Bob (relay peer)
Returns nonce on success, error otherwise.
func (*PeerTestManager) RemoveTest ¶
func (ptm *PeerTestManager) RemoveTest(nonce uint32)
RemoveTest removes a test from tracking.
Parameters:
- nonce: Test nonce
func (*PeerTestManager) SetAliceAddr ¶
func (ptm *PeerTestManager) SetAliceAddr(nonce uint32, addr *net.UDPAddr) error
SetAliceAddr sets Alice's address in an existing test.
Used when Alice's external address is determined during the test.
Parameters:
- nonce: Test nonce
- addr: Alice's address
Returns error if test not found.
func (*PeerTestManager) SetRawResult ¶
func (ptm *PeerTestManager) SetRawResult(key string, result *TestResult)
SetRawResult stores a result directly in the results map (for testing).
func (*PeerTestManager) SetTestAliceAddr ¶
func (ptm *PeerTestManager) SetTestAliceAddr(nonce uint32, addr *net.UDPAddr)
SetTestAliceAddr sets AliceAddr on the test with the given nonce (for testing).
func (*PeerTestManager) SetTestStartTime ¶
func (ptm *PeerTestManager) SetTestStartTime(nonce uint32, t time.Time)
SetTestStartTime sets StartTime on the test with the given nonce (for testing).
func (*PeerTestManager) Stop ¶
func (ptm *PeerTestManager) Stop()
Stop halts the background cleanup goroutine. Call when the manager is no longer needed to avoid goroutine leaks. Safe to call multiple times.
func (*PeerTestManager) UpdateState ¶
func (ptm *PeerTestManager) UpdateState(nonce uint32, state PeerTestState) error
UpdateState updates the state of a peer test.
Parameters:
- nonce: Test nonce
- state: New state
Returns error if test not found.
type PeerTestMessageCode ¶
type PeerTestMessageCode uint8
PeerTestMessageCode identifies the message type in the seven-message protocol.
const ( // PeerTestRequest (1): Alice -> Bob, request test via relay PeerTestRequest PeerTestMessageCode = 1 // PeerTestRelay (2): Bob -> Charlie, relay request to responder PeerTestRelay PeerTestMessageCode = 2 // PeerTestResponse (3): Charlie -> Bob, responder acknowledges PeerTestResponse PeerTestMessageCode = 3 // PeerTestResult (4): Bob -> Alice, relay sends result PeerTestResult PeerTestMessageCode = 4 // PeerTestProbe (5): Charlie -> Alice, responder probes directly PeerTestProbe PeerTestMessageCode = 5 // PeerTestReply (6): Alice -> Charlie, initiator confirms probe PeerTestReply PeerTestMessageCode = 6 // PeerTestConfirmation (7): Charlie -> Alice, responder confirms success PeerTestConfirmation PeerTestMessageCode = 7 )
func (PeerTestMessageCode) String ¶
func (c PeerTestMessageCode) String() string
String returns string representation of the message code.
type PeerTestRole ¶
type PeerTestRole int
PeerTestRole represents the role of a peer in the test.
const ( // RoleInitiator is Alice who initiates the test RoleInitiator PeerTestRole = iota // RoleRelay is Bob who relays messages RoleRelay // RoleResponder is Charlie who responds to test RoleResponder )
func (PeerTestRole) String ¶
func (r PeerTestRole) String() string
String returns human-readable role name.
type PeerTestState ¶
type PeerTestState int
PeerTestState represents the current state of a peer test.
const ( // TestRequested indicates test has been requested TestRequested PeerTestState = iota // TestRelayed indicates test has been relayed to responder TestRelayed // TestProbed indicates probe has been sent TestProbed // TestComplete indicates test completed successfully TestComplete // TestFailed indicates test failed TestFailed )
func (PeerTestState) String ¶
func (s PeerTestState) String() string
String returns human-readable state name.
type PendingSession ¶
type PendingSession struct {
// SessionID uniquely identifies this pending session
SessionID uint64
// RemoteAddr is the target peer's UDP address
RemoteAddr *net.UDPAddr
// IntroducerAddr is the introducer being used
IntroducerAddr *net.UDPAddr
// RelayTag is the tag provided by the introducer
RelayTag uint32
// CreatedAt is when this session was initiated
CreatedAt time.Time
// Retries tracks the number of retry attempts
Retries int
}
PendingSession represents a connection awaiting hole punch completion.
type PendingSessionRegistry ¶
type PendingSessionRegistry interface {
AddPendingSession(sessionID uint64, remoteAddr, introducerAddr *net.UDPAddr, relayTag uint32) error
IncrementRetries(sessionID uint64) int
RemovePendingSession(sessionID uint64)
}
PendingSessionRegistry is the relay dependency of HolePunchCoordinator. *RelayManager satisfies this interface.
type RegisteredIntroducer ¶
type RegisteredIntroducer struct {
// Addr is the UDP address of the introducer
Addr *net.UDPAddr
// RouterHash is the 32-byte I2P router identity
RouterHash []byte
// StaticKey is the introducer's static public key for RouterInfo publication.
// This is the 44-byte base64-encoded form (encoding 32 raw bytes) as required
// by the RouterInfo address format. Other key fields in this package (e.g.,
// IntroKeySize in key_rotation.go) use raw 32-byte slices (M-5).
StaticKey []byte
// IntroKey is the introducer's introduction key for RouterInfo publication.
// This is the 44-byte base64-encoded form (encoding 32 raw bytes) as required
// by the RouterInfo address format (M-5).
IntroKey []byte
// RelayTag is the tag assigned by this introducer
RelayTag uint32
// AddedAt is when this introducer was registered
AddedAt time.Time
// LastSeen is when we last communicated with this introducer
LastSeen time.Time
}
RegisteredIntroducer represents an introducer registered for RouterInfo publication.
type RelayIntroBlock ¶
type RelayIntroBlock struct {
// Flag is a 1-byte flag field (unused, set to 0)
Flag uint8
// AliceRouterHash is Alice's 32-byte router identity hash
AliceRouterHash []byte
// Nonce uniquely identifies this relay request (4 bytes, forwarded from Alice)
Nonce uint32
// AliceRelayTag is the itag from Charlie's RI (4 bytes)
AliceRelayTag uint32
// Timestamp is when the intro was created (4 bytes, seconds since epoch)
Timestamp uint32
// Version is the SSU version for the introduction (1=SSU1, 2=SSU2)
Version uint8
// AlicePort is Alice's port number (2 bytes, big endian)
AlicePort uint16
// AliceIP is Alice's IP address (4 bytes IPv4 or 16 bytes IPv6)
AliceIP net.IP
// Signature is the variable-length signature (64 bytes for Ed25519)
Signature []byte
}
RelayIntroBlock represents a relay introduction (Type 9). Bob sends this to Charlie to introduce Alice.
Wire format per SSU2 spec:
[flag:1][AliceRouterHash:32][nonce:4][relay_tag:4][timestamp:4] [ver:1][asz:1][AlicePort:2][AliceIP:asz-2][signature:varies]
func DecodeRelayIntro ¶
func DecodeRelayIntro(block *SSU2Block) (*RelayIntroBlock, error)
DecodeRelayIntro decodes a RelayIntro block from wire format.
Wire format per SSU2 spec:
[flag:1][AliceRouterHash:32][nonce:4][relay_tag:4][timestamp:4] [ver:1][asz:1][AlicePort:2][AliceIP:asz-2][signature:varies]
type RelayManager ¶
type RelayManager struct {
// contains filtered or unexported fields
}
RelayManager manages relay connections and introducer services for NAT traversal. It handles relay tag allocation, introducer registration, and relay request processing.
Design rationale: - Relay tags are cryptographically random 4-byte values for security - Introducers expire after 1 hour (I2P spec recommendation) - Pending sessions track connections awaiting hole punch completion - Thread-safe for concurrent relay operations
Thread Safety: All public methods are thread-safe.
func NewRelayManager ¶
func NewRelayManager(listener ListenerRef) *RelayManager
NewRelayManager creates a new RelayManager for the specified listener.
Parameters:
- listener: The SSU2Listener to manage relays for
Returns a new RelayManager with empty state.
func (*RelayManager) AddPendingSession ¶
func (rm *RelayManager) AddPendingSession(sessionID uint64, remoteAddr, introducerAddr *net.UDPAddr, relayTag uint32) error
AddPendingSession adds a session awaiting hole punch completion.
Parameters:
- sessionID: Unique session identifier
- remoteAddr: Target peer's UDP address
- introducerAddr: Introducer's UDP address
- relayTag: Tag for relay requests
Returns error if parameters are invalid.
func (*RelayManager) AllocateRelayTag ¶
func (rm *RelayManager) AllocateRelayTag(addr *net.UDPAddr) (uint32, error)
AllocateRelayTag allocates a new relay tag for the specified address. Relay tags are used to identify connections being relayed through this peer.
Design rationale: - Tags are cryptographically random 4-byte values - Tags expire after 1 hour per I2P spec - Zero tag is reserved and never allocated
Parameters:
- addr: UDP address to allocate tag for
Returns the allocated tag, or error if allocation fails.
func (*RelayManager) CleanupExpired ¶
func (rm *RelayManager) CleanupExpired()
CleanupExpired removes expired relay tags, introducers, and pending sessions. This is called periodically by the cleanup timer.
func (*RelayManager) ExpireAllIntroducers ¶
func (rm *RelayManager) ExpireAllIntroducers()
ExpireAllIntroducers immediately expires all registered introducers (test helper).
func (*RelayManager) GetAllIntroducers ¶
func (rm *RelayManager) GetAllIntroducers() []*IntroducerInfo
GetAllIntroducers returns all introducers including expired (for testing).
func (*RelayManager) GetIntroducers ¶
func (rm *RelayManager) GetIntroducers() []*IntroducerInfo
GetIntroducers returns a copy of all active introducers.
Returns a slice of IntroducerInfo pointers (defensive copies).
func (*RelayManager) GetListener ¶
func (rm *RelayManager) GetListener() ListenerRef
GetListener returns the listener reference (for testing).
func (*RelayManager) GetPendingSession ¶
func (rm *RelayManager) GetPendingSession(sessionID uint64) *PendingSession
GetPendingSession retrieves a pending session by ID.
Parameters:
- sessionID: Session identifier
Returns PendingSession info, or nil if not found.
func (*RelayManager) GetPendingSessionsMap ¶
func (rm *RelayManager) GetPendingSessionsMap() map[uint64]*PendingSession
GetPendingSessionsMap returns a copy of the pending sessions map (for testing).
func (*RelayManager) GetRelayTag ¶
func (rm *RelayManager) GetRelayTag(tag uint32) *RelayTag
GetRelayTag retrieves relay tag information.
Parameters:
- tag: Relay tag to look up
Returns RelayTag info, or nil if not found.
Performance Note (BUG-L05): This method returns a defensive copy including deep copy of IP address slice to prevent external mutation. This involves multiple allocations per call but ensures thread-safety and prevents aliasing bugs. The current approach prioritizes correctness over performance. Optimize only if profiling shows this is a bottleneck in production.
func (*RelayManager) GetRelayTagsMap ¶
func (rm *RelayManager) GetRelayTagsMap() map[uint32]*RelayTag
GetRelayTagsMap returns a copy of the relay tags map (for testing).
func (*RelayManager) GetStats ¶
func (rm *RelayManager) GetStats() map[string]int
GetStats returns statistics about the relay manager state.
func (*RelayManager) IncrementRetries ¶
func (rm *RelayManager) IncrementRetries(sessionID uint64) int
IncrementRetries increments the retry counter for a pending session.
Parameters:
- sessionID: Session identifier
Returns the new retry count, or -1 if session not found.
func (*RelayManager) RegisterIntroducer ¶
func (rm *RelayManager) RegisterIntroducer(addr *net.UDPAddr, routerHash data.Hash, relayTag uint32) error
RegisterIntroducer registers a new introducer for this peer. The introducer can be used to relay connection requests to this peer when behind NAT.
Design rationale: - Maximum 3 introducers per I2P spec - Introducers expire after 1 hour - Oldest introducer is replaced if at capacity
Parameters:
- addr: UDP address of the introducer
- routerHash: router identity hash of the introducer
- relayTag: Tag assigned by the introducer for relay requests
Returns error if parameters are invalid.
func (*RelayManager) RemoveIntroducer ¶
func (rm *RelayManager) RemoveIntroducer(addr *net.UDPAddr)
RemoveIntroducer removes an introducer by address.
Parameters:
- addr: UDP address of the introducer to remove
func (*RelayManager) RemovePendingSession ¶
func (rm *RelayManager) RemovePendingSession(sessionID uint64)
RemovePendingSession removes a pending session.
Parameters:
- sessionID: Session identifier to remove
func (*RelayManager) SetIntroducerExpiry ¶
func (rm *RelayManager) SetIntroducerExpiry(idx int, t time.Time)
SetIntroducerExpiry sets the expiry of the introducer at given index (for testing).
func (*RelayManager) SetRelayTagExpiry ¶
func (rm *RelayManager) SetRelayTagExpiry(tag uint32, t time.Time)
SetRelayTagExpiry sets the expiry of a relay tag (for testing).
func (*RelayManager) Start ¶
func (rm *RelayManager) Start()
Start starts the cleanup timer for periodic maintenance. Must be called after NewRelayManager() to enable automatic cleanup. Safe to call multiple times (idempotent).
func (*RelayManager) Stop ¶
func (rm *RelayManager) Stop()
Stop stops the relay manager and cleans up resources. Safe to call multiple times (idempotent).
func (*RelayManager) ValidateRelayTag ¶
func (rm *RelayManager) ValidateRelayTag(tag uint32, addr *net.UDPAddr) bool
ValidateRelayTag validates that a relay tag is active and matches the specified address.
Parameters:
- tag: Relay tag to validate
- addr: Expected address for the tag
Returns true if tag is valid and matches address.
type RelayRequestBlock ¶
type RelayRequestBlock struct {
// Flag is a 1-byte flag field (unused, set to 0)
Flag uint8
// Nonce uniquely identifies this relay request (4 bytes)
Nonce uint32
// RelayTag is the itag from Charlie's RI (4 bytes)
RelayTag uint32
// Timestamp is Unix timestamp in seconds (4 bytes)
Timestamp uint32
// Version is the SSU version for the introduction (1=SSU1, 2=SSU2)
Version uint8
// AlicePort is Alice's port number (2 bytes, big endian)
AlicePort uint16
// AliceIP is Alice's IP address (4 bytes IPv4 or 16 bytes IPv6)
AliceIP net.IP
// Signature is the variable-length signature (64 bytes for Ed25519)
Signature []byte
}
RelayRequestBlock represents a relay request (Type 7). Alice sends this to Bob to request relay through to Charlie.
Wire format per SSU2 spec:
[Flag:1][Nonce:4][RelayTag:4][Timestamp:4][Ver:1][Asz:1][AlicePort:2][AliceIP:asz-2][Signature:varies]
func DecodeRelayRequest ¶
func DecodeRelayRequest(block *SSU2Block) (*RelayRequestBlock, error)
DecodeRelayRequest decodes a RelayRequest block from wire format.
Wire format: [Flag:1][Nonce:4][RelayTag:4][Timestamp:4][Ver:1][Asz:1][AlicePort:2][AliceIP:asz-2][Signature:varies]
type RelayResponseBlock ¶
type RelayResponseBlock struct {
// Flag is a 1-byte flag field (unused, set to 0)
Flag uint8
// Code indicates success or failure reason (1 byte)
// 0 = accepted, 1-63 = rejected by Bob, 64+ = rejected by Charlie
Code uint8
// Nonce matches the nonce from RelayRequest (4 bytes)
Nonce uint32
// Timestamp is Unix timestamp in seconds (4 bytes).
// Present when Code == 0 or Code >= 64.
Timestamp uint32
// Version is the SSU version (1 byte).
// Present when Code == 0 or Code >= 64.
Version uint8
// CharliePort is Charlie's port number (2 bytes).
// Present when Code == 0 or Code >= 64 with csz > 0.
CharliePort uint16
// CharlieIP is Charlie's IP address.
// Present when Code == 0 or Code >= 64 with csz > 0.
CharlieIP net.IP
// Signature is the variable-length signature (typically 64 bytes for Ed25519).
// Present when Code == 0 or Code >= 64.
Signature []byte
// Token is the 8-byte session request token from Charlie.
// Only present when Code == 0 (accepted).
Token []byte
}
RelayResponseBlock represents a relay response (Type 8). Bob sends this to Alice after processing relay request.
Wire format per SSU2 spec:
[Flag:1][Code:1][Nonce:4]
When Code == 0 (accepted by Charlie), additional fields follow:
[Timestamp:4][Ver:1][Csz:1][CharliePort:2][CharlieIP:csz-2][Signature:varies][Token:8]
When Code >= 64 (rejected by Charlie), additional fields follow:
[Timestamp:4][Ver:1][Csz:1][CharliePort:2][CharlieIP:csz-2][Signature:varies]
When Code 1-63 (rejected by Bob), no additional fields.
func DecodeRelayResponse ¶
func DecodeRelayResponse(block *SSU2Block) (*RelayResponseBlock, error)
DecodeRelayResponse decodes a RelayResponse block from wire format.
Wire format: [Flag:1][Code:1][Nonce:4][...]
type RelayTag ¶
type RelayTag struct {
// Tag is the 4-byte relay tag value
Tag uint32
// ForAddr is the address this tag was allocated for
ForAddr *net.UDPAddr
// CreatedAt is when this tag was created
CreatedAt time.Time
// ExpiresAt is when this tag expires
ExpiresAt time.Time
}
RelayTag represents an active relay tag allocation.
type RelayTagBlock ¶
type RelayTagBlock struct {
// RelayTag is the assigned tag value (4 bytes)
RelayTag uint32
}
RelayTagBlock represents a relay tag assignment (Type 16). Per spec: relay tag is 4 bytes, big-endian, nonzero.
func DecodeRelayTag ¶
func DecodeRelayTag(block *SSU2Block) (*RelayTagBlock, error)
DecodeRelayTag decodes a RelayTag block from wire format. Per spec: 4 bytes minimum (relay tag only).
Parameters:
- block: SSU2Block with Type 16
Returns:
- *RelayTagBlock: Decoded relay tag
- error: If decoding fails or validation fails
type RelayTagRequestBlock ¶
type RelayTagRequestBlock struct{}
RelayTagRequestBlock represents a relay tag request (Type 15). Per spec §Relay Tag Request Block, the data portion is empty (size=0).
func DecodeRelayTagRequest ¶
func DecodeRelayTagRequest(block *SSU2Block) (*RelayTagRequestBlock, error)
DecodeRelayTagRequest decodes a RelayTagRequest block from wire format. Per spec the data portion is empty (size=0). Non-empty data is accepted for backward compatibility with older implementations but is ignored.
type SSU2Block ¶
Type aliases from ssu2/wire so path code can reference these types without qualifying them with the wire package name.
func EncodePathChallenge ¶
EncodePathChallenge encodes a Path Challenge block (Type 18).
Wire format: [ChallengeID:8]
Parameters:
- challengeID: 8-byte challenge identifier
Returns encoded block.
func EncodePathChallengeWithPadding ¶
EncodePathChallengeWithPadding creates a Path Challenge block padded to probeSize bytes (total block data length). The first 8 bytes are the challenge ID; remaining bytes are random padding for MTU probing (G-5).
func EncodePathResponse ¶
EncodePathResponse encodes a Path Response block (Type 19).
Wire format: [ChallengeID:8]
Parameters:
- challengeID: 8-byte challenge identifier from the Path Challenge
Returns encoded block.
func EncodePeerTestBlock ¶
func EncodePeerTestBlock(block *PeerTestBlock) (*SSU2Block, error)
EncodePeerTestBlock encodes a PeerTest block to wire format per the SSU2 spec.
func EncodeRelayIntro ¶
func EncodeRelayIntro(intro *RelayIntroBlock) (*SSU2Block, error)
EncodeRelayIntro encodes a RelayIntro block to wire format.
Wire format per SSU2 spec:
[flag:1][AliceRouterHash:32][nonce:4][relay_tag:4][timestamp:4] [ver:1][asz:1][AlicePort:2][AliceIP:asz-2][signature:varies]
func EncodeRelayRequest ¶
func EncodeRelayRequest(req *RelayRequestBlock) (*SSU2Block, error)
EncodeRelayRequest encodes a RelayRequest block to wire format.
Wire format per SSU2 spec: [Flag:1][Nonce:4][RelayTag:4][Timestamp:4][Ver:1][Asz:1][AlicePort:2][AliceIP:asz-2][Signature:varies]
func EncodeRelayResponse ¶
func EncodeRelayResponse(resp *RelayResponseBlock) (*SSU2Block, error)
EncodeRelayResponse encodes a RelayResponse block to wire format.
Wire format per SSU2 spec:
[Flag:1][Code:1][Nonce:4][...]
For Code 0 (accepted): includes Timestamp, Ver, Csz, CharliePort, CharlieIP, Signature, Token. For Code >= 64 (Charlie rejection): includes Timestamp, Ver, Csz, CharliePort, CharlieIP, Signature. For Code 1-63 (Bob rejection): only Flag, Code, Nonce.
func EncodeRelayTag ¶
func EncodeRelayTag(tag *RelayTagBlock) (*SSU2Block, error)
EncodeRelayTag encodes a RelayTag block to wire format. Per spec: [RelayTag:4] (4 bytes, big-endian, nonzero)
Parameters:
- tag: RelayTag data to encode
Returns:
- *SSU2Block: Encoded block ready for transmission
- error: If validation fails
func EncodeRelayTagRequest ¶
func EncodeRelayTagRequest(req *RelayTagRequestBlock) (*SSU2Block, error)
EncodeRelayTagRequest encodes a RelayTagRequest block to wire format. Per spec §Relay Tag Request Block: size=0 (empty data portion).
type SSU2Packet ¶
type SSU2Packet = wire.SSU2Packet
Type aliases from ssu2/wire so path code can reference these types without qualifying them with the wire package name.
type TestResult ¶
type TestResult struct {
// NATType is the determined NAT type
NATType NATType
// ExternalAddr is the detected external address
ExternalAddr *net.UDPAddr
// ExternalPort is the detected external port
ExternalPort uint16
// Reachable indicates if peer is directly reachable
Reachable bool
// TestTime is when the test completed
TestTime time.Time
// DirectProbeSuccess indicates if Charlie → Alice direct probe succeeded
DirectProbeSuccess bool
// RelayedProbeSuccess indicates if Charlie → Alice via Bob succeeded
RelayedProbeSuccess bool
// PortConsistent indicates if external port is consistent
PortConsistent bool
// IPConsistent indicates if external IP is consistent
IPConsistent bool
}
TestResult stores the results of a completed peer test.
func AnalyzeProbeResults ¶
func AnalyzeProbeResults(directSuccess, relayedSuccess bool, addr1, addr2 *net.UDPAddr) *TestResult
AnalyzeProbeResults analyzes probe outcomes and address consistency to build a TestResult summary.
This helper consolidates probe data into a structured result for NAT type determination.
Parameters:
- directSuccess: Whether direct probe (Charlie → Alice) succeeded
- relayedSuccess: Whether relayed probe succeeded
- addr1: First observed external address
- addr2: Second observed external address
Returns a TestResult with consistency flags set.
type TokenCacheAccessor ¶
TokenCacheAccessor provides address-based token invalidation. Implemented by *ssu2.TokenCache.