High-level, easy-to-use listeners and clients for I2P and onion URL's from Go. Provides only the most widely-used functions in a basic way. It expects nothing from the users, an otherwise empty instance of the structs will listen and dial I2P Streaming and Tor TCP sessions successfully.
In all cases, it assumes that keys are "persistent" in that they are managed
maintained between usages of the same application in the same configuration.
This means that hidden services will maintain their identities, and that clients
will always have the same return addresses. If you don't want this behavior,
make sure to delete the "keystore" when your app closes or when your application
needs to cycle keys by calling the Garlic.DeleteKeys() or Onion.DeleteKeys()
function. For more information, check out the godoc.
STATUS: This project is maintained. I will respond to issues, pull requests, and feature requests within a few days.
Basic usage is designed to be very simple, import the package and instantiate a struct and you're ready to go.
For more extensive examples, see: EXAMPLE
When using it to manage an I2P session, set up an onramp.Garlic
struct.
package main
import (
"log"
"github.com/go-i2p/onramp"
)
func main() {
garlic, err := onramp.NewGarlic("my-app", onramp.SAM_ADDR, nil)
if err != nil {
log.Fatal(err)
}
defer garlic.Close()
listener, err := garlic.Listen()
if err != nil {
log.Fatal(err)
}
defer listener.Close()
}The Garlic struct supports an embedded SAM bridge feature. When NewGarlic()
is called and no external SAM bridge is listening on port 7656, onramp will
automatically attempt to create an embedded SAM bridge using the
github.com/go-i2p/go-sam-bridge/lib/embedding package. This allows applications
to operate without requiring an external I2P router in some scenarios.
Behavior:
- If port 7656 is already in use (external I2P router running), the embedded bridge is not created
- If port 7656 is available, an embedded bridge is created and used automatically
- The embedded bridge is managed by the
Garlic.Bridgefield
All Garlic instances created via NewGarlic() automatically register for cleanup
to prevent orphaned SAM sessions when your program exits unexpectedly (e.g., crashes,
signals, or failing to call Close()). This is enabled by default.
Cleanup mechanisms:
- Signal handlers catch SIGINT, SIGTERM, and SIGHUP to clean up sessions before exit
- Runtime finalizers provide best-effort cleanup during garbage collection
- Custom cleanup hooks can be registered via
RegisterCleanupHook(func())
If you need to disable automatic cleanup for a specific instance, use DisableAutoCleanup(garlic).
When using it to manage a Tor session, set up an onramp.Onion
struct.
package main
import (
"log"
"github.com/go-i2p/onramp"
)
func main() {
onion, err := onramp.NewOnion("my-app")
if err != nil {
log.Fatal(err)
}
defer onion.Close()
listener, err := onion.Listen()
if err != nil {
log.Fatal(err)
}
defer listener.Close()
}The hybrid2 sub-package provides an efficient datagram protocol for I2P
that balances authentication overhead with throughput. It uses a 1:499 ratio
of authenticated (datagram2) to low-overhead (datagram3) messages.
package main
import (
"log"
"time"
"github.com/go-i2p/onramp"
"github.com/go-i2p/onramp/hybrid2"
)
func main() {
// Create a hybrid session using the garlic integration helper
// This connects to the local SAM bridge and creates I2P tunnels
// onramp.SAM_ADDR defaults to "127.0.0.1:7656"
integration, err := hybrid2.NewGarlicIntegration(onramp.SAM_ADDR, "my-hybrid")
if err != nil {
log.Fatal(err)
}
defer integration.Close()
// Get a standard net.PacketConn for UDP-like operations
conn := integration.PacketConn()
// Set read deadline for timeout support
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
// Use like any other PacketConn
buf := make([]byte, 4096)
n, addr, err := conn.ReadFrom(buf)
if err != nil {
log.Printf("Read error: %v", err)
return
}
log.Printf("Received %d bytes from %s", n, addr)
}The hybrid2 protocol automatically:
- Sends authenticated datagram2 messages every 500th send (1:499 ratio)
- Sends low-overhead datagram3 messages for all other sends
- Uses time-based refresh to prevent hash mapping expiry on idle connections
- Maintains sender hash mappings for efficient message routing
- Provides deadline support via
SetReadDeadline()andSetWriteDeadline()
The hybrid1 sub-package provides i2pd-compatible datagram protocol support.
Use this mode when interoperating with i2pd-based applications.
package main
import (
"log"
"time"
"github.com/go-i2p/onramp"
)
func main() {
// Create a Garlic instance
garlic, err := onramp.NewGarlic("my-hybrid1-app", onramp.SAM_ADDR, nil)
if err != nil {
log.Fatal(err)
}
defer garlic.Close()
// Option 1: Explicit Hybrid1 method (i2pd-compatible)
conn, err := garlic.ListenPacketHybrid1()
if err != nil {
log.Fatal(err)
}
// Option 2: Using mode constant
// conn, err := garlic.ListenPacketWithMode(onramp.HYBRID_MODE_V1)
// Set read deadline for timeout support
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
// Use like any other PacketConn
buf := make([]byte, 64*1024) // i2pd max MTU
n, addr, err := conn.ReadFrom(buf)
if err != nil {
log.Printf("Read error: %v", err)
return
}
log.Printf("Received %d bytes from %s", n, addr)
}Hybrid Mode Selection:
| Mode | Constant | Method | Use Case |
|---|---|---|---|
| Hybrid2 (Default) | HYBRID_MODE_V2 |
ListenPacket() or ListenPacketHybrid2() |
Go-to-Go communication, optimal performance |
| Hybrid1 (i2pd) | HYBRID_MODE_V1 |
ListenPacketHybrid1() |
i2pd interoperability |
When to use Hybrid1:
- Communicating with i2pd-based applications
- Interoperability with non-Go I2P implementations
- Matching i2pd's datagram frame structure is required
When to use Hybrid2 (Recommended):
- Go-to-Go communication
- Most flexibility
- Native SAM3 datagram support
Logging is provided by the github.com/go-i2p/logger package, which offers structured logging with advanced debugging features.
By default, logging is disabled for zero-impact performance. Enable logging using environment variables:
Log Levels:
- Debug - Detailed information for development and troubleshooting
export DEBUG_I2P=debug- Warn - Important warnings about potential issues
export DEBUG_I2P=warn- Error - Only serious errors and failures
export DEBUG_I2P=errorIf DEBUG_I2P is set to an unrecognized value, it defaults to "debug" level.
Enable fast-fail mode to catch warnings and errors during testing. When enabled, any warning or error will cause the application to exit immediately:
export WARNFAIL_I2P=trueThis is particularly useful in CI/CD pipelines and during development to ensure no issues are overlooked.
# Run with debug logging enabled
DEBUG_I2P=debug go run your-app.go
# Run with fast-fail mode for testing
DEBUG_I2P=debug WARNFAIL_I2P=true go test ./...The logger provides structured logging with contextual fields, making logs searchable and analyzable:
- WithField() / WithFields() - Add contextual metadata to log entries
- WithError() - Include error context with stack traces
- Zero overhead when logging is disabled
- Proper log level filtering based on environment configuration
For more details, see the logger package documentation.
See CONTRIBUTING.md for more information.
This project is licensed under the MIT license, see LICENSE for more information.