mirror of
https://github.com/go-i2p/go-sam-go.git
synced 2025-12-01 09:54:58 -05:00
fixup docs
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
221
datagram2/DOC.md
221
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
|
||||
|
||||
|
||||
438
datagram3/DOC.md
438
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
|
||||
|
||||
1210
datagram3/DOC.md.backup
Normal file
1210
datagram3/DOC.md.backup
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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)
|
||||
// }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
30
raw/DOC.md
30
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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user