A pure-Go implementation of SAMv3.3 (Simple Anonymous Messaging) for I2P, focused on maintainability and clean architecture. This project is forked from github.com/go-i2p/sam3 with reorganized code structure.
Stable & Production-Ready:
- β Stream - TCP-like reliable connections (fully tested)
- β Datagram - Legacy authenticated repliable datagrams (fully tested, SAMv3 UDP forwarding required)
- β Raw - Encrypted unauthenticated datagrams (fully tested, SAMv3 UDP forwarding required)
- β Primary Sessions - Multi-session management (fully implemented with comprehensive lifecycle management)
Implemented & Documented (Awaiting I2P Router Support):
β οΈ Datagram2 - Authenticated repliable datagrams with replay protection (spec finalized early 2025, no router implementations yet, available in dev builds)β οΈ Datagram3 - Unauthenticated repliable datagrams with hash-based sources (spec finalized early 2025, no router implementations yet, available in dev builds, requires direct package import)
Note: DATAGRAM2 and DATAGRAM3 are fully implemented in this library but require I2P router support (Java I2P or i2pd) to function. Check your router's release notes for SAMv3 DATAGRAM2/3 support.
go get github.com/go-i2p/go-sam-gogithub.com/go-i2p/i2pkeysv0.33.92 - I2P cryptographic key managementgithub.com/go-i2p/crypto- I2P-specific cryptographic operationsgithub.com/sirupsen/logrusv1.9.3 - Structured logginggithub.com/samber/oopsv1.19.0 - Enhanced error handling
package main
import (
"github.com/go-i2p/go-sam-go"
)
func main() {
// Create SAM client
client, err := sam3.NewSAM("127.0.0.1:7656")
if err != nil {
panic(err)
}
defer client.Close()
// Generate keys (optionally specify signature type)
keys, err := client.NewKeys() // Uses default EdDSA_SHA512_Ed25519
// Or: keys, err := client.NewKeys(sam3.Sig_ECDSA_SHA256_P256)
if err != nil {
panic(err)
}
// Create streaming session
session, err := client.NewStreamSession("myTunnel", keys, sam3.Options_Default)
if err != nil {
panic(err)
}
}The root package provides a high-level wrapper API:
client, err := sam3.NewSAM("127.0.0.1:7656")Available session types:
NewStreamSession()- For reliable TCP-like connectionsNewDatagramSession()- For UDP-like messagingNewRawSession()- For encrypted but unauthenticated datagramsNewPrimarySession()- For creating multiple sub-sessions
Core session management functionality:
primary, err := sam.NewPrimarySession("mainSession", keys, options)
sub1, err := primary.NewStreamSubSession("web")
sub2, err := primary.NewDatagramSubSession("chat") TCP-like reliable connections:
listener, err := session.Listen()
conn, err := listener.Accept() // Reuse listener for multiple connections
// or for single connection (creates new listener each time - less efficient)
conn, err := session.Accept()
// or dial outbound
conn, err := session.DialI2P(remote)Performance Note: For servers accepting multiple connections, use Listen() once and call Accept() on the listener. The session.Accept() method creates and destroys a new listener for each call, which is inefficient for high-traffic scenarios.
UDP-like message delivery with SAMv3 UDP forwarding:
// SAMv3 UDP forwarding is mandatory - creates local UDP listener automatically
// Port parameter: 0 = OS-assigned port for local UDP listener (not SAM bridge port 7655)
dgram, err := sam.NewDatagramSession("udp", keys, options, 0)
n, err := dgram.WriteTo(data, dest)Important: All datagram sessions in this library use SAMv3 UDP forwarding. A local UDP listener is automatically created for receiving forwarded datagrams from the I2P router. The port parameter specifies the local listener port (0 for auto-assignment), NOT the SAM bridge's UDP port (which defaults to 7655).
Low-level datagram access with SAMv3 UDP forwarding:
// SAMv3 UDP forwarding is mandatory - creates local UDP listener automatically
// Port parameter: 0 = OS-assigned port for local UDP listener (not SAM bridge port 7655)
raw, err := sam.NewRawSession("raw", keys, options, 0)
n, err := raw.WriteTo(data, dest)Important: All raw sessions in this library use SAMv3 UDP forwarding. A local UDP listener is automatically created for receiving forwarded datagrams from the I2P router. The port parameter specifies the local listener port (0 for auto-assignment), NOT the SAM bridge's UDP port.
Authenticated repliable datagrams with replay protection:
// DATAGRAM2 - Authenticated with replay protection (requires router support)
// Specification finalized early 2025, awaiting I2P router implementation
import "github.com/go-i2p/go-sam-go/datagram2"
session, err := datagram2.NewDatagram2Session(sam, "session-id", keys, options)
if err != nil {
// Handle error - may fail if router doesn't support DATAGRAM2 yet
log.Fatal(err)
}
defer session.Close()
// Send authenticated datagram
err = session.SendDatagram(data, destination)
// Receive with full authentication
conn := session.PacketConn()
n, addr, err := conn.ReadFrom(buffer)Security: Provides cryptographic authentication and replay protection. Recommended for new applications requiring source verification.
Status: Implementation complete. Waiting for I2P router support (Java I2P 0.9.x+ or i2pd 2.x+).
Note: Datagram3 is not integrated into the root sam3 package API. You must import and use the datagram3 package directly.
// DATAGRAM3 - UNAUTHENTICATED sources (requires router support + app-layer auth)
// Sources are 32-byte hashes that can be spoofed - implement your own authentication!
import "github.com/go-i2p/go-sam-go/datagram3"
import "github.com/go-i2p/go-sam-go/common"
// Create SAM connection using common package
samCommon, err := common.NewSAM("127.0.0.1:7656")
if err != nil {
log.Fatal(err)
}
session, err := datagram3.NewDatagram3Session(samCommon, "session-id", keys, options)
if err != nil {
log.Fatal(err)
}
defer session.Close()
// Receive datagram with UNAUTHENTICATED source hash
reader := session.NewReader()
datagram, err := reader.ReceiveDatagram()
// β οΈ Source hash is NOT authenticated - verify at application layer!
// Resolve hash to destination for reply (requires NAMING LOOKUP)
err = datagram.ResolveSource(session)
// Send reply
writer := session.NewWriter()
err = writer.SendDatagram(replyData, datagram.Source)Status: Implementation complete with documentation. Waiting for I2P router support.
Built-in configuration profiles:
sam3.Options_Default // Balanced defaults
sam3.Options_Small // Minimal resources
sam3.Options_Medium // Enhanced reliability
sam3.Options_Large // High throughput
sam3.Options_Humongous // Maximum performanceThis library uses github.com/go-i2p/logger for structured logging with environment-based configuration:
Control logging output via the DEBUG_I2P environment variable:
export DEBUG_I2P=debug # Verbose debugging (tunnel state, operations, timing)
export DEBUG_I2P=warn # Warnings only (deprecations, recoverable issues)
export DEBUG_I2P=error # Errors only (failed operations, connectivity issues)
# Unset = No logging (zero performance impact)Enable strict mode where warnings and errors become fatal:
export WARNFAIL_I2P=true # Convert warnings/errors to fatal for testing
go test ./... # Tests fail fast on any warning/errorUse Case: Catch potential issues during development that might be warnings in production.
The logger provides structured, searchable output with contextual fields:
time="2025-01-20T15:04:05Z" level=debug msg="SAM session created" session_id="mytunnel" signature_type="EdDSA_SHA512_Ed25519" tunnel_in_length=3 tunnel_out_length=3
time="2025-01-20T15:04:10Z" level=info msg="Connection established" remote_dest="abc123...def.b32.i2p" connection_time="2.3s"
time="2025-01-20T15:04:15Z" level=error msg="Failed to send datagram" error="connection timeout" session_id="udp-tunnel" retry_count=3
- Production: Leave
DEBUG_I2Punset for zero logging overhead - Troubleshooting: Set
DEBUG_I2P=debugto diagnose I2P timing and connectivity issues - Testing: Use
WARNFAIL_I2P=trueto enforce clean test runs without warnings - CI/CD: Combine both for maximum issue detection:
DEBUG_I2P=debug WARNFAIL_I2P=true go test ./...
- Go 1.24.2 or later (toolchain go1.24.4)
- Running I2P router with SAM bridge enabled (default port: 7656)
- For DATAGRAM2/DATAGRAM3: I2P router with SAMv3 DATAGRAM2/3 support (check router release notes)
# Format code
make fmt
# Run tests (short mode, no I2P required)
go test -short ./...
# Run full integration tests (requires running I2P router)
# Note: I2P tests can take 30-150 seconds due to tunnel establishment
go test ./...
# Run with race detection
go test -race -short ./...Each sub-package has comprehensive documentation:
- stream/ - TCP-like reliable connections
- datagram/ - Legacy authenticated datagrams
- datagram2/ - DATAGRAM2 authenticated datagrams with replay protection
- datagram3/ - DATAGRAM3 unauthenticated datagrams
- raw/ - Encrypted unauthenticated datagrams
- primary/ - PRIMARY session management
I2P operations have significant latency due to tunnel-based architecture:
- Session creation: 2-5 minutes on initial connection
- Message delivery: Variable (network-dependent)
- Best practice: Use generous timeouts (5+ minutes) and exponential backoff retry logic
All tests accommodate I2P timing requirements.
MIT License
Based on the original github.com/go-i2p/sam3 library.