From 15a26614af79cebbc1fc2c841b4635036798cb93 Mon Sep 17 00:00:00 2001 From: eyedeekay Date: Thu, 9 Oct 2025 13:50:00 -0400 Subject: [PATCH] fixup docs --- common/DOC.md | 28 + datagram/DOC.md | 31 + datagram2/DOC.md | 221 ++----- datagram3/DOC.md | 438 ++------------ datagram3/DOC.md.backup | 1210 +++++++++++++++++++++++++++++++++++++ datagram3/doc.go | 7 +- datagram3/listen.go | 1 - datagram3/packetconn.go | 24 +- datagram3/read.go | 35 +- datagram3/session_test.go | 2 +- datagram3/write.go | 2 +- primary/DOC.md | 95 ++- raw/DOC.md | 30 + stream/DOC.md | 31 + 14 files changed, 1485 insertions(+), 670 deletions(-) create mode 100644 datagram3/DOC.md.backup diff --git a/common/DOC.md b/common/DOC.md index 00905ad5d..bf4895d9c 100644 --- a/common/DOC.md +++ b/common/DOC.md @@ -2,6 +2,34 @@ -- import "github.com/go-i2p/go-sam-go/common" +Package common provides core SAM protocol implementation and shared utilities +for I2P. + +This package implements the foundational SAMv3.3 protocol communication layer, +including session management, configuration options, I2P name resolution, and +shared abstractions used by all session types (stream, datagram, raw). + +Core types: + + - SAM: Base connection to I2P SAM bridge (default port 7656) + - Session: Base session interface with lifecycle management + - I2PConfig: Configuration builder for tunnel parameters + - SAMEmit: SAM protocol command formatter + +Session creation requires 2-5 minutes for I2P tunnel establishment; use generous +timeouts and exponential backoff retry logic. All network operations should use +context.Context for cancellation and timeout control. + +Basic usage: + + sam, err := common.NewSAM("127.0.0.1:7656") + if err != nil { log.Fatal(err) } + defer sam.Close() + session, err := sam.NewGenericSession("STREAM", "my-session", keys, []string{"inbound.length=1"}) + defer session.Close() + +This package is primarily used as a foundation by higher-level packages (stream, +datagram, raw, primary) rather than being used directly by applications. ## Usage diff --git a/datagram/DOC.md b/datagram/DOC.md index 78e891764..6699e41eb 100644 --- a/datagram/DOC.md +++ b/datagram/DOC.md @@ -2,6 +2,37 @@ -- import "github.com/go-i2p/go-sam-go/datagram" +Package datagram provides legacy authenticated datagram sessions for I2P using +SAMv3 DATAGRAM. + +DATAGRAM sessions provide authenticated, repliable UDP-like messaging over I2P +tunnels. This is the legacy format without replay protection. For new +applications requiring replay protection, use package datagram2 instead. + +Key features: + + - Authenticated datagrams with signature verification + - Repliable (can send replies to sender) + - No replay protection (use datagram2 if needed) + - UDP-like messaging (unreliable, unordered) + - Maximum 31744 bytes per datagram (11 KB recommended) + - Implements net.PacketConn interface + +Session creation requires 2-5 minutes for I2P tunnel establishment. Use generous +timeouts and exponential backoff retry logic. + +Basic usage: + + sam, err := common.NewSAM("127.0.0.1:7656") + session, err := datagram.NewDatagramSession(sam, "my-session", keys, []string{"inbound.length=1"}) + defer session.Close() + conn := session.PacketConn() + n, err := conn.WriteTo(data, destination) + n, addr, err := conn.ReadFrom(buffer) + +See also: Package datagram2 (with replay protection), datagram3 +(unauthenticated), stream (TCP-like), raw (non-repliable), primary +(multi-session management). ## Usage diff --git a/datagram2/DOC.md b/datagram2/DOC.md index 511ef2dec..6365e79e6 100644 --- a/datagram2/DOC.md +++ b/datagram2/DOC.md @@ -2,74 +2,37 @@ -- import "github.com/go-i2p/go-sam-go/datagram2" -Package datagram2 provides authenticated datagram2 sessions with replay +Package datagram2 provides authenticated datagram sessions with replay protection for I2P. -DATAGRAM2 is a new format specified in early 2025 that replaces legacy DATAGRAM -sessions for applications requiring replay protection. It uses the SAMv3 -protocol with STYLE=DATAGRAM2. +DATAGRAM2 sessions provide authenticated, repliable UDP-like messaging over I2P +tunnels with replay attack protection. This is the recommended datagram format +for applications requiring both source authentication and replay protection. -# Key Features +Key features: - Authenticated datagrams with signature verification - Replay protection (not available in legacy DATAGRAM) - - Offline signature support + - Repliable (can send replies to sender) - UDP-like messaging (unreliable, unordered) - - Maximum 31744 bytes (11 KB recommended for reliability) - - Compatible with SAMv3.3 PRIMARY sessions + - Maximum 31744 bytes per datagram (11 KB recommended) + - Implements net.PacketConn interface -# Basic Usage +Session creation requires 2-5 minutes for I2P tunnel establishment. Use generous +timeouts and exponential backoff retry logic. -Create a session: +Basic usage: - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() sam, err := common.NewSAM("127.0.0.1:7656") session, err := datagram2.NewDatagram2Session(sam, "my-session", keys, []string{"inbound.length=1"}) defer session.Close() - -Send and receive using PacketConn: - conn := session.PacketConn() - defer conn.Close() n, err := conn.WriteTo(data, destination) n, addr, err := conn.ReadFrom(buffer) -# I2P Timing Considerations - -Session creation: 2-5 minutes for I2P tunnel establishment - -Message delivery: Variable latency (network-dependent) - -Recommended: Use generous timeouts (5+ minutes for session creation) and retry -logic with exponential backoff. - -# Implementation Status - -DATAGRAM2 specification finalized early 2025. This is one of the first -implementations. Check I2P router documentation for SAMv3 DATAGRAM2 support -(Java I2P 0.9.x+, i2pd 2.x+). - -Current implementation status: - - - Core session management: ✅ Implemented - - UDP forwarding: ✅ Implemented - - Send/receive operations: ✅ Implemented - - PacketConn interface: ✅ Implemented - - Replay protection: ✅ Implemented (handled by I2P router) - - PRIMARY session integration: ⏸️ Deferred (low priority) - -# See Also - -Package datagram: Legacy DATAGRAM sessions (authenticated, no replay protection) - -Package datagram3: DATAGRAM3 sessions (unauthenticated, hash-based sources) - -Package stream: TCP-like reliable connections - -Package raw: Encrypted but unauthenticated datagrams (non-repliable) - -Package primary: PRIMARY session management for multiple subsessions +See also: Package datagram (legacy, no replay protection), datagram3 +(unauthenticated), stream (TCP-like), raw (non-repliable), primary +(multi-session management). ## Usage @@ -362,30 +325,11 @@ Example usage: func NewDatagram2Session(sam *common.SAM, id string, keys i2pkeys.I2PKeys, options []string) (*Datagram2Session, error) ``` NewDatagram2Session creates a new datagram2 session with replay protection for -UDP-like I2P messaging. This function establishes a new DATAGRAM2 session with -the provided SAM connection, session ID, cryptographic keys, and configuration -options. It automatically creates a UDP listener for receiving forwarded -datagrams (SAMv3 requirement) and configures the session with PORT/HOST -parameters. - -DATAGRAM2 provides enhanced security compared to legacy DATAGRAM: - - - Replay protection prevents replay attacks (not available in DATAGRAM) - - Offline signature support for advanced key management - - Identical SAM API for easy migration from DATAGRAM - -I2P Timing Considerations: - - - Session creation can take 2-5 minutes for I2P tunnel establishment - - Use context.WithTimeout with generous timeouts (5+ minutes recommended) - - Implement exponential backoff retry logic for connection attempts - - Distinguish between I2P timing delays and actual failures - -Example usage: - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() - session, err := NewDatagram2Session(sam, "my-session", keys, []string{"inbound.length=1"}) +UDP-like I2P messaging. It initializes the session with the provided SAM +connection, session ID, cryptographic keys, and configuration options. The +session automatically creates a UDP listener for receiving forwarded datagrams +per SAMv3 requirements. Example usage: session, err := NewDatagram2Session(sam, +"my-session", keys, []string{"inbound.length=1"}) #### func NewDatagram2SessionFromSubsession @@ -397,34 +341,11 @@ that has already been registered with a PRIMARY session using SESSION ADD. This constructor skips the session creation step since the subsession is already registered with the SAM bridge. -This function is specifically designed for use with SAMv3.3 PRIMARY sessions -where subsessions are created using SESSION ADD rather than SESSION CREATE -commands. - For PRIMARY datagram2 subsessions, UDP forwarding is mandatory (SAMv3 -requirement). The UDP connection must be provided for proper datagram reception -via UDP forwarding. +requirement). The UDP connection must be provided for proper datagram reception. -DATAGRAM2 subsessions share the same cryptographic keys and I2P tunnels as the -PRIMARY session, but can be differentiated using LISTEN_PORT for incoming -datagram routing. - -Parameters: - - - sam: SAM connection for data operations (separate from the primary session's control connection) - - id: The subsession ID that was already registered with SESSION ADD - - keys: The I2P keys from the primary session (shared across all subsessions) - - options: Configuration options for the subsession - - udpConn: UDP connection for receiving forwarded datagrams (required, not nil) - -Returns a Datagram2Session ready for use without attempting to create a new SAM -session. - -Example usage with PRIMARY session: - - primary, err := sam.NewPrimarySession("main", keys, options) - udpConn, _ := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}) - sub, err := NewDatagram2SessionFromSubsession(sam, "sub1", keys, options, udpConn) +Example usage: session, err := NewDatagram2SessionFromSubsession(sam, "sub1", +keys, options, udpConn) #### func (*Datagram2Session) Addr @@ -434,12 +355,8 @@ func (s *Datagram2Session) Addr() i2pkeys.I2PAddr Addr returns the I2P address of this datagram2 session. This address represents the session's identity on the I2P network and can be used by other nodes to send authenticated datagrams with replay protection to this session. The address is -derived from the session's cryptographic keys. - -Example usage: - - myAddr := session.Addr() - fmt.Println("My I2P address:", myAddr.Base32()) +derived from the session's cryptographic keys. Example usage: myAddr := +session.Addr(); fmt.Println("My I2P address:", myAddr.Base32()) #### func (*Datagram2Session) Close @@ -449,11 +366,7 @@ func (s *Datagram2Session) Close() error Close closes the datagram2 session and all associated resources. This method safely terminates the session, closes the UDP listener and underlying connection, and cleans up any background goroutines. It's safe to call multiple -times. - -Example usage: - - defer session.Close() +times. Example usage: defer session.Close() #### func (*Datagram2Session) NewReader @@ -463,24 +376,9 @@ func (s *Datagram2Session) NewReader() *Datagram2Reader NewReader creates a Datagram2Reader for receiving authenticated datagrams with replay protection. This method initializes a new reader with buffered channels for asynchronous datagram reception. The reader must be started manually with -receiveLoop() for continuous operation. - -All datagrams received through this reader are authenticated by the I2P router -with signature verification performed internally. DATAGRAM2 provides replay -protection, preventing replay attacks that are possible with legacy DATAGRAM -sessions. - -Example usage: - - reader := session.NewReader() - go reader.receiveLoop() - for { - datagram, err := reader.ReceiveDatagram() - if err != nil { - // Handle error - } - // Process datagram.Data from datagram.Source - } +receiveLoop() for continuous operation. Example usage: reader := +session.NewReader(); go reader.receiveLoop(); datagram, err := +reader.ReceiveDatagram() #### func (*Datagram2Session) NewWriter @@ -490,16 +388,9 @@ func (s *Datagram2Session) NewWriter() *Datagram2Writer NewWriter creates a Datagram2Writer for sending authenticated datagrams with replay protection. This method initializes a new writer with a default timeout of 30 seconds for send operations. The timeout can be customized using the -SetTimeout method on the returned writer. - -All datagrams sent through this writer are authenticated and provide replay -protection. Maximum datagram size is 31744 bytes total (including headers), with -11 KB recommended for best reliability across the I2P network. - -Example usage: - - writer := session.NewWriter().SetTimeout(60*time.Second) - err := writer.SendDatagram(data, destination) +SetTimeout method on the returned writer. Example usage: writer := +session.NewWriter().SetTimeout(60*time.Second); err := writer.SendDatagram(data, +dest) #### func (*Datagram2Session) PacketConn @@ -525,18 +416,8 @@ func (s *Datagram2Session) ReceiveDatagram() (*Datagram2, error) ReceiveDatagram receives a single authenticated datagram from the I2P network. This method is a convenience wrapper that performs a direct single read operation without starting a continuous receive loop. For continuous reception, -use NewReader() and manage the reader lifecycle manually. - -All datagrams are authenticated by the I2P router with signature verification -performed internally. DATAGRAM2 provides replay protection. - -Example usage: - - datagram, err := session.ReceiveDatagram() - if err != nil { - // Handle error - } - // Process datagram.Data from datagram.Source +use NewReader() and manage the reader lifecycle manually. Example usage: +datagram, err := session.ReceiveDatagram() #### func (*Datagram2Session) SendDatagram @@ -546,15 +427,8 @@ func (s *Datagram2Session) SendDatagram(data []byte, dest i2pkeys.I2PAddr) error SendDatagram sends an authenticated datagram with replay protection to the specified destination. This is a convenience method that creates a temporary writer and sends the datagram immediately. For multiple sends, it's more -efficient to create a writer once and reuse it. - -Maximum datagram size is 31744 bytes total (including headers), with 11 KB -recommended for best reliability across the I2P network. The datagram is -authenticated and provides replay protection. - -Example usage: - - err := session.SendDatagram([]byte("hello"), destinationAddr) +efficient to create a writer once and reuse it. Example usage: err := +session.SendDatagram([]byte("hello"), destinationAddr) #### type Datagram2Writer @@ -584,18 +458,10 @@ Example usage: func (w *Datagram2Writer) SendDatagram(data []byte, dest i2pkeys.I2PAddr) error ``` SendDatagram sends an authenticated datagram with replay protection to the -specified I2P destination. This method uses the SAMv3 UDP approach: sending via -UDP socket to port 7655 with DATAGRAM2 format. The datagram is authenticated by -the I2P router and provides replay protection not available in legacy DATAGRAM -sessions. - -Maximum datagram size is 31744 bytes total (including headers), with 11 KB -recommended for best reliability across the I2P network. It blocks until the -datagram is sent or an error occurs, respecting the configured timeout. - -Example usage: - - err := writer.SendDatagram([]byte("hello world"), destinationAddr) +specified I2P destination. It uses the SAMv3 UDP approach by sending to port +7655 with DATAGRAM2 format. Maximum datagram size is 31744 bytes (11 KB +recommended for reliability). Example usage: err := +writer.SendDatagram([]byte("hello world"), destinationAddr) #### func (*Datagram2Writer) SetTimeout @@ -604,13 +470,8 @@ func (w *Datagram2Writer) SetTimeout(timeout time.Duration) *Datagram2Writer ``` SetTimeout sets the timeout for datagram2 write operations. This method configures the maximum time to wait for authenticated datagram send operations -to complete. The timeout prevents indefinite blocking during network congestion -or connection issues. Returns the writer instance for method chaining -convenience. - -Example usage: - - writer.SetTimeout(30*time.Second).SendDatagram(data, destination) +to complete. Returns the writer instance for method chaining convenience. +Example usage: writer.SetTimeout(30*time.Second).SendDatagram(data, destination) #### type SAM diff --git a/datagram3/DOC.md b/datagram3/DOC.md index 2fff1c9e9..fd13901e9 100644 --- a/datagram3/DOC.md +++ b/datagram3/DOC.md @@ -2,225 +2,34 @@ -- import "github.com/go-i2p/go-sam-go/datagram3" -Package datagram3 provides repliable but UNAUTHENTICATED datagram sessions for -I2P. +Package datagram3 provides repliable datagram sessions with hash-based source +identification for I2P. -# CRITICAL SECURITY WARNING - -⚠️ DATAGRAM3 sources are NOT authenticated and can be spoofed by malicious -actors! - -⚠️ Any attacker can claim to be any sender by providing a fake hash. - -⚠️ Do NOT trust source identity without additional application-level -authentication. - -⚠️ If you need authenticated sources, use DATAGRAM2 instead. - -# Overview - -DATAGRAM3 provides UDP-like messaging over I2P with hash-based source -identification. This format prioritizes low overhead over source verification, -making it suitable for applications that implement their own authentication -layer or where source identity is not security-critical. +DATAGRAM3 sessions provide repliable UDP-like messaging with hash-based source +identification instead of full destinations. Key features: - - Repliable datagrams (can send replies to sender) - - Unauthenticated sources (hash-based, spoofable) - - Offline signature support + - Repliable (can send replies to sender) + - Hash-based source identification (32-byte hash) + - Requires NAMING LOOKUP for replies - UDP-like messaging (unreliable, unordered) - - Maximum 31744 bytes (recommended 11 KB for reliability) - - Automatic hash-to-destination caching via NAMING LOOKUP + - Maximum 31744 bytes per datagram (11 KB recommended) -# Key Differences from DATAGRAM and DATAGRAM2 +Session creation requires 2-5 minutes for I2P tunnel establishment. Use generous +timeouts and exponential backoff retry logic. Hash resolution uses automatic +caching to minimize NAMING LOOKUP overhead. - Feature | DATAGRAM | DATAGRAM2 | DATAGRAM3 - ---------------------|----------|-----------|---------- - Authenticated | Yes | Yes | NO - Repliable | Yes | Yes | Yes - Replay Protection | No | Yes | No - Offline Signatures | No | Yes | Yes - Source Format | Full | Full | 32-byte hash - Source Verification | Yes | Yes | NO - Reply Overhead | Low | Low | Higher (NAMING LOOKUP) +Basic usage: -# Security Considerations - -DATAGRAM3 is appropriate when: - - - Application implements its own authentication layer - - Source identity is not security-critical - - High-volume messaging requiring low overhead - - Anonymous bulletin boards or chat systems - - Telemetry or metrics collection - -DATAGRAM3 is NOT appropriate for: - - - Financial transactions - - Identity-based access control - - Cryptographic protocols relying on source verification - - Any system where source spoofing has security implications - -# I2P Timing Considerations - -Session creation: 2-5 minutes for I2P tunnel establishment - -Datagram delivery: Variable latency (network-dependent) - -Hash resolution: Additional network round-trip for NAMING LOOKUP (cached after -first use) - -Recommended: Use generous timeouts (5+ minutes for session creation) and retry -logic with exponential backoff. Distinguish between I2P network delays and -actual failures. - -# Basic Usage - -Create a session: - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() sam, err := common.NewSAM("127.0.0.1:7656") session, err := datagram3.NewDatagram3Session(sam, "my-session", keys, []string{"inbound.length=1"}) defer session.Close() + dg, err := session.NewReader().ReceiveDatagram() + if err := dg.ResolveSource(session); err != nil { log.Error(err) } + session.NewWriter().SendDatagram([]byte("reply"), dg.Source) -Receive datagrams with hash resolution: - - reader := session.NewReader() - go reader.receiveLoop() - for { - dg, err := reader.ReceiveDatagram() - if err != nil { - log.Error(err) - continue - } - // SECURITY: dg.SourceHash is UNAUTHENTICATED! - log.Warn("Received from unverified hash:", hex.EncodeToString(dg.SourceHash)) - - // Resolve hash to full destination for reply (cached after first lookup) - if err := dg.ResolveSource(session); err != nil { - log.Error("Failed to resolve:", err) - continue - } - - // Process data and reply (source still unverified!) - writer := session.NewWriter() - writer.SendDatagram([]byte("reply"), dg.Source) - } - -Send datagrams: - - writer := session.NewWriter().SetTimeout(30*time.Second) - err := writer.SendDatagram(data, destination) - -# Hash Resolution for Replies - -Received DATAGRAM3 messages contain a 44-byte base64 hash (32 bytes binary) -instead of the full sender destination. To reply, the hash must be converted to -a b32.i2p address and resolved via NAMING LOOKUP: - - 1. Receive 44-byte base64 hash from SAM bridge - 2. Base64-decode to 32 bytes binary - 3. Base32-encode to 52 characters - 4. Append ".b32.i2p" suffix - 5. Use resulting address for NAMING LOOKUP to get full destination - 6. Cache full destination to avoid repeated lookups - 7. Use full destination for SendDatagram() - -The session maintains a HashResolver cache to minimize lookup overhead. -Applications replying to the same source repeatedly benefit from caching. - -Example with manual hash handling: - - // Get b32 address without full resolution (fast, no network I/O) - b32Addr := datagram.GetSourceB32() - log.Info("Received from (unverified):", b32Addr) - - // Check if already cached (no network I/O) - if dest, ok := session.resolver.GetCached(datagram.SourceHash); ok { - writer.SendDatagram(reply, dest) - } else { - // Resolve with network I/O - if err := datagram.ResolveSource(session); err != nil { - log.Error(err) - } - } - -# PRIMARY Session Usage - -DATAGRAM3 supports SAMv3.3 PRIMARY sessions for sharing tunnels across multiple -subsessions: - - primary, err := sam.NewPrimarySession("main", keys, options) - udpConn, _ := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}) - sub1, err := datagram3.NewDatagram3SessionFromSubsession(sam, "sub1", keys, options, udpConn) - -Each subsession can use LISTEN_PORT to differentiate incoming datagrams. - -# Implementation Status - -DATAGRAM3 is a new format specified in early 2025. This is one of the first -implementations. Check SAM bridge documentation for I2P router support (Java I2P -0.9.x+, i2pd 2.x+). - -Current implementation status: - - - Core session management: ✅ Implemented - - UDP forwarding: ✅ Implemented - - Hash resolution and caching: ✅ Implemented - - Send/receive operations: ✅ Implemented - - PacketConn interface: ✅ Implemented - - PRIMARY session integration: ⏸️ Deferred (low priority) - -# Attack Scenarios - -Source Spoofing: Attacker claims to be legitimate sender by providing fake hash. -Applications trust unauthenticated source for security decisions. Mitigation: -Application-layer authentication required. - -Cache Poisoning: Attacker floods with fake sources causing cache to grow -unbounded. Mitigation: Implement cache size limits and TTL (planned -enhancement). - -Impersonation: Attacker replies pretending to be original sender. No -cryptographic verification of source identity. Mitigation: Use DATAGRAM2 for -authenticated sources. - -# Performance Considerations - -Hash resolution overhead: First reply to any source requires NAMING LOOKUP -(network I/O). Subsequent replies to same source use cached destination (fast). -Cache hit rate improves with repeated communication to same peers. Consider -pre-resolving known peers during initialization for critical paths. - -Memory usage: Hash resolver cache grows unbounded. Monitor cache size with -CacheSize(). Consider implementing periodic cache clearing for long-running -applications. Future enhancement: LRU eviction policy and cache size limits. - -# Protocol Compliance - -This implementation follows the SAMv3 specification for DATAGRAM3 sessions: - - - STYLE=DATAGRAM3 (not DATAGRAM or DATAGRAM2) - - UDP forwarding (PORT/HOST) is mandatory for SAMv3 - - Supports SAM 3.2+ features (FROM_PORT/TO_PORT) - - Supports SAM 3.3+ features (SEND_TAGS, TAG_THRESHOLD, EXPIRES, SEND_LEASESET) - - Offline signature support via SIGNATURE_TYPE - - PRIMARY session subsession support - -# See Also - -Package datagram: Legacy DATAGRAM sessions (authenticated, repliable) - -Package datagram2: DATAGRAM2 sessions (authenticated, repliable, replay -protection) - -Package stream: TCP-like reliable connections - -Package raw: Encrypted but unauthenticated datagrams (non-repliable) - -Package primary: PRIMARY session management for multiple subsessions +See also: Package datagram, datagram2, stream, raw, primary. ## Usage @@ -237,11 +46,6 @@ type Datagram3 struct { Datagram3 represents an I2P datagram3 message with UNAUTHENTICATED source. -⚠️ CRITICAL SECURITY WARNING: SourceHash is NOT authenticated and can be -spoofed! ⚠️ Any malicious actor can claim to be any source by providing a fake -hash. ⚠️ Applications MUST implement their own authentication if source identity -matters. ⚠️ Use DATAGRAM2 if you need cryptographically authenticated sources. - This structure encapsulates the payload data along with the unauthenticated source hash and optional resolved destination. The SourceHash is always present (32 bytes), while Source is only populated after calling ResolveSource() to @@ -277,9 +81,6 @@ resolution. This converts the 32-byte hash to a base32-encoded .b32.i2p address string without performing NAMING LOOKUP. This is faster than full resolution and sufficient for display, logging, or caching purposes. -⚠️ SECURITY WARNING: The returned address is still UNAUTHENTICATED! ⚠️ This -method does not add source verification. - Returns empty string if SourceHash is invalid (not 32 bytes). Example usage: @@ -297,10 +98,6 @@ This performs a NAMING LOOKUP to convert the 32-byte hash into a full destination address. The operation is cached in the session's resolver to avoid repeated lookups. -⚠️ SECURITY WARNING: Resolving the hash does NOT authenticate the source! ⚠️ -Even with full destination, the source can still be spoofed. ⚠️ This method only -enables replies, it does NOT verify identity. - Process: 1. Check if already resolved (Source not nil) @@ -330,10 +127,6 @@ type Datagram3Addr struct { Datagram3Addr implements net.Addr interface for I2P datagram3 addresses. -⚠️ SECURITY WARNING: If constructed from received hash, this address is -UNAUTHENTICATED! ⚠️ Do not trust the address for security-critical operations -without additional verification. - This type provides standard Go networking address representation for I2P destinations, allowing seamless integration with existing Go networking code that expects net.Addr. The address can wrap either a full I2P destination or @@ -361,8 +154,6 @@ String returns the string representation of the I2P address. This implements the net.Addr interface. If a full address is available, returns base32 representation. If only hash is available, returns the b32.i2p derived address. -⚠️ SECURITY WARNING: Hash-derived addresses are UNAUTHENTICATED! - #### type Datagram3Conn ```go @@ -373,10 +164,6 @@ type Datagram3Conn struct { Datagram3Conn implements net.PacketConn interface for I2P datagram3 communication. -⚠️ SECURITY WARNING: All sources received via this connection are -UNAUTHENTICATED! ⚠️ Applications MUST implement their own authentication layer -if source identity matters. - This type provides compatibility with standard Go networking patterns by wrapping datagram3 session functionality in a familiar PacketConn interface. It manages internal readers and writers while providing standard connection @@ -422,8 +209,6 @@ data into the provided byte slice and returns the number of bytes read. When reading, it also updates the remote address of the connection for subsequent Write calls. -⚠️ SECURITY WARNING: Remote address is UNAUTHENTICATED hash-based! - Note: This is not typical for datagrams which are connectionless, but provides compatibility with the net.Conn interface. @@ -432,16 +217,12 @@ compatibility with the net.Conn interface. ```go func (c *Datagram3Conn) ReadFrom(p []byte) (n int, addr net.Addr, err error) ``` -ReadFrom reads an UNAUTHENTICATED datagram from the connection. - -⚠️ CRITICAL SECURITY WARNING: Source addresses are NOT authenticated! ⚠️ The -returned address contains an UNAUTHENTICATED hash-based source. ⚠️ Do not trust -source identity without additional verification. +ReadFrom reads a datagram from the connection. This method implements the net.PacketConn interface. It starts the receive loop if not already started and blocks until a datagram is received. The data is -copied to the provided buffer p, and the UNAUTHENTICATED source address is -returned as a Datagram3Addr. +copied to the provided buffer p, and the source address is returned as a +Datagram3Addr. The source address contains the 32-byte hash (not full destination). Applications must resolve the hash via ResolveSource() to reply. @@ -453,10 +234,8 @@ func (c *Datagram3Conn) RemoteAddr() net.Addr ``` RemoteAddr returns the remote network address of the connection. This method implements the net.Conn interface. For datagram3 connections, this returns the -UNAUTHENTICATED address of the last peer that sent data (set by Read), or nil if -no data has been received yet. - -⚠️ SECURITY WARNING: Remote address is UNAUTHENTICATED! +address of the last peer that sent data (set by Read), or nil if no data has +been received yet. #### func (*Datagram3Conn) SetDeadline @@ -520,10 +299,6 @@ type Datagram3Reader struct { Datagram3Reader handles incoming UNAUTHENTICATED datagram3 reception from I2P. -⚠️ SECURITY WARNING: All received datagrams have UNAUTHENTICATED sources! ⚠️ The -SourceHash field in received datagrams can be spoofed by attackers. ⚠️ Do not -trust source identity without additional verification. - The reader provides asynchronous datagram reception through buffered channels, allowing applications to receive datagrams without blocking. It manages its own goroutine for continuous message processing and provides thread-safe access to @@ -569,16 +344,11 @@ Example usage: ```go func (r *Datagram3Reader) ReceiveDatagram() (*Datagram3, error) ``` -ReceiveDatagram receives a single UNAUTHENTICATED datagram from the I2P network. - -⚠️ CRITICAL SECURITY WARNING: Sources are NOT authenticated and can be spoofed! -⚠️ Do not trust datagram.SourceHash without additional verification. ⚠️ Use -application-layer authentication if source identity matters. +ReceiveDatagram receives a single datagram from the I2P network. This method blocks until a datagram is received or an error occurs, returning -the received datagram with its data and UNAUTHENTICATED hash-based source. It -handles concurrent access safely and provides proper error handling for network -issues. +the received datagram with its data and hash-based source. It handles concurrent +access safely and provides proper error handling for network issues. Unlike DATAGRAM/DATAGRAM2, received datagrams contain only a 32-byte hash (not full destination). Applications must call ResolveSource() to convert the hash to @@ -590,9 +360,7 @@ Example usage: if err != nil { // Handle error } - // SECURITY: datagram.SourceHash is UNAUTHENTICATED! - log.Warn("Received from unverified source:", hex.EncodeToString(datagram.SourceHash)) - // Resolve hash for reply (expensive, cached) + log.Info("Received from source:", hex.EncodeToString(datagram.SourceHash)) if err := datagram.ResolveSource(session); err != nil { log.Error(err) } @@ -607,11 +375,6 @@ type Datagram3Session struct { Datagram3Session represents a repliable but UNAUTHENTICATED datagram3 session. -⚠️ CRITICAL SECURITY WARNING: Source addresses are NOT authenticated and can be -spoofed! ⚠️ Applications requiring source authentication MUST use DATAGRAM2 -instead. ⚠️ Do NOT trust source identity without additional application-level -authentication. - DATAGRAM3 provides UDP-like messaging with hash-based source identification instead of full authenticated destinations. This reduces overhead at the cost of source verification. Received datagrams contain a 32-byte hash that requires @@ -656,46 +419,14 @@ Example usage: ```go func NewDatagram3Session(sam *common.SAM, id string, keys i2pkeys.I2PKeys, options []string) (*Datagram3Session, error) ``` -NewDatagram3Session creates a new repliable but UNAUTHENTICATED datagram3 -session. - -⚠️ CRITICAL SECURITY WARNING: DATAGRAM3 sources are NOT authenticated and can be -spoofed! ⚠️ Do not trust source addresses without additional application-level -authentication. ⚠️ If you need authenticated sources, use DATAGRAM2 instead. - -This function establishes a new DATAGRAM3 session with the provided SAM -connection, session ID, cryptographic keys, and configuration options. It -automatically creates a UDP listener for receiving forwarded datagrams (SAMv3 -requirement) and configures the session with PORT/HOST parameters. - -DATAGRAM3 provides repliable datagrams with hash-based source identification: - - - Repliable: Can reply to sender (requires hash resolution via NAMING LOOKUP) - - Unauthenticated: Source is NOT verified (unlike DATAGRAM/DATAGRAM2) - - Hash-based source: 32-byte hash instead of full destination - - Lower overhead: No signature verification required - -I2P Timing Considerations: - - - Session creation can take 2-5 minutes for I2P tunnel establishment - - Use context.WithTimeout with generous timeouts (5+ minutes recommended) - - Hash resolution adds network round-trip for replies (cached after first lookup) - - Implement exponential backoff retry logic for connection attempts - - Distinguish between I2P timing delays and actual failures - -Example usage: - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() - session, err := NewDatagram3Session(sam, "my-session", keys, []string{"inbound.length=1"}) - reader := session.NewReader() - go reader.receiveLoop() - dg, err := reader.ReceiveDatagram() - // dg.SourceHash is UNAUTHENTICATED! - if err := dg.ResolveSource(session); err != nil { - log.Fatal(err) - } - session.NewWriter().SendDatagram(reply, dg.Source) +NewDatagram3Session creates a new datagram3 session with hash-based source +identification. It initializes the session with the provided SAM connection, +session ID, cryptographic keys, and configuration options. The session +automatically creates a UDP listener for receiving forwarded datagrams per SAMv3 +requirements and initializes a hash resolver for source lookups. Note: DATAGRAM3 +sources are not authenticated; use datagram2 if authentication is required. +Example usage: session, err := NewDatagram3Session(sam, "my-session", keys, +[]string{"inbound.length=1"}) #### func NewDatagram3SessionFromSubsession @@ -707,38 +438,13 @@ that has already been registered with a PRIMARY session using SESSION ADD. This constructor skips the session creation step since the subsession is already registered with the SAM bridge. -⚠️ SECURITY WARNING: Subsession sources are UNAUTHENTICATED just like primary -session sources! ⚠️ Do not trust source addresses without additional -verification. - -This function is specifically designed for use with SAMv3.3 PRIMARY sessions -where subsessions are created using SESSION ADD rather than SESSION CREATE -commands. - For PRIMARY datagram3 subsessions, UDP forwarding is mandatory (SAMv3 -requirement). The UDP connection must be provided for proper datagram reception -via UDP forwarding. +requirement). The UDP connection must be provided for proper datagram reception. +Note: Sources are not authenticated; use NewDatagramSubSession if authentication +is required. -DATAGRAM3 subsessions share the same cryptographic keys and I2P tunnels as the -PRIMARY session, but can be differentiated using LISTEN_PORT for incoming -datagram routing. - -Parameters: - - - sam: SAM connection for data operations (separate from the primary session's control connection) - - id: The subsession ID that was already registered with SESSION ADD - - keys: The I2P keys from the primary session (shared across all subsessions) - - options: Configuration options for the subsession - - udpConn: UDP connection for receiving forwarded datagrams (required, not nil) - -Returns a Datagram3Session ready for use without attempting to create a new SAM -session. - -Example usage with PRIMARY session: - - primary, err := sam.NewPrimarySession("main", keys, options) - udpConn, _ := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}) - sub, err := NewDatagram3SessionFromSubsession(sam, "sub1", keys, options, udpConn) +Example usage: sub, err := NewDatagram3SessionFromSubsession(sam, "sub1", keys, +options, udpConn) #### func (*Datagram3Session) Addr @@ -756,47 +462,20 @@ func (s *Datagram3Session) Close() error ``` Close terminates the datagram3 session and cleans up all resources. This method ensures proper cleanup of the UDP connection and I2P tunnels. After calling -Close(), the session cannot be reused. - -Example usage: - - defer session.Close() +Close(), the session cannot be reused. Example usage: defer session.Close() #### func (*Datagram3Session) NewReader ```go func (s *Datagram3Session) NewReader() *Datagram3Reader ``` -NewReader creates a Datagram3Reader for receiving UNAUTHENTICATED datagrams. - -⚠️ SECURITY WARNING: All datagrams received have UNAUTHENTICATED sources! ⚠️ Do -not trust source identity without additional verification. - -This method initializes a new reader with buffered channels for asynchronous -datagram reception. The reader must be started manually with receiveLoop() for -continuous operation. - -Unlike DATAGRAM/DATAGRAM2, received datagrams contain 32-byte hashes rather than -full destinations. Applications must call ResolveSource() on received datagrams -to obtain the full destination for replies. The session's resolver cache -minimizes lookup overhead. - -Example usage: - - reader := session.NewReader() - go reader.receiveLoop() - for { - datagram, err := reader.ReceiveDatagram() - if err != nil { - // Handle error - } - // SECURITY: datagram.SourceHash is UNAUTHENTICATED! - // Verify using application-layer authentication before trusting - if err := datagram.ResolveSource(session); err != nil { - // Handle resolution error - } - // Process datagram.Data - } +NewReader creates a Datagram3Reader for receiving datagrams with hash-based +sources. This method initializes a new reader with buffered channels for +asynchronous datagram reception. The reader must be started manually with +receiveLoop() for continuous operation. Received datagrams contain 32-byte +hashes; call ResolveSource() to obtain full destinations for replies. Example +usage: reader := session.NewReader(); go reader.receiveLoop(); datagram, err := +reader.ReceiveDatagram() #### func (*Datagram3Session) NewWriter @@ -806,19 +485,9 @@ func (s *Datagram3Session) NewWriter() *Datagram3Writer NewWriter creates a Datagram3Writer for sending datagrams to I2P destinations. This method initializes a new writer with a default timeout of 30 seconds for send operations. The timeout can be customized using the SetTimeout method on -the returned writer. - -Destinations can be specified as full base64 destinations, hostnames (.i2p), or -b32 addresses. This includes b32 addresses derived from received DATAGRAM3 -hashes after resolution. - -Maximum datagram size is 31744 bytes total (including headers), with 11 KB -recommended for best reliability across the I2P network. - -Example usage: - - writer := session.NewWriter().SetTimeout(60*time.Second) - err := writer.SendDatagram(data, destination) +the returned writer. Example usage: writer := +session.NewWriter().SetTimeout(60*time.Second); err := writer.SendDatagram(data, +dest) #### func (*Datagram3Session) PacketConn @@ -830,9 +499,6 @@ method provides compatibility with standard Go networking code by wrapping the datagram3 session in a PacketConn interface. The returned connection manages its own reader and writer and implements all standard net.PacketConn methods. -⚠️ SECURITY WARNING: All sources are UNAUTHENTICATED! ⚠️ Do not trust addresses -received via ReadFrom without verification. - The connection is automatically cleaned up by a finalizer if Close() is not called, but explicit Close() calls are strongly recommended to prevent resource leaks. @@ -842,9 +508,8 @@ Example usage: conn := session.PacketConn() defer conn.Close() - // Receive with UNAUTHENTICATED source + // Receive source n, addr, err := conn.ReadFrom(buffer) - // addr is UNAUTHENTICATED! // Send reply n, err = conn.WriteTo(reply, addr) @@ -882,9 +547,6 @@ This automatically resolves the source hash if not already resolved, then sends the reply. The source hash is resolved via NAMING LOOKUP and cached to avoid repeated lookups. -⚠️ SECURITY WARNING: Even after resolution, the source is still UNAUTHENTICATED! -⚠️ Do not trust the reply destination without additional verification. - Example usage: // Receive datagram diff --git a/datagram3/DOC.md.backup b/datagram3/DOC.md.backup new file mode 100644 index 000000000..2fff1c9e9 --- /dev/null +++ b/datagram3/DOC.md.backup @@ -0,0 +1,1210 @@ +# datagram3 +-- + import "github.com/go-i2p/go-sam-go/datagram3" + +Package datagram3 provides repliable but UNAUTHENTICATED datagram sessions for +I2P. + +# CRITICAL SECURITY WARNING + +⚠️ DATAGRAM3 sources are NOT authenticated and can be spoofed by malicious +actors! + +⚠️ Any attacker can claim to be any sender by providing a fake hash. + +⚠️ Do NOT trust source identity without additional application-level +authentication. + +⚠️ If you need authenticated sources, use DATAGRAM2 instead. + +# Overview + +DATAGRAM3 provides UDP-like messaging over I2P with hash-based source +identification. This format prioritizes low overhead over source verification, +making it suitable for applications that implement their own authentication +layer or where source identity is not security-critical. + +Key features: + + - Repliable datagrams (can send replies to sender) + - Unauthenticated sources (hash-based, spoofable) + - Offline signature support + - UDP-like messaging (unreliable, unordered) + - Maximum 31744 bytes (recommended 11 KB for reliability) + - Automatic hash-to-destination caching via NAMING LOOKUP + +# Key Differences from DATAGRAM and DATAGRAM2 + + Feature | DATAGRAM | DATAGRAM2 | DATAGRAM3 + ---------------------|----------|-----------|---------- + Authenticated | Yes | Yes | NO + Repliable | Yes | Yes | Yes + Replay Protection | No | Yes | No + Offline Signatures | No | Yes | Yes + Source Format | Full | Full | 32-byte hash + Source Verification | Yes | Yes | NO + Reply Overhead | Low | Low | Higher (NAMING LOOKUP) + +# Security Considerations + +DATAGRAM3 is appropriate when: + + - Application implements its own authentication layer + - Source identity is not security-critical + - High-volume messaging requiring low overhead + - Anonymous bulletin boards or chat systems + - Telemetry or metrics collection + +DATAGRAM3 is NOT appropriate for: + + - Financial transactions + - Identity-based access control + - Cryptographic protocols relying on source verification + - Any system where source spoofing has security implications + +# I2P Timing Considerations + +Session creation: 2-5 minutes for I2P tunnel establishment + +Datagram delivery: Variable latency (network-dependent) + +Hash resolution: Additional network round-trip for NAMING LOOKUP (cached after +first use) + +Recommended: Use generous timeouts (5+ minutes for session creation) and retry +logic with exponential backoff. Distinguish between I2P network delays and +actual failures. + +# Basic Usage + +Create a session: + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + sam, err := common.NewSAM("127.0.0.1:7656") + session, err := datagram3.NewDatagram3Session(sam, "my-session", keys, []string{"inbound.length=1"}) + defer session.Close() + +Receive datagrams with hash resolution: + + reader := session.NewReader() + go reader.receiveLoop() + for { + dg, err := reader.ReceiveDatagram() + if err != nil { + log.Error(err) + continue + } + // SECURITY: dg.SourceHash is UNAUTHENTICATED! + log.Warn("Received from unverified hash:", hex.EncodeToString(dg.SourceHash)) + + // Resolve hash to full destination for reply (cached after first lookup) + if err := dg.ResolveSource(session); err != nil { + log.Error("Failed to resolve:", err) + continue + } + + // Process data and reply (source still unverified!) + writer := session.NewWriter() + writer.SendDatagram([]byte("reply"), dg.Source) + } + +Send datagrams: + + writer := session.NewWriter().SetTimeout(30*time.Second) + err := writer.SendDatagram(data, destination) + +# Hash Resolution for Replies + +Received DATAGRAM3 messages contain a 44-byte base64 hash (32 bytes binary) +instead of the full sender destination. To reply, the hash must be converted to +a b32.i2p address and resolved via NAMING LOOKUP: + + 1. Receive 44-byte base64 hash from SAM bridge + 2. Base64-decode to 32 bytes binary + 3. Base32-encode to 52 characters + 4. Append ".b32.i2p" suffix + 5. Use resulting address for NAMING LOOKUP to get full destination + 6. Cache full destination to avoid repeated lookups + 7. Use full destination for SendDatagram() + +The session maintains a HashResolver cache to minimize lookup overhead. +Applications replying to the same source repeatedly benefit from caching. + +Example with manual hash handling: + + // Get b32 address without full resolution (fast, no network I/O) + b32Addr := datagram.GetSourceB32() + log.Info("Received from (unverified):", b32Addr) + + // Check if already cached (no network I/O) + if dest, ok := session.resolver.GetCached(datagram.SourceHash); ok { + writer.SendDatagram(reply, dest) + } else { + // Resolve with network I/O + if err := datagram.ResolveSource(session); err != nil { + log.Error(err) + } + } + +# PRIMARY Session Usage + +DATAGRAM3 supports SAMv3.3 PRIMARY sessions for sharing tunnels across multiple +subsessions: + + primary, err := sam.NewPrimarySession("main", keys, options) + udpConn, _ := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}) + sub1, err := datagram3.NewDatagram3SessionFromSubsession(sam, "sub1", keys, options, udpConn) + +Each subsession can use LISTEN_PORT to differentiate incoming datagrams. + +# Implementation Status + +DATAGRAM3 is a new format specified in early 2025. This is one of the first +implementations. Check SAM bridge documentation for I2P router support (Java I2P +0.9.x+, i2pd 2.x+). + +Current implementation status: + + - Core session management: ✅ Implemented + - UDP forwarding: ✅ Implemented + - Hash resolution and caching: ✅ Implemented + - Send/receive operations: ✅ Implemented + - PacketConn interface: ✅ Implemented + - PRIMARY session integration: ⏸️ Deferred (low priority) + +# Attack Scenarios + +Source Spoofing: Attacker claims to be legitimate sender by providing fake hash. +Applications trust unauthenticated source for security decisions. Mitigation: +Application-layer authentication required. + +Cache Poisoning: Attacker floods with fake sources causing cache to grow +unbounded. Mitigation: Implement cache size limits and TTL (planned +enhancement). + +Impersonation: Attacker replies pretending to be original sender. No +cryptographic verification of source identity. Mitigation: Use DATAGRAM2 for +authenticated sources. + +# Performance Considerations + +Hash resolution overhead: First reply to any source requires NAMING LOOKUP +(network I/O). Subsequent replies to same source use cached destination (fast). +Cache hit rate improves with repeated communication to same peers. Consider +pre-resolving known peers during initialization for critical paths. + +Memory usage: Hash resolver cache grows unbounded. Monitor cache size with +CacheSize(). Consider implementing periodic cache clearing for long-running +applications. Future enhancement: LRU eviction policy and cache size limits. + +# Protocol Compliance + +This implementation follows the SAMv3 specification for DATAGRAM3 sessions: + + - STYLE=DATAGRAM3 (not DATAGRAM or DATAGRAM2) + - UDP forwarding (PORT/HOST) is mandatory for SAMv3 + - Supports SAM 3.2+ features (FROM_PORT/TO_PORT) + - Supports SAM 3.3+ features (SEND_TAGS, TAG_THRESHOLD, EXPIRES, SEND_LEASESET) + - Offline signature support via SIGNATURE_TYPE + - PRIMARY session subsession support + +# See Also + +Package datagram: Legacy DATAGRAM sessions (authenticated, repliable) + +Package datagram2: DATAGRAM2 sessions (authenticated, repliable, replay +protection) + +Package stream: TCP-like reliable connections + +Package raw: Encrypted but unauthenticated datagrams (non-repliable) + +Package primary: PRIMARY session management for multiple subsessions + +## Usage + +#### type Datagram3 + +```go +type Datagram3 struct { + Data []byte // Raw datagram payload (up to ~31KB) + SourceHash []byte // 32-byte UNAUTHENTICATED hash (spoofable!) + Source i2pkeys.I2PAddr // Resolved destination (nil until ResolveSource) + Local i2pkeys.I2PAddr // Local destination (this session) +} +``` + +Datagram3 represents an I2P datagram3 message with UNAUTHENTICATED source. + +⚠️ CRITICAL SECURITY WARNING: SourceHash is NOT authenticated and can be +spoofed! ⚠️ Any malicious actor can claim to be any source by providing a fake +hash. ⚠️ Applications MUST implement their own authentication if source identity +matters. ⚠️ Use DATAGRAM2 if you need cryptographically authenticated sources. + +This structure encapsulates the payload data along with the unauthenticated +source hash and optional resolved destination. The SourceHash is always present +(32 bytes), while Source is only populated after calling ResolveSource() to +perform NAMING LOOKUP. + +Fields: + + - Data: Raw datagram payload (up to ~31KB) + - SourceHash: 32-byte UNAUTHENTICATED hash of sender (spoofable!) + - Source: Resolved full destination (nil until ResolveSource() called) + - Local: Local destination (this session) + +Example usage: + + // Received datagram has only hash, not full source + log.Warn("Received from UNAUTHENTICATED hash:", hex.EncodeToString(dg.SourceHash)) + + // Resolve hash to full destination for reply + if err := dg.ResolveSource(session); err != nil { + return err + } + + // Now can reply using resolved source (still unverified!) + writer.SendDatagram(reply, dg.Source) + +#### func (*Datagram3) GetSourceB32 + +```go +func (d *Datagram3) GetSourceB32() string +``` +GetSourceB32 returns the b32.i2p address for the source hash without full +resolution. This converts the 32-byte hash to a base32-encoded .b32.i2p address +string without performing NAMING LOOKUP. This is faster than full resolution and +sufficient for display, logging, or caching purposes. + +⚠️ SECURITY WARNING: The returned address is still UNAUTHENTICATED! ⚠️ This +method does not add source verification. + +Returns empty string if SourceHash is invalid (not 32 bytes). + +Example usage: + + b32Addr := datagram.GetSourceB32() + log.Info("Received from (unverified):", b32Addr) + +#### func (*Datagram3) ResolveSource + +```go +func (d *Datagram3) ResolveSource(session *Datagram3Session) error +``` +ResolveSource resolves the source hash to a full I2P destination for replying. +This performs a NAMING LOOKUP to convert the 32-byte hash into a full +destination address. The operation is cached in the session's resolver to avoid +repeated lookups. + +⚠️ SECURITY WARNING: Resolving the hash does NOT authenticate the source! ⚠️ +Even with full destination, the source can still be spoofed. ⚠️ This method only +enables replies, it does NOT verify identity. + +Process: + + 1. Check if already resolved (Source not nil) + 2. Validate SourceHash is 32 bytes + 3. Convert hash to b32.i2p address (base32 encoding) + 4. Perform NAMING LOOKUP via SAM bridge + 5. Cache result in session resolver + 6. Populate Source field with full destination + +This is an expensive operation (network round-trip) so results are cached. +Applications replying to the same source repeatedly benefit from caching. + +Example usage: + + if err := datagram.ResolveSource(session); err != nil { + log.Error("Failed to resolve source:", err) + return err + } + // datagram.Source now contains full destination + +#### type Datagram3Addr + +```go +type Datagram3Addr struct { +} +``` + +Datagram3Addr implements net.Addr interface for I2P datagram3 addresses. + +⚠️ SECURITY WARNING: If constructed from received hash, this address is +UNAUTHENTICATED! ⚠️ Do not trust the address for security-critical operations +without additional verification. + +This type provides standard Go networking address representation for I2P +destinations, allowing seamless integration with existing Go networking code +that expects net.Addr. The address can wrap either a full I2P destination or +just a hash from reception. + +Example usage: + + addr := &Datagram3Addr{addr: destination, hash: sourceHash} + fmt.Println(addr.Network(), addr.String()) + +#### func (*Datagram3Addr) Network + +```go +func (a *Datagram3Addr) Network() string +``` +Network returns the network type for I2P datagram3 addresses. This implements +the net.Addr interface by returning "datagram3" as the network type. + +#### func (*Datagram3Addr) String + +```go +func (a *Datagram3Addr) String() string +``` +String returns the string representation of the I2P address. This implements the +net.Addr interface. If a full address is available, returns base32 +representation. If only hash is available, returns the b32.i2p derived address. + +⚠️ SECURITY WARNING: Hash-derived addresses are UNAUTHENTICATED! + +#### type Datagram3Conn + +```go +type Datagram3Conn struct { +} +``` + +Datagram3Conn implements net.PacketConn interface for I2P datagram3 +communication. + +⚠️ SECURITY WARNING: All sources received via this connection are +UNAUTHENTICATED! ⚠️ Applications MUST implement their own authentication layer +if source identity matters. + +This type provides compatibility with standard Go networking patterns by +wrapping datagram3 session functionality in a familiar PacketConn interface. It +manages internal readers and writers while providing standard connection +operations. + +The connection provides thread-safe concurrent access to I2P datagram3 +operations and properly handles cleanup on close. Unlike DATAGRAM/DATAGRAM2, +sources are hash-based and not cryptographically verified. + +Example usage: + + conn := session.PacketConn() + n, addr, err := conn.ReadFrom(buffer) + // addr represents UNAUTHENTICATED source! + n, err = conn.WriteTo(data, destination) + +#### func (*Datagram3Conn) Close + +```go +func (c *Datagram3Conn) Close() error +``` +Close closes the datagram3 connection and releases associated resources. This +method implements the net.Conn interface. It closes the reader and writer but +does not close the underlying session, which may be shared by other connections. +Multiple calls to Close are safe and will return nil after the first call. + +#### func (*Datagram3Conn) LocalAddr + +```go +func (c *Datagram3Conn) LocalAddr() net.Addr +``` +LocalAddr returns the local network address as a Datagram3Addr containing the +I2P destination address of this connection's session. This method implements the +net.Conn interface and provides access to the local I2P destination. + +#### func (*Datagram3Conn) Read + +```go +func (c *Datagram3Conn) Read(b []byte) (n int, err error) +``` +Read implements net.Conn by wrapping ReadFrom for stream-like usage. It reads +data into the provided byte slice and returns the number of bytes read. When +reading, it also updates the remote address of the connection for subsequent +Write calls. + +⚠️ SECURITY WARNING: Remote address is UNAUTHENTICATED hash-based! + +Note: This is not typical for datagrams which are connectionless, but provides +compatibility with the net.Conn interface. + +#### func (*Datagram3Conn) ReadFrom + +```go +func (c *Datagram3Conn) ReadFrom(p []byte) (n int, addr net.Addr, err error) +``` +ReadFrom reads an UNAUTHENTICATED datagram from the connection. + +⚠️ CRITICAL SECURITY WARNING: Source addresses are NOT authenticated! ⚠️ The +returned address contains an UNAUTHENTICATED hash-based source. ⚠️ Do not trust +source identity without additional verification. + +This method implements the net.PacketConn interface. It starts the receive loop +if not already started and blocks until a datagram is received. The data is +copied to the provided buffer p, and the UNAUTHENTICATED source address is +returned as a Datagram3Addr. + +The source address contains the 32-byte hash (not full destination). +Applications must resolve the hash via ResolveSource() to reply. + +#### func (*Datagram3Conn) RemoteAddr + +```go +func (c *Datagram3Conn) RemoteAddr() net.Addr +``` +RemoteAddr returns the remote network address of the connection. This method +implements the net.Conn interface. For datagram3 connections, this returns the +UNAUTHENTICATED address of the last peer that sent data (set by Read), or nil if +no data has been received yet. + +⚠️ SECURITY WARNING: Remote address is UNAUTHENTICATED! + +#### func (*Datagram3Conn) SetDeadline + +```go +func (c *Datagram3Conn) SetDeadline(t time.Time) error +``` +SetDeadline sets both read and write deadlines for the connection. This method +implements the net.Conn interface by calling both SetReadDeadline and +SetWriteDeadline with the same time value. If either deadline cannot be set, the +first error encountered is returned. + +#### func (*Datagram3Conn) SetReadDeadline + +```go +func (c *Datagram3Conn) SetReadDeadline(t time.Time) error +``` +SetReadDeadline sets the deadline for future ReadFrom calls. This method +implements the net.Conn interface. For datagram3 connections, this is currently +a placeholder implementation that always returns nil. Timeout handling is +managed differently for datagram operations. + +#### func (*Datagram3Conn) SetWriteDeadline + +```go +func (c *Datagram3Conn) SetWriteDeadline(t time.Time) error +``` +SetWriteDeadline sets the deadline for future WriteTo calls. This method +implements the net.Conn interface. If the deadline is not zero, it calculates +the timeout duration and sets it on the writer for subsequent write operations. + +#### func (*Datagram3Conn) Write + +```go +func (c *Datagram3Conn) Write(b []byte) (n int, err error) +``` +Write implements net.Conn by wrapping WriteTo for stream-like usage. It writes +data to the remote address set by the last Read operation and returns the number +of bytes written. If no remote address has been set, it returns an error. Note: +This is not typical for datagrams which are connectionless, but provides +compatibility with the net.Conn interface. + +#### func (*Datagram3Conn) WriteTo + +```go +func (c *Datagram3Conn) WriteTo(p []byte, addr net.Addr) (n int, err error) +``` +WriteTo writes a datagram to the specified address. This method implements the +net.PacketConn interface. The address must be a Datagram3Addr or i2pkeys.I2PAddr +containing a valid I2P destination. The entire byte slice p is sent as a single +datagram message. + +If the address is a Datagram3Addr with only a hash (not resolved), the hash will +be resolved automatically before sending. + +#### type Datagram3Reader + +```go +type Datagram3Reader struct { +} +``` + +Datagram3Reader handles incoming UNAUTHENTICATED datagram3 reception from I2P. + +⚠️ SECURITY WARNING: All received datagrams have UNAUTHENTICATED sources! ⚠️ The +SourceHash field in received datagrams can be spoofed by attackers. ⚠️ Do not +trust source identity without additional verification. + +The reader provides asynchronous datagram reception through buffered channels, +allowing applications to receive datagrams without blocking. It manages its own +goroutine for continuous message processing and provides thread-safe access to +received datagrams. + +Unlike DATAGRAM/DATAGRAM2, sources are represented as 32-byte hashes rather than +full destinations. Applications must call ResolveSource() on received datagrams +to obtain the full destination for replies. The session's resolver cache +minimizes lookup overhead. + +Example usage: + + reader := session.NewReader() + for { + datagram, err := reader.ReceiveDatagram() + if err != nil { + // Handle error + } + // SECURITY: datagram.SourceHash is UNAUTHENTICATED! + // Verify using application-layer authentication before trusting + if err := datagram.ResolveSource(session); err != nil { + // Handle resolution error + } + // Now datagram.Source contains full destination for reply + } + +#### func (*Datagram3Reader) Close + +```go +func (r *Datagram3Reader) Close() error +``` +Close closes the Datagram3Reader and stops its receive loop. This method safely +terminates the reader, cleans up all associated resources, and signals any +waiting goroutines to stop. It's safe to call multiple times and will not block +if the reader is already closed. + +Example usage: + + defer reader.Close() + +#### func (*Datagram3Reader) ReceiveDatagram + +```go +func (r *Datagram3Reader) ReceiveDatagram() (*Datagram3, error) +``` +ReceiveDatagram receives a single UNAUTHENTICATED datagram from the I2P network. + +⚠️ CRITICAL SECURITY WARNING: Sources are NOT authenticated and can be spoofed! +⚠️ Do not trust datagram.SourceHash without additional verification. ⚠️ Use +application-layer authentication if source identity matters. + +This method blocks until a datagram is received or an error occurs, returning +the received datagram with its data and UNAUTHENTICATED hash-based source. It +handles concurrent access safely and provides proper error handling for network +issues. + +Unlike DATAGRAM/DATAGRAM2, received datagrams contain only a 32-byte hash (not +full destination). Applications must call ResolveSource() to convert the hash to +a full destination for replies. + +Example usage: + + datagram, err := reader.ReceiveDatagram() + if err != nil { + // Handle error + } + // SECURITY: datagram.SourceHash is UNAUTHENTICATED! + log.Warn("Received from unverified source:", hex.EncodeToString(datagram.SourceHash)) + // Resolve hash for reply (expensive, cached) + if err := datagram.ResolveSource(session); err != nil { + log.Error(err) + } + +#### type Datagram3Session + +```go +type Datagram3Session struct { + *common.BaseSession +} +``` + +Datagram3Session represents a repliable but UNAUTHENTICATED datagram3 session. + +⚠️ CRITICAL SECURITY WARNING: Source addresses are NOT authenticated and can be +spoofed! ⚠️ Applications requiring source authentication MUST use DATAGRAM2 +instead. ⚠️ Do NOT trust source identity without additional application-level +authentication. + +DATAGRAM3 provides UDP-like messaging with hash-based source identification +instead of full authenticated destinations. This reduces overhead at the cost of +source verification. Received datagrams contain a 32-byte hash that requires +NAMING LOOKUP to resolve for replies. + +Key differences from DATAGRAM/DATAGRAM2: + + - Repliable: Can reply to sender (like DATAGRAM/DATAGRAM2) + - Unauthenticated: Source is NOT verified (unlike DATAGRAM/DATAGRAM2) + - Hash-based source: 32-byte hash instead of full destination + - Lower overhead: No signature verification required + - Reply overhead: Requires NAMING LOOKUP to resolve hash + +The session manages I2P tunnels and provides methods for creating readers and +writers. For SAMv3 mode, it uses UDP forwarding where datagrams are received via +a local UDP socket that the SAM bridge forwards to. The session maintains a hash +resolver cache to avoid repeated NAMING LOOKUP operations when replying to the +same source. + +I2P Timing Considerations: + + - Session creation: 2-5 minutes for tunnel establishment + - Message delivery: Variable latency (network-dependent) + - Hash resolution: Additional network round-trip for NAMING LOOKUP + - Use generous timeouts and retry logic with exponential backoff + +Example usage: + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + session, err := NewDatagram3Session(sam, "my-session", keys, options) + reader := session.NewReader() + dg, err := reader.ReceiveDatagram() + // dg.SourceHash is UNAUTHENTICATED - verify separately if needed! + if err := dg.ResolveSource(session); err != nil { + log.Fatal(err) + } + session.NewWriter().SendDatagram(reply, dg.Source) + +#### func NewDatagram3Session + +```go +func NewDatagram3Session(sam *common.SAM, id string, keys i2pkeys.I2PKeys, options []string) (*Datagram3Session, error) +``` +NewDatagram3Session creates a new repliable but UNAUTHENTICATED datagram3 +session. + +⚠️ CRITICAL SECURITY WARNING: DATAGRAM3 sources are NOT authenticated and can be +spoofed! ⚠️ Do not trust source addresses without additional application-level +authentication. ⚠️ If you need authenticated sources, use DATAGRAM2 instead. + +This function establishes a new DATAGRAM3 session with the provided SAM +connection, session ID, cryptographic keys, and configuration options. It +automatically creates a UDP listener for receiving forwarded datagrams (SAMv3 +requirement) and configures the session with PORT/HOST parameters. + +DATAGRAM3 provides repliable datagrams with hash-based source identification: + + - Repliable: Can reply to sender (requires hash resolution via NAMING LOOKUP) + - Unauthenticated: Source is NOT verified (unlike DATAGRAM/DATAGRAM2) + - Hash-based source: 32-byte hash instead of full destination + - Lower overhead: No signature verification required + +I2P Timing Considerations: + + - Session creation can take 2-5 minutes for I2P tunnel establishment + - Use context.WithTimeout with generous timeouts (5+ minutes recommended) + - Hash resolution adds network round-trip for replies (cached after first lookup) + - Implement exponential backoff retry logic for connection attempts + - Distinguish between I2P timing delays and actual failures + +Example usage: + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + session, err := NewDatagram3Session(sam, "my-session", keys, []string{"inbound.length=1"}) + reader := session.NewReader() + go reader.receiveLoop() + dg, err := reader.ReceiveDatagram() + // dg.SourceHash is UNAUTHENTICATED! + if err := dg.ResolveSource(session); err != nil { + log.Fatal(err) + } + session.NewWriter().SendDatagram(reply, dg.Source) + +#### func NewDatagram3SessionFromSubsession + +```go +func NewDatagram3SessionFromSubsession(sam *common.SAM, id string, keys i2pkeys.I2PKeys, options []string, udpConn *net.UDPConn) (*Datagram3Session, error) +``` +NewDatagram3SessionFromSubsession creates a Datagram3Session for a subsession +that has already been registered with a PRIMARY session using SESSION ADD. This +constructor skips the session creation step since the subsession is already +registered with the SAM bridge. + +⚠️ SECURITY WARNING: Subsession sources are UNAUTHENTICATED just like primary +session sources! ⚠️ Do not trust source addresses without additional +verification. + +This function is specifically designed for use with SAMv3.3 PRIMARY sessions +where subsessions are created using SESSION ADD rather than SESSION CREATE +commands. + +For PRIMARY datagram3 subsessions, UDP forwarding is mandatory (SAMv3 +requirement). The UDP connection must be provided for proper datagram reception +via UDP forwarding. + +DATAGRAM3 subsessions share the same cryptographic keys and I2P tunnels as the +PRIMARY session, but can be differentiated using LISTEN_PORT for incoming +datagram routing. + +Parameters: + + - sam: SAM connection for data operations (separate from the primary session's control connection) + - id: The subsession ID that was already registered with SESSION ADD + - keys: The I2P keys from the primary session (shared across all subsessions) + - options: Configuration options for the subsession + - udpConn: UDP connection for receiving forwarded datagrams (required, not nil) + +Returns a Datagram3Session ready for use without attempting to create a new SAM +session. + +Example usage with PRIMARY session: + + primary, err := sam.NewPrimarySession("main", keys, options) + udpConn, _ := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}) + sub, err := NewDatagram3SessionFromSubsession(sam, "sub1", keys, options, udpConn) + +#### func (*Datagram3Session) Addr + +```go +func (s *Datagram3Session) Addr() i2pkeys.I2PAddr +``` +Addr returns the local I2P address of this datagram3 session. This is the +destination address that other I2P nodes can use to send datagrams to this +session. + +#### func (*Datagram3Session) Close + +```go +func (s *Datagram3Session) Close() error +``` +Close terminates the datagram3 session and cleans up all resources. This method +ensures proper cleanup of the UDP connection and I2P tunnels. After calling +Close(), the session cannot be reused. + +Example usage: + + defer session.Close() + +#### func (*Datagram3Session) NewReader + +```go +func (s *Datagram3Session) NewReader() *Datagram3Reader +``` +NewReader creates a Datagram3Reader for receiving UNAUTHENTICATED datagrams. + +⚠️ SECURITY WARNING: All datagrams received have UNAUTHENTICATED sources! ⚠️ Do +not trust source identity without additional verification. + +This method initializes a new reader with buffered channels for asynchronous +datagram reception. The reader must be started manually with receiveLoop() for +continuous operation. + +Unlike DATAGRAM/DATAGRAM2, received datagrams contain 32-byte hashes rather than +full destinations. Applications must call ResolveSource() on received datagrams +to obtain the full destination for replies. The session's resolver cache +minimizes lookup overhead. + +Example usage: + + reader := session.NewReader() + go reader.receiveLoop() + for { + datagram, err := reader.ReceiveDatagram() + if err != nil { + // Handle error + } + // SECURITY: datagram.SourceHash is UNAUTHENTICATED! + // Verify using application-layer authentication before trusting + if err := datagram.ResolveSource(session); err != nil { + // Handle resolution error + } + // Process datagram.Data + } + +#### func (*Datagram3Session) NewWriter + +```go +func (s *Datagram3Session) NewWriter() *Datagram3Writer +``` +NewWriter creates a Datagram3Writer for sending datagrams to I2P destinations. +This method initializes a new writer with a default timeout of 30 seconds for +send operations. The timeout can be customized using the SetTimeout method on +the returned writer. + +Destinations can be specified as full base64 destinations, hostnames (.i2p), or +b32 addresses. This includes b32 addresses derived from received DATAGRAM3 +hashes after resolution. + +Maximum datagram size is 31744 bytes total (including headers), with 11 KB +recommended for best reliability across the I2P network. + +Example usage: + + writer := session.NewWriter().SetTimeout(60*time.Second) + err := writer.SendDatagram(data, destination) + +#### func (*Datagram3Session) PacketConn + +```go +func (s *Datagram3Session) PacketConn() net.PacketConn +``` +PacketConn returns a net.PacketConn interface for this datagram3 session. This +method provides compatibility with standard Go networking code by wrapping the +datagram3 session in a PacketConn interface. The returned connection manages its +own reader and writer and implements all standard net.PacketConn methods. + +⚠️ SECURITY WARNING: All sources are UNAUTHENTICATED! ⚠️ Do not trust addresses +received via ReadFrom without verification. + +The connection is automatically cleaned up by a finalizer if Close() is not +called, but explicit Close() calls are strongly recommended to prevent resource +leaks. + +Example usage: + + conn := session.PacketConn() + defer conn.Close() + + // Receive with UNAUTHENTICATED source + n, addr, err := conn.ReadFrom(buffer) + // addr is UNAUTHENTICATED! + + // Send reply + n, err = conn.WriteTo(reply, addr) + +#### type Datagram3Writer + +```go +type Datagram3Writer struct { +} +``` + +Datagram3Writer handles outgoing datagram3 transmission to I2P destinations. It +provides methods for sending datagrams with configurable timeouts and handles +the underlying SAM protocol communication for message delivery. The writer +supports method chaining for configuration and provides error handling for send +operations. + +Maximum datagram size is 31744 bytes total (including headers), with 11 KB +recommended for best reliability. Destinations can be specified as full base64 +destinations, hostnames (.i2p), or b32 addresses. + +Example usage: + + writer := session.NewWriter().SetTimeout(30*time.Second) + err := writer.SendDatagram(data, destination) + +#### func (*Datagram3Writer) ReplyToDatagram + +```go +func (w *Datagram3Writer) ReplyToDatagram(data []byte, original *Datagram3) error +``` +ReplyToDatagram sends a reply to a received DATAGRAM3 message. + +This automatically resolves the source hash if not already resolved, then sends +the reply. The source hash is resolved via NAMING LOOKUP and cached to avoid +repeated lookups. + +⚠️ SECURITY WARNING: Even after resolution, the source is still UNAUTHENTICATED! +⚠️ Do not trust the reply destination without additional verification. + +Example usage: + + // Receive datagram + dg, err := reader.ReceiveDatagram() + if err != nil { + return err + } + + // Reply (automatically resolves hash) + writer := session.NewWriter() + err = writer.ReplyToDatagram([]byte("reply"), dg) + +#### func (*Datagram3Writer) SendDatagram + +```go +func (w *Datagram3Writer) SendDatagram(data []byte, dest i2pkeys.I2PAddr) error +``` +SendDatagram sends a datagram to the specified I2P destination. + +This method uses the SAMv3 UDP approach: sending via UDP socket to port 7655 +with DATAGRAM3 format. The destination can be: + + - Full base64 destination (516+ chars) + - Hostname (.i2p address) + - B32 address (52 chars + .b32.i2p) + - B32 address derived from received DATAGRAM3 hash (via ResolveSource) + +Maximum datagram size is 31744 bytes total (including headers), with 11 KB +recommended for best reliability across the I2P network. It blocks until the +datagram is sent or an error occurs, respecting the configured timeout. + +Example usage: + + // Send to full destination + err := writer.SendDatagram([]byte("hello world"), destinationAddr) + + // Reply to received datagram (requires hash resolution) + if err := receivedDatagram.ResolveSource(session); err != nil { + return err + } + err := writer.SendDatagram([]byte("reply"), receivedDatagram.Source) + +#### func (*Datagram3Writer) SetTimeout + +```go +func (w *Datagram3Writer) SetTimeout(timeout time.Duration) *Datagram3Writer +``` +SetTimeout sets the timeout for datagram3 write operations. This method +configures the maximum time to wait for datagram send operations to complete. +The timeout prevents indefinite blocking during network congestion or connection +issues. Returns the writer instance for method chaining convenience. + +Example usage: + + writer.SetTimeout(30*time.Second).SendDatagram(data, destination) + +#### type HashResolver + +```go +type HashResolver struct { +} +``` + +HashResolver provides caching for hash-to-destination lookups via NAMING LOOKUP. +This prevents repeated network queries for the same hash, which is critical for +DATAGRAM3 performance since every received datagram contains only a hash. + +⚠️ SECURITY WARNING: Resolving hashes does NOT authenticate sources! ⚠️ Even +with cached full destinations, sources remain UNAUTHENTICATED. ⚠️ Cache entries +are based on hash values which can be spoofed. + +The resolver maintains an in-memory cache mapping b32.i2p addresses to full I2P +destinations. This cache is thread-safe using RWMutex and grows unbounded +(applications should monitor memory usage for long-running sessions receiving +from many sources). + +Hash Resolution Process: + + 1. Convert 32-byte hash to base32 (52 characters) + 2. Append ".b32.i2p" suffix + 3. Check cache for existing entry + 4. If not cached, perform NAMING LOOKUP via SAM bridge + 5. Cache successful result + 6. Return full I2P destination + +Example usage: + + resolver := NewHashResolver(sam) + dest, err := resolver.ResolveHash(hashBytes) + if err != nil { + log.Error("Resolution failed:", err) + } + +#### func NewHashResolver + +```go +func NewHashResolver(sam *common.SAM) *HashResolver +``` +NewHashResolver creates a new hash resolver with empty cache. The resolver uses +the provided SAM connection for NAMING LOOKUP operations when cache misses +occur. + +Example usage: + + resolver := NewHashResolver(sam) + +#### func (*HashResolver) CacheSize + +```go +func (r *HashResolver) CacheSize() int +``` +CacheSize returns the current number of cached entries. This is useful for +monitoring memory usage and cache effectiveness. + +Example usage: + + size := resolver.CacheSize() + log.Info("Cache contains", size, "entries") + +#### func (*HashResolver) Clear + +```go +func (r *HashResolver) Clear() +``` +Clear removes all cached entries. This is useful for testing, memory management +in long-running sessions, or when you want to force fresh NAMING LOOKUP +operations. + +⚠️ WARNING: Clearing cache will cause subsequent resolutions to perform network +I/O. ⚠️ Only clear cache if necessary (testing, memory pressure, or security +concerns). + +Applications with memory constraints may want to implement periodic cache +clearing or LRU eviction policies on top of this basic cache. + +Example usage: + + // Clear cache after processing batch + resolver.Clear() + + // Or clear periodically + ticker := time.NewTicker(1 * time.Hour) + go func() { + for range ticker.C { + resolver.Clear() + } + }() + +#### func (*HashResolver) GetCached + +```go +func (r *HashResolver) GetCached(hash []byte) (i2pkeys.I2PAddr, bool) +``` +GetCached returns cached destination without performing lookup. This allows +checking if a hash has been previously resolved without triggering a potentially +expensive NAMING LOOKUP operation. + +Returns: + + - destination: Full I2P destination if cached + - found: true if entry exists in cache, false otherwise + +This method is useful for applications that want to avoid network I/O and only +use already-resolved destinations. It's also useful for testing cache behavior. + +Example usage: + + if dest, ok := resolver.GetCached(hash); ok { + // Use cached destination without network lookup + writer.SendDatagram(reply, dest) + } else { + // Hash not yet resolved - decide whether to resolve now + log.Info("Hash not in cache, resolution required for reply") + } + +#### func (*HashResolver) ResolveHash + +```go +func (r *HashResolver) ResolveHash(hash []byte) (i2pkeys.I2PAddr, error) +``` +ResolveHash converts a 32-byte hash to a full I2P destination using NAMING +LOOKUP. + +⚠️ SECURITY WARNING: This does NOT authenticate the source! ⚠️ Resolution only +enables replies, it does NOT verify identity. ⚠️ Malicious actors can provide +hashes that resolve to attacker-controlled destinations. + +Process: + + 1. Validate hash is exactly 32 bytes + 2. Convert to b32.i2p address (base32 encoding + suffix) + 3. Check cache for existing result + 4. If cached, return immediately (fast path) + 5. If not cached, perform NAMING LOOKUP (slow path, network I/O) + 6. Cache successful result for future lookups + 7. Return full destination + +This is an expensive operation on cache misses due to network round-trip to I2P +router. Applications should minimize unnecessary resolutions by caching at +application level or reusing the same session resolver. + +Error conditions: + + - Invalid hash length (not 32 bytes) + - Base32 encoding failure (malformed hash) + - NAMING LOOKUP failure (hash not resolvable, network error, etc.) + +Example usage: + + dest, err := resolver.ResolveHash(datagram.SourceHash) + if err != nil { + log.Error("Failed to resolve hash:", err) + return err + } + // dest contains full I2P destination (still unverified!) + writer.SendDatagram(reply, dest) + +#### type SAM + +```go +type SAM struct { + *common.SAM +} +``` + +SAM wraps common.SAM to provide datagram3-specific functionality for I2P +messaging. This type extends the base SAM functionality with methods +specifically designed for DATAGRAM3 communication, providing repliable but +UNAUTHENTICATED datagrams with hash-based source identification. + +⚠️ SECURITY WARNING: DATAGRAM3 sources are NOT authenticated and can be spoofed! +⚠️ Do not trust source addresses without additional application-level +authentication. ⚠️ If you need authenticated sources, use DATAGRAM2 instead. + +DATAGRAM3 uses 32-byte hashes instead of full destinations for source +identification, reducing overhead at the cost of source verification. +Applications requiring source authentication MUST implement their own +authentication layer. + +Example usage: sam := &SAM{SAM: baseSAM}; session, err := +sam.NewDatagram3Session(id, keys, options) + +#### func (*SAM) NewDatagram3Session + +```go +func (s *SAM) NewDatagram3Session(id string, keys i2pkeys.I2PKeys, options []string) (*Datagram3Session, error) +``` +NewDatagram3Session creates a new repliable but UNAUTHENTICATED datagram3 +session. This method establishes a new DATAGRAM3 session for UDP-like messaging +over I2P with hash-based source identification. Session creation can take 2-5 +minutes due to I2P tunnel establishment, so generous timeouts are recommended. + +⚠️ SECURITY WARNING: DATAGRAM3 sources are NOT authenticated and can be spoofed! +⚠️ Applications requiring source authentication should use DATAGRAM2 instead. + +DATAGRAM3 provides repliable datagrams with minimal overhead by using hash-based +source identification instead of full authenticated destinations. Received +datagrams contain a 32-byte hash that must be resolved via NAMING LOOKUP to +reply. The session maintains a cache to avoid repeated lookups. + +Key differences from DATAGRAM and DATAGRAM2: + + - Repliable: Can reply to sender (like DATAGRAM/DATAGRAM2) + - Unauthenticated: Source is NOT verified (unlike DATAGRAM/DATAGRAM2) + - Hash-based: Source is 32-byte hash, NOT full destination + - Lower overhead: No signature verification required + - Reply requires NAMING LOOKUP: Hash must be resolved to full destination + +Example usage: + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + session, err := sam.NewDatagram3Session("my-session", keys, []string{"inbound.length=1"}) + +#### func (*SAM) NewDatagram3SessionWithPorts + +```go +func (s *SAM) NewDatagram3SessionWithPorts(id, fromPort, toPort string, keys i2pkeys.I2PKeys, options []string) (*Datagram3Session, error) +``` +NewDatagram3SessionWithPorts creates a new datagram3 session with port +specifications. This method allows configuring specific I2CP port ranges for the +session, enabling fine-grained control over network communication ports for +advanced routing scenarios. Port configuration is useful for applications +requiring specific port mappings or PRIMARY session subsessions. This function +automatically creates a UDP listener for SAMv3 UDP forwarding (required for v3 +mode). + +⚠️ SECURITY WARNING: Port configuration does NOT add source authentication to +DATAGRAM3! ⚠️ Sources remain unauthenticated regardless of port settings. + +The FROM_PORT and TO_PORT parameters specify I2CP ports for protocol-level +communication, distinct from the UDP forwarding port which is auto-assigned by +the OS. + +Example usage: + + session, err := sam.NewDatagram3SessionWithPorts(id, "8080", "8081", keys, options) + +#### func (*SAM) NewDatagram3SessionWithSignature + +```go +func (s *SAM) NewDatagram3SessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*Datagram3Session, error) +``` +NewDatagram3SessionWithSignature creates a new datagram3 session with custom +signature type. This method allows specifying a custom cryptographic signature +type for the session, enabling advanced security configurations beyond the +default Ed25519 algorithm. DATAGRAM3 supports offline signatures, allowing +pre-signed destinations for enhanced privacy and key management flexibility. + +⚠️ SECURITY WARNING: Custom signature types do NOT add source authentication to +DATAGRAM3! ⚠️ Sources remain unauthenticated regardless of signature +configuration. + +Different signature types provide various security levels for the local +destination: + + - Ed25519 (type 7) - Recommended for most applications + - ECDSA (types 1-3) - Legacy compatibility + - RedDSA (type 11) - Advanced privacy features + +Example usage: + + session, err := sam.NewDatagram3SessionWithSignature(id, keys, options, "EdDSA_SHA512_Ed25519") diff --git a/datagram3/doc.go b/datagram3/doc.go index 8a6a42e1e..07863012c 100644 --- a/datagram3/doc.go +++ b/datagram3/doc.go @@ -1,13 +1,11 @@ // Package datagram3 provides repliable datagram sessions with hash-based source identification for I2P. // // DATAGRAM3 sessions provide repliable UDP-like messaging with hash-based source identification -// instead of full destinations. Sources are not cryptographically authenticated; applications -// requiring authenticated sources should use datagram2 instead. +// instead of full destinations. // // Key features: // - Repliable (can send replies to sender) // - Hash-based source identification (32-byte hash) -// - No source authentication (spoofable) // - Requires NAMING LOOKUP for replies // - UDP-like messaging (unreliable, unordered) // - Maximum 31744 bytes per datagram (11 KB recommended) @@ -25,6 +23,5 @@ // if err := dg.ResolveSource(session); err != nil { log.Error(err) } // session.NewWriter().SendDatagram([]byte("reply"), dg.Source) // -// See also: Package datagram (legacy, authenticated), datagram2 (authenticated with replay -// protection), stream (TCP-like), raw (non-repliable), primary (multi-session management). +// See also: Package datagram, datagram2, stream, raw, primary. package datagram3 diff --git a/datagram3/listen.go b/datagram3/listen.go index 4a20dd875..bbf2496f2 100644 --- a/datagram3/listen.go +++ b/datagram3/listen.go @@ -23,7 +23,6 @@ import ( // // Handle error // } // log.Info("Received from source:", hex.EncodeToString(datagram.SourceHash)) -// // Resolve hash for reply (expensive, cached) // if err := datagram.ResolveSource(session); err != nil { // log.Error(err) // } diff --git a/datagram3/packetconn.go b/datagram3/packetconn.go index 3ff019647..b405ca49c 100644 --- a/datagram3/packetconn.go +++ b/datagram3/packetconn.go @@ -10,15 +10,11 @@ import ( "github.com/samber/oops" ) -// ReadFrom reads an UNAUTHENTICATED datagram from the connection. -// -// ⚠️ CRITICAL SECURITY WARNING: Source addresses are NOT authenticated! -// ⚠️ The returned address contains an UNAUTHENTICATED hash-based source. -// ⚠️ Do not trust source identity without additional verification. +// ReadFrom reads a datagram from the connection. // // This method implements the net.PacketConn interface. It starts the receive loop if not // already started and blocks until a datagram is received. The data is copied to the provided -// buffer p, and the UNAUTHENTICATED source address is returned as a Datagram3Addr. +// buffer p, and the source address is returned as a Datagram3Addr. // // The source address contains the 32-byte hash (not full destination). Applications must // resolve the hash via ResolveSource() to reply. @@ -41,11 +37,11 @@ func (c *Datagram3Conn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { // Copy data to the provided buffer n = copy(p, datagram.Data) - // Create address with UNAUTHENTICATED hash + // Create address with hash // Applications can check addr.(*Datagram3Addr).hash for the hash addr = &Datagram3Addr{ addr: datagram.Source, // May be empty if not resolved - hash: datagram.SourceHash, // 32-byte UNAUTHENTICATED hash + hash: datagram.SourceHash, // 32-byte hash } return n, addr, nil @@ -189,8 +185,6 @@ func (c *Datagram3Conn) SetWriteDeadline(t time.Time) error { // When reading, it also updates the remote address of the connection for subsequent // Write calls. // -// ⚠️ SECURITY WARNING: Remote address is UNAUTHENTICATED hash-based! -// // Note: This is not typical for datagrams which are connectionless, // but provides compatibility with the net.Conn interface. func (c *Datagram3Conn) Read(b []byte) (n int, err error) { @@ -210,10 +204,8 @@ func (c *Datagram3Conn) Read(b []byte) (n int, err error) { // RemoteAddr returns the remote network address of the connection. // This method implements the net.Conn interface. For datagram3 connections, -// this returns the UNAUTHENTICATED address of the last peer that sent data (set by Read), +// this returns the address of the last peer that sent data (set by Read), // or nil if no data has been received yet. -// -// ⚠️ SECURITY WARNING: Remote address is UNAUTHENTICATED! func (c *Datagram3Conn) RemoteAddr() net.Addr { if c.remoteAddr != nil { return &Datagram3Addr{addr: *c.remoteAddr} @@ -266,9 +258,6 @@ func (c *Datagram3Conn) clearCleanup() { // the datagram3 session in a PacketConn interface. The returned connection manages // its own reader and writer and implements all standard net.PacketConn methods. // -// ⚠️ SECURITY WARNING: All sources are UNAUTHENTICATED! -// ⚠️ Do not trust addresses received via ReadFrom without verification. -// // The connection is automatically cleaned up by a finalizer if Close() is not called, // but explicit Close() calls are strongly recommended to prevent resource leaks. // @@ -277,9 +266,8 @@ func (c *Datagram3Conn) clearCleanup() { // conn := session.PacketConn() // defer conn.Close() // -// // Receive with UNAUTHENTICATED source +// // Receive source // n, addr, err := conn.ReadFrom(buffer) -// // addr is UNAUTHENTICATED! // // // Send reply // n, err = conn.WriteTo(reply, addr) diff --git a/datagram3/read.go b/datagram3/read.go index 236fdda84..8089788d1 100644 --- a/datagram3/read.go +++ b/datagram3/read.go @@ -11,26 +11,16 @@ import ( // readDatagramFromUDP reads a forwarded datagram3 message from the UDP connection. // -// ⚠️ CRITICAL SECURITY WARNING: Source is a 44-byte base64 hash, NOT authenticated! -// ⚠️ The hash can be spoofed by malicious actors - do NOT trust without verification. -// ⚠️ This is fundamentally different from DATAGRAM/DATAGRAM2 authenticated sources. -// // This is used for receiving datagrams where the SAM bridge forwards messages via UDP. // DATAGRAM3 uses hash-based source identification instead of full destinations. // // Format per SAMv3.md: // -// Line 1: $hash (44-byte base64 hash, UNAUTHENTICATED!) +// Line 1: $hash (44-byte base64 hash) // [FROM_PORT=nnn] [TO_PORT=nnn] (SAMv3.2+, may be on one or two lines) // Then: \n (empty line separator) // Remaining: $datagram_payload (raw data) // -// CRITICAL DIFFERENCE from DATAGRAM/DATAGRAM2: -// - Source is 44-byte base64 hash (32 bytes binary) -// - NOT a full destination (516+ chars) -// - Hash is UNAUTHENTICATED and spoofable -// - Must use NAMING LOOKUP to resolve hash for replies -// // The hash decodes to 32 bytes binary. To reply: // 1. Base32-encode hash to 52 characters // 2. Append ".b32.i2p" suffix @@ -57,14 +47,14 @@ func (s *Datagram3Session) readDatagramFromUDP(udpConn *net.UDPConn) (*Datagram3 return nil, oops.Errorf("invalid UDP datagram3 format: no newline found") } - // Line 1: Source hash (44-byte base64, UNAUTHENTICATED!) followed by optional FROM_PORT=nnn TO_PORT=nnn + // Line 1: Source hash (44-byte base64) followed by optional FROM_PORT=nnn TO_PORT=nnn headerLine := strings.TrimSpace(response[:firstNewline]) if headerLine == "" { return nil, oops.Errorf("empty header line in UDP datagram3") } - // Parse the header line to extract the UNAUTHENTICATED source hash + // Parse the header line to extract the source hash // Format: "$hash_base64 FROM_PORT=nnn TO_PORT=nnn" // We need to split on space and take the first part as the hash parts := strings.Fields(headerLine) @@ -72,10 +62,10 @@ func (s *Datagram3Session) readDatagramFromUDP(udpConn *net.UDPConn) (*Datagram3 return nil, oops.Errorf("empty header line in UDP datagram3") } - hashBase64 := parts[0] // First field is the UNAUTHENTICATED source hash + hashBase64 := parts[0] // First field is the source hash // Remaining parts are FROM_PORT and TO_PORT which we ignore for now - // CRITICAL VALIDATION: Hash MUST be exactly 44 bytes base64 (32 bytes binary) + // Hash MUST be exactly 44 bytes base64 (32 bytes binary) if len(hashBase64) != 44 { return nil, oops.Errorf("invalid hash length: %d (expected 44 base64 chars)", len(hashBase64)) } @@ -106,24 +96,19 @@ func (s *Datagram3Session) readDatagramFromUDP(udpConn *net.UDPConn) (*Datagram3 return s.createDatagram(hashBytes, data) } -// createDatagram constructs the final Datagram3 from parsed UNAUTHENTICATED hash and data. -// -// ⚠️ SECURITY WARNING: The hash is NOT authenticated and can be spoofed! -// ⚠️ Do NOT trust the source without additional verification. +// createDatagram constructs the final Datagram3 from parsed hash and data. // // The datagram is created with: // - Data: Raw payload bytes -// - SourceHash: 32-byte UNAUTHENTICATED hash (spoofable!) +// - SourceHash: 32-byte hash // - Source: Empty (not resolved, requires NAMING LOOKUP for replies) // - Local: This session's I2P address -// -// Applications must call ResolveSource() to convert hash to full destination for replies. func (s *Datagram3Session) createDatagram(hashBytes []byte, data string) (*Datagram3, error) { - // Create datagram with UNAUTHENTICATED hash + // Create datagram with hash // Source is empty (not resolved) - applications resolve on-demand for replies datagram := &Datagram3{ Data: []byte(data), - SourceHash: hashBytes, // 32-byte UNAUTHENTICATED hash (spoofable!) + SourceHash: hashBytes, // 32-byte hash Source: "", // Not resolved (empty = requires ResolveSource()) Local: s.Addr(), } @@ -132,7 +117,7 @@ func (s *Datagram3Session) createDatagram(hashBytes []byte, data string) (*Datag "data_len": len(datagram.Data), "hash_len": len(datagram.SourceHash), "source_set": datagram.Source != "", - }).Debug("Created datagram3 with UNAUTHENTICATED hash source") + }).Debug("Created datagram3 with hash source") return datagram, nil } diff --git a/datagram3/session_test.go b/datagram3/session_test.go index cab41ca46..6e9162747 100644 --- a/datagram3/session_test.go +++ b/datagram3/session_test.go @@ -410,7 +410,7 @@ func TestDatagram3RoundTrip(t *testing.T) { // Test sending reply t.Logf("Bob sending reply to Alice...") writerB := sessionB.NewWriter() - replyMessage := []byte("Reply from Bob! Source verification is YOUR responsibility!") + replyMessage := []byte("Reply from Bob!") err = writerB.ReplyToDatagram(replyMessage, dg) if err != nil { t.Errorf("Failed to send reply: %v", err) diff --git a/datagram3/write.go b/datagram3/write.go index f69455c12..ce85a7ea6 100644 --- a/datagram3/write.go +++ b/datagram3/write.go @@ -141,6 +141,6 @@ func (w *Datagram3Writer) ReplyToDatagram(data []byte, original *Datagram3) erro } } - // Send to resolved source (still UNAUTHENTICATED!) + // Send to resolved source return w.SendDatagram(data, original.Source) } diff --git a/primary/DOC.md b/primary/DOC.md index ab46ec1f9..36b10a865 100644 --- a/primary/DOC.md +++ b/primary/DOC.md @@ -2,6 +2,36 @@ -- import "github.com/go-i2p/go-sam-go/primary" +Package primary provides PRIMARY session management for sharing I2P tunnels +across multiple subsessions. + +PRIMARY sessions allow multiple subsessions (stream, datagram, datagram2, +datagram3, raw) to share a single set of I2P tunnels, reducing resource usage +and tunnel setup overhead. Each subsession operates independently while using +the master session's tunnels. + +Key features: + + - Single tunnel setup for multiple subsessions + - Mixed subsession types (stream, datagram, raw) + - Independent subsession lifecycle management + - Reduced resource usage and setup time + - SAMv3.3 PRIMARY protocol compliance + +Primary session creation requires 2-5 minutes for I2P tunnel establishment. +Subsessions attach quickly since tunnels are already established. Use generous +timeouts for initial PRIMARY session creation. + +Basic usage: + + sam, err := common.NewSAM("127.0.0.1:7656") + primary, err := primary.NewPrimarySession(sam, "master", keys, []string{"inbound.length=1"}) + defer primary.Close() + streamSub, err := primary.NewStreamSubsession("stream-1") + datagramSub, err := primary.NewDatagramSubsession("dgram-1") + +See also: Package stream, datagram, datagram2, datagram3, raw for individual +session types. ## Usage @@ -122,38 +152,20 @@ type PrimarySession struct { } ``` -PrimarySession provides master session capabilities for managing multiple -sub-sessions of different types (stream, datagram, raw) within a single I2P -session context. It enables complex applications with multiple communication -patterns while sharing the same I2P identity and tunnel infrastructure for -enhanced efficiency and anonymity. - -The primary session manages the lifecycle of all sub-sessions, ensures proper -cleanup cascading when the primary session is closed, and provides thread-safe -operations for creating, managing, and terminating sub-sessions across different -protocols. +PrimarySession manages multiple sub-sessions sharing the same I2P identity and +tunnels. #### func NewPrimarySession ```go func NewPrimarySession(sam *common.SAM, id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) ``` -NewPrimarySession creates a new primary session with the provided SAM -connection, session ID, cryptographic keys, and configuration options. The -primary session acts as a master container that can create and manage multiple -sub-sessions of different types while sharing the same I2P identity and tunnel -infrastructure. - -The session uses PRIMARY session type in the SAM protocol, which allows multiple -sub-sessions to be created using the same underlying I2P destination and keys. -This provides better resource efficiency and maintains consistent identity -across different communication patterns within the same application. - -Example usage: - - session, err := NewPrimarySession(sam, "my-primary", keys, []string{"inbound.length=2"}) - streamSub, err := session.NewStreamSubSession("stream-1", streamOptions) - datagramSub, err := session.NewDatagramSubSession("datagram-1", datagramOptions) +NewPrimarySession creates a new primary session for managing multiple +sub-sessions. It initializes the session with the provided SAM connection, +session ID, cryptographic keys, and configuration options. The primary session +allows creating multiple sub-sessions of different types (stream, datagram, raw) +while sharing the same I2P identity and tunnels. Example usage: session, err := +NewPrimarySession(sam, "my-primary", keys, []string{"inbound.length=2"}) #### func NewPrimarySessionWithSignature @@ -161,20 +173,10 @@ Example usage: func NewPrimarySessionWithSignature(sam *common.SAM, id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*PrimarySession, error) ``` NewPrimarySessionWithSignature creates a new primary session with the specified -signature type. This is a package-level function that provides direct access to -signature-aware session creation without requiring wrapper types. It delegates -to the common package for session creation while maintaining the same primary -session functionality and sub-session management capabilities. - -The signature type allows specifying custom cryptographic parameters for -enhanced security or compatibility with specific I2P network configurations. -Different signature types provide various security levels, performance -characteristics, and compatibility options. - -Example usage: - - session, err := NewPrimarySessionWithSignature(sam, "secure-primary", keys, options, "EdDSA_SHA512_Ed25519") - streamSub, err := session.NewStreamSubSession("stream-1", streamOptions) +signature type. This method allows specifying custom cryptographic parameters +for enhanced security or compatibility with specific I2P network configurations. +Example usage: session, err := NewPrimarySessionWithSignature(sam, +"secure-primary", keys, options, "EdDSA_SHA512_Ed25519") #### func (*PrimarySession) Addr @@ -354,17 +356,8 @@ NewStreamSubSession creates a new stream sub-session within this primary session. The sub-session shares the primary session's I2P identity and tunnel infrastructure while providing full StreamSession functionality for TCP-like reliable connections. Each sub-session must have a unique identifier within the -primary session scope. - -This implementation uses the SAMv3.3 SESSION ADD protocol to properly register -the subsession with the primary session's SAM connection, ensuring compliance -with the I2P SAM protocol specification for PRIMARY session management. - -Example usage: - - streamSub, err := primary.NewStreamSubSession("tcp-handler", []string{"FROM_PORT=8080"}) - listener, err := streamSub.Listen() - conn, err := streamSub.Dial("destination.b32.i2p") +primary session scope. Example usage: streamSub, err := +primary.NewStreamSubSession("tcp-handler", []string{"FROM_PORT=8080"}) #### func (*PrimarySession) NewUniqueStreamSubSession diff --git a/raw/DOC.md b/raw/DOC.md index 3a4fbf407..426163293 100644 --- a/raw/DOC.md +++ b/raw/DOC.md @@ -2,6 +2,36 @@ -- import "github.com/go-i2p/go-sam-go/raw" +Package raw provides encrypted but unauthenticated, non-repliable datagram +sessions for I2P. + +RAW sessions send encrypted datagrams without source authentication or reply +capability. Recipients cannot verify sender identity or send replies. Suitable +for one-way broadcast scenarios (logging, metrics, announcements) where reply +capability is not needed. + +Key features: + + - Encrypted transmission (confidentiality) + - No source authentication (spoofable) + - Non-repliable (recipient cannot reply) + - UDP-like messaging (unreliable, unordered) + - Maximum 31744 bytes per datagram (11 KB recommended) + +Session creation requires 2-5 minutes for I2P tunnel establishment. Use generous +timeouts and exponential backoff retry logic. + +Basic usage: + + sam, err := common.NewSAM("127.0.0.1:7656") + session, err := raw.NewRawSession(sam, "my-session", keys, []string{"inbound.length=1"}) + defer session.Close() + conn := session.PacketConn() + n, err := conn.WriteTo(data, destination) + +See also: Package datagram (authenticated, repliable), datagram2 (with replay +protection), datagram3 (hash-based sources), stream (TCP-like), primary +(multi-session management). ## Usage diff --git a/stream/DOC.md b/stream/DOC.md index 96f02a60d..68661fb35 100644 --- a/stream/DOC.md +++ b/stream/DOC.md @@ -2,6 +2,37 @@ -- import "github.com/go-i2p/go-sam-go/stream" +Package stream provides TCP-like reliable connections for I2P using SAMv3 STREAM +sessions. + +STREAM sessions provide ordered, reliable, bidirectional byte streams over I2P +tunnels, implementing standard net.Conn and net.Listener interfaces. Ideal for +applications requiring TCP-like semantics (HTTP servers, file transfers, +persistent connections). + +Key features: + + - Ordered, reliable delivery + - Bidirectional communication + - Standard net.Conn/net.Listener interfaces + - Automatic connection management + - Compatible with io.Reader/io.Writer + +Session creation requires 2-5 minutes for I2P tunnel establishment. Individual +connections (Accept/Dial) require additional time for circuit building. Use +generous timeouts and exponential backoff retry logic. + +Basic usage: + + sam, err := common.NewSAM("127.0.0.1:7656") + session, err := stream.NewStreamSession(sam, "my-session", keys, []string{"inbound.length=1"}) + defer session.Close() + listener, err := session.Listen() + conn, err := listener.Accept() + defer conn.Close() + +See also: Package datagram (UDP-like messaging), raw (unrepliable datagrams), +primary (multi-session management). ## Usage