datagrams

package module
v0.1.59999 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2026 License: MIT Imports: 12 Imported by: 2

README

go-datagrams

A Go library for sending and receiving I2P datagrams over I2CP.

Overview

go-datagrams provides a stateless, port-based messaging layer for I2P applications. It wraps I2CP datagram sessions, implementing Go's standard net.PacketConn interface for compatibility with existing networking code.

Key Features:

  • Stateless datagram send/receive over I2CP
  • Port-based message routing on a single I2P session
  • Standard net.PacketConn interface implementation
  • Support for Raw (protocol 18), Datagram1 (protocol 17), Datagram2 (protocol 19), and Datagram3 (protocol 20)
  • Thread-safe concurrent operations

Installation

go get github.com/go-i2p/go-datagrams

Requirements:

  • Go 1.21+
  • Access to an I2P router with I2CP enabled
  • github.com/go-i2p/go-i2cp for I2CP transport

Quick Start

package main

import (
    "fmt"
    "github.com/go-i2p/go-datagrams"
    "github.com/go-i2p/go-i2cp"
)

func main() {
    // Initialize I2CP session (see go-i2cp documentation)
    session, err := i2cp.NewSession(/* config */)
    if err != nil {
        panic(err)
    }
    defer session.Close()

    // Create datagram connection
    conn, err := datagrams.NewDatagramConn(session, 8080)
    if err != nil {
        panic(err)
    }
    defer conn.Close()

    // Send a datagram
    payload := []byte("Hello, I2P!")
    destAddr := &datagrams.I2PAddr{
        Destination: "example.i2p.destination.string",
        Port:        8081,
    }
    _, err = conn.WriteTo(payload, destAddr)
    if err != nil {
        panic(err)
    }

    // Receive a datagram
    buf := make([]byte, 8192)
    n, addr, err := conn.ReadFrom(buf)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Received %d bytes from %s: %s\n", n, addr, buf[:n])
}

Architecture

Datagram Types

I2P supports multiple datagram types with different trade-offs:

Type Protocol Authenticated? Repliable? Overhead Use Case
Raw 18 No No 0 bytes Low-latency, high-throughput
Datagram1 17 Yes Yes ~455 bytes Legacy authenticated messaging
Datagram2 19 Yes Yes ~457+ bytes Modern authenticated with replay prevention
Datagram3 20 No Yes ~34 bytes Lightweight repliable

Recommendation: Use Raw datagrams for performance, Datagram3 for repliability with minimal overhead, or Datagram2 for authentication with replay protection.

Datagram3 Sender Identification

Datagram3 provides repliability with minimal overhead, but the protocol only includes the sender's 32-byte hash, not the full destination. This means ReceiveFrom() returns an empty destination for Datagram3 messages.

To identify the sender when using Datagram3:

// Method 1: Check protocol and use appropriate receive method
if conn.HasSenderDestination() {
    // For Raw, Datagram1, Datagram2 - full destination available
    payload, from, port, err := conn.ReceiveFrom()
    // from.Base64() contains the full destination
} else {
    // For Datagram3 - only hash available
    payload, addr, err := conn.ReceiveFromWithAddr()
    if addr.IsHashOnly() {
        // Look up full destination from cache or network database
        hash := addr.DestinationHash // [32]byte hash
        fullDest := myDestinationCache.Lookup(hash)
    }
}

// Method 2: Always use ReceiveFromWithAddr() for flexibility
payload, addr, err := conn.ReceiveFromWithAddr()
if addr.HasFullDestination() {
    // Full destination available
    dest := addr.Destination
} else if addr.HasDestinationHash() {
    // Only hash available (Datagram3)
    hash := addr.DestinationHash
}
Size Limits
  • Maximum I2CP datagram: ~64KB (nominal)
  • Recommended maximum: 8-10KB for reliable delivery

I2NP messages are fragmented into 1KB tunnel messages. Larger datagrams have exponentially higher drop probability due to fragmentation.

Port-Based Routing

Multiple application protocols can share a single I2CP session by registering handlers for specific ports:

conn.RegisterPort(8080, func(payload []byte, from *i2cp.Destination) {
    // Handle incoming messages on port 8080
    fmt.Printf("Received %d bytes from %s\n", len(payload), from)
})

Design Principles

Following the patterns from copilot-instructions.md:

  • Stateless operation: No connection state tracking (matches I2CP datagram semantics)
  • Standard interfaces: Implements net.PacketConn and net.Addr
  • Thread safety: All operations are safe for concurrent use
  • No IP assumptions: Works with I2P destinations, not IP addresses

Limitations

Cryptographic Requirements:

  • Ed25519 only: This library exclusively supports Ed25519 destinations and signatures. Legacy DSA_SHA1, ElGamal, and ECDSA signature types are not supported. This aligns with go-i2cp's Ed25519-only approach and I2P's direction toward modern cryptography.

I2P Datagram Characteristics:

  • Unreliable delivery: No automatic retransmission or ordering guarantees
  • No connection state: Each datagram is independent
  • Size-dependent reliability: Larger datagrams (>10KB) have significantly higher drop rates
  • End-to-end unreliability: Messages may be dropped at any hop despite reliable hop-to-hop transport

Applications requiring reliability must implement it at the application layer (ACKs, sequence numbers, retransmission).

Documentation

Containerized Router Test

This repository includes a deterministic multi-container test harness that runs the full Go test suite in one container against a dedicated Java I2P router container.

  • Router base image: geti2p/i2p:latest (same image is also published as ghcr.io/i2p/i2p.i2p)
  • No host ports are published by default
  • Router service: i2p-router
  • Test service: router-tests
  • Tests run after the test container confirms I2CP is reachable on i2p-router:7654
Run With Helper Script
./scripts/container/test-with-router.sh
Run With Docker Compose
docker compose -f docker-compose.router-tests.yml build
docker compose -f docker-compose.router-tests.yml up -d i2p-router
docker compose -f docker-compose.router-tests.yml run --rm router-tests
docker compose -f docker-compose.router-tests.yml down

License

See LICENSE file for details.

Documentation

Overview

Package datagrams provides stateless, port-based messaging over I2P.

This library wraps I2CP datagram sessions, implementing Go's standard net.PacketConn interface for compatibility with existing networking code.

I2P Datagram Types

I2P supports four datagram types with different trade-offs:

  • Raw (Protocol 18): Non-repliable, no authentication, 0 bytes overhead Use for: Performance-critical applications with trusted peers

  • Datagram1 (Protocol 17): Repliable, authenticated, ~455 bytes overhead (Ed25519) Use for: Legacy compatibility with older I2P applications

  • Datagram2 (Protocol 19): Repliable, authenticated with replay prevention, ~457+ bytes overhead (Ed25519) Use for: Modern authenticated messaging requiring replay attack prevention

  • Datagram3 (Protocol 20): Repliable, no authentication, ~34 bytes overhead Use for: Lightweight repliable messages with minimal overhead

Size Limits

The practical size limit for reliable delivery is 8-10KB. While I2CP supports up to ~64KB datagrams, larger messages are fragmented into 1KB tunnel messages with exponentially increasing drop probability.

Stateless Design

Unlike TCP connections, I2P datagrams are stateless. Each message is independent with no automatic retransmission, ordering, or connection state. Applications requiring reliability must implement it at the application layer using sequence numbers, ACKs, and retransmission logic.

Basic Usage

Create a datagram connection and send/receive messages:

session, _ := i2cp.NewSession(config)
conn, _ := datagrams.NewDatagramConn(session, 8080)
defer conn.Close()

// Send a message
addr := &datagrams.I2PAddr{
    Destination: "example.i2p.destination.string",
    Port:        8081,
}
conn.WriteTo([]byte("Hello"), addr)

// Receive a message
buf := make([]byte, 8192)
n, addr, _ := conn.ReadFrom(buf)
fmt.Printf("Received: %s\n", buf[:n])

Port-Based Routing

This library provides port-based message routing to allow multiple services to share a single I2CP session. Each DatagramConn is bound to a local port, enabling:

  • Multiple application protocols per I2CP session
  • Port-based message filtering and dispatch
  • Familiar networking semantics for Go developers

Applications that don't need port-based routing can use Raw datagrams with minimal overhead.

Index

Constants

View Source
const (
	// ProtocolStreaming (6) is reserved for I2P streaming protocol.
	// This protocol number MUST NOT be used for datagrams.
	ProtocolStreaming = i2cp.ProtoStreaming

	// ProtocolRaw (18) is for non-repliable, non-authenticated datagrams.
	// Zero overhead, highest performance. Use for trusted peers or when
	// authentication is handled at the application layer.
	ProtocolRaw = i2cp.ProtoDatagramRaw

	// ProtocolDatagram1 (17) is for repliable, authenticated datagrams (legacy).
	// ~455 bytes overhead (Ed25519). Use for compatibility with older I2P applications.
	// WARNING: Does NOT support offline signatures (LS2 offline keys).
	ProtocolDatagram1 = i2cp.ProtoDatagram

	// ProtocolDatagram2 (19) is for repliable, authenticated datagrams with replay prevention.
	// ~457+ bytes overhead (Ed25519). Use for modern authenticated messaging requiring
	// protection against replay attacks. Supports offline signatures.
	ProtocolDatagram2 = i2cp.ProtoDatagram2

	// ProtocolDatagram3 (20) is for repliable, non-authenticated datagrams.
	// ~34 bytes overhead. Use when repliability is needed with minimal overhead
	// and authentication is not required.
	ProtocolDatagram3 = i2cp.ProtoDatagram3
)

Protocol numbers for I2P datagram types. These are aliases to the constants defined in go-i2cp for convenience. See SPEC.md for detailed format specifications.

View Source
const (
	// MaxI2NPSize is the nominal maximum size for I2NP messages including datagrams.
	// Per I2P specification, this is 64KB but actual limits may be slightly less
	// due to I2CP gzip header (~10 bytes) and garlic message overhead.
	MaxI2NPSize = 64 * 1024 // 64 KB

	// RecommendedMaxSize is the recommended maximum payload size for reliable delivery.
	// I2NP messages fragment into 1KB tunnel messages, and drop probability increases
	// exponentially with size. This limit ensures good reliability.
	RecommendedMaxSize = 10 * 1024 // 10 KB

	// OptimalMaxSize is the optimal payload size for best reliability.
	// Keeping messages small reduces fragmentation and improves delivery probability.
	OptimalMaxSize = 4 * 1024 // 4 KB

	// Ed25519SignatureLength is the fixed signature length for Ed25519 (64 bytes).
	// go-i2cp exclusively uses Ed25519, so this is constant.
	Ed25519SignatureLength = 64

	// Ed25519DestinationSize is the wire format size for an Ed25519 destination.
	// Wire format: pubKey(256) + signingPubKey(128) + certificate(7) = 391 bytes
	//
	// Note: The I2P spec says "387+" for serialized format because DSA-SHA1 destinations
	// fit in 387 bytes. Ed25519 with KEY certificates requires 391 bytes due to the
	// certificate structure. This value is constant for go-i2cp's Ed25519-only destinations.
	Ed25519DestinationSize = 391

	// MinDatagram1Overhead is the minimum envelope overhead for Datagram1 with Ed25519.
	// destination(391) + signature(64) = 455 bytes
	//
	// This is the minimum because the destination size can vary with certificate type.
	// For go-i2cp's Ed25519-only destinations, this is also the exact overhead.
	MinDatagram1Overhead = Ed25519DestinationSize + Ed25519SignatureLength // 455

	// MinDatagram2Overhead is the minimum envelope overhead for Datagram2 with Ed25519.
	// destination(391) + flags(2) + signature(64) = 457 bytes (without options or offline sig)
	//
	// Actual overhead may be larger when:
	// - Options field is present (adds 2+ bytes for mapping)
	// - Offline signature is present (adds ~102 bytes for Ed25519)
	MinDatagram2Overhead = Ed25519DestinationSize + 2 + Ed25519SignatureLength // 457

	// MinDatagram3Overhead is the minimum envelope overhead for Datagram3.
	// fromhash(32) + flags(2) = 34 bytes (without options)
	//
	// Actual overhead may be larger when:
	// - Options field is present (adds 2+ bytes for mapping)
	MinDatagram3Overhead = 32 + 2 // 34
)

Size constants for I2P datagrams.

Variables

This section is empty.

Functions

This section is empty.

Types

type DatagramConn

type DatagramConn struct {
	// contains filtered or unexported fields
}

DatagramConn represents a stateless I2P datagram connection that wraps an I2CP session. It implements net.PacketConn for compatibility with standard Go networking code.

DatagramConn supports port-based message routing, allowing multiple application protocols to share a single I2CP session. Each connection is bound to a local port and can send/receive datagrams to/from remote I2P destinations and ports.

Design decisions:

  • Stateless: No connection tracking between sends, matching I2CP semantics
  • Thread-safe: All methods safe for concurrent use via internal mutex
  • Protocol-aware: Supports Raw (18), Datagram1 (17), Datagram2 (19), Datagram3 (20)
  • Port-based: Port multiplexing on a single I2CP session

Port-based routing: I2CP datagrams are raw byte payloads without built-in addressing. This library provides port-based routing to allow multiple services to share a single I2CP session. Applications that don't need ports can use Raw datagrams with zero overhead.

func NewDatagramConn

func NewDatagramConn(session I2CPSession, localPort uint16) (*DatagramConn, error)

NewDatagramConn creates a new DatagramConn bound to the specified local port. The connection uses ProtocolRaw (18) by default for zero overhead.

The session parameter must be a valid, open I2CP session. The caller is responsible for session lifecycle management. DatagramConn will not close the session.

The localPort parameter specifies the UDP port number for this connection. Port 0 is allowed but not recommended - it won't filter incoming packets by port.

Design rationale:

  • Default to Raw protocol for performance and simplicity
  • Let caller manage session lifecycle (follows Go convention of explicit ownership)
  • Require explicit port binding (no auto-assignment) for clarity

Example:

session, _ := i2cp.NewSession(client, callbacks)
conn, err := datagrams.NewDatagramConn(session, 8080)
if err != nil {
    log.Fatal(err)
}
defer conn.Close()

func NewDatagramConnWithProtocol

func NewDatagramConnWithProtocol(session I2CPSession, localPort uint16, protocol uint8) (*DatagramConn, error)

NewDatagramConnWithProtocol creates a new DatagramConn with a specific protocol type.

The protocol parameter must be one of:

  • ProtocolRaw (18): Non-repliable, no authentication, 0 bytes overhead
  • ProtocolDatagram1 (17): Repliable, authenticated, ~455 bytes overhead (Ed25519)
  • ProtocolDatagram2 (19): Repliable, authenticated with replay prevention, ~457+ bytes overhead (Ed25519)
  • ProtocolDatagram3 (20): Repliable, non-authenticated, ~34 bytes overhead

Protocol selection guide:

  • Use Raw for high-performance, trusted communication
  • Use Datagram3 for repliability with minimal overhead
  • Use Datagram2 for authentication with replay attack prevention
  • Use Datagram1 only for legacy compatibility

Returns an error if:

  • session is nil
  • session is closed
  • session destination cannot be retrieved
  • protocol is ProtocolStreaming (6) which is reserved for streaming

func (*DatagramConn) Close

func (d *DatagramConn) Close() error

Close closes the datagram connection and releases associated resources. It cancels any background operations (like the receive loop) and marks the connection as closed.

Close does NOT close the underlying I2CP session - the caller must manage session lifecycle independently. This design allows multiple DatagramConns to share a single session (though currently only one is typical).

After Close() is called, all subsequent operations on the connection will return net.ErrClosed. Close() is idempotent - calling it multiple times is safe and only the first call has effect.

Close() is safe to call concurrently with other operations.

func (*DatagramConn) HasSenderDestination

func (d *DatagramConn) HasSenderDestination() bool

HasSenderDestination returns true if ReceiveFrom() will return a usable sender destination.

For Datagram3 (protocol 20), this returns false because the protocol only includes the sender's 32-byte hash, not the full destination. In this case, applications should use ReceiveFromWithAddr() instead to get the sender's hash via I2PAddr.DestinationHash.

For all other protocols (Raw, Datagram1, Datagram2), this returns true.

Example usage:

if conn.HasSenderDestination() {
    payload, from, port, err := conn.ReceiveFrom()
    // from is a valid destination
} else {
    payload, addr, err := conn.ReceiveFromWithAddr()
    // Use addr.DestinationHash to identify the sender
}

func (*DatagramConn) IsClosed

func (d *DatagramConn) IsClosed() bool

IsClosed returns true if Close() has been called on this connection. This is a convenience method for checking connection state.

func (*DatagramConn) LocalAddr

func (d *DatagramConn) LocalAddr() net.Addr

LocalAddr returns the local network address. For I2P, this is an I2PAddr containing our destination and port.

This implements the net.PacketConn interface.

func (*DatagramConn) MaxPayloadSize

func (d *DatagramConn) MaxPayloadSize() int

MaxPayloadSize returns the maximum payload size for this connection's protocol type. This accounts for protocol-specific overhead in the I2NP message.

Protocol-specific limits (for Ed25519 destinations):

  • Raw (18): 64KB - no envelope overhead
  • Datagram3 (20): 64KB - 34 bytes (fromhash + flags)
  • Datagram1 (17): 64KB - 455 bytes (dest(391) + signature(64))
  • Datagram2 (19): 64KB - 457 bytes (dest(391) + flags(2) + signature(64))

Note: The I2P spec uses "387+" for destination size (serialized format) and "40+" for signature (DSA_SHA1). This library uses Ed25519 exclusively, so:

  • Destination wire format: 391 bytes (pubKey(256) + signingPubKey(128) + cert(7))
  • Signature: 64 bytes (Ed25519)

For reliable delivery, limit payloads to RecommendedMaxSize (~10KB) or less due to I2NP fragmentation into 1KB tunnel messages. Drop probability increases exponentially with message size.

func (*DatagramConn) Protocol

func (d *DatagramConn) Protocol() uint8

Protocol returns the I2P datagram protocol type (17, 18, 19, or 20). This allows inspecting which protocol the connection is using.

func (*DatagramConn) ReadFrom

func (d *DatagramConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)

ReadFrom reads a packet from the connection, copying the payload into p. It returns the number of bytes copied into p and the return address that sent the packet.

This implements the net.PacketConn interface by wrapping ReceiveFrom().

ReadFrom can be made to time out and return an error after a fixed time limit; see SetDeadline and SetReadDeadline.

Design notes:

  • Wraps ReceiveFrom() to provide standard net.PacketConn semantics
  • Converts I2P destination + port to I2PAddr for interface compliance
  • Copies payload into provided buffer (standard Go networking pattern)
  • Returns short read if buffer is too small (no error, matches UDP behavior)

Returns an error if:

  • The connection is closed (net.ErrClosed)
  • The read deadline has expired
  • The underlying receive operation fails

func (*DatagramConn) ReceiveFrom

func (d *DatagramConn) ReceiveFrom() ([]byte, *i2cp.Destination, uint16, error)

ReceiveFrom receives a datagram and returns the payload, sender destination, and source port.

This method blocks until a datagram is received or an error occurs. It respects the read deadline set by SetReadDeadline() or SetDeadline().

The method parses protocol-specific envelopes:

  • Raw (18): Payload is returned directly (no envelope)
  • Datagram1 (17): Extracts from destination + signature, verifies signature, then payload
  • Datagram2 (19): Extracts from + flags + signature, verifies with replay prevention, then payload
  • Datagram3 (20): Extracts fromhash(32) + flags(2), then payload (see WARNING below)

WARNING: Datagram3 Sender Identification

For Datagram3 (protocol 20), the returned sender destination is EMPTY because the protocol only includes a 32-byte hash of the sender's destination, not the full destination itself. If you need to identify the sender when using Datagram3:

  1. Use DatagramConn.ReceiveFromWithAddr instead - it returns an I2PAddr containing the sender's hash in DestinationHash field
  2. Use DatagramConn.HasSenderDestination to check if this method will return a valid destination before calling

To reply to a Datagram3 sender, applications must look up the full destination from a cache or the I2P network database using the hash.

Returns an error if:

  • The connection is closed
  • The read deadline has expired
  • The envelope is malformed
  • Signature verification fails (Datagram1/2)

func (*DatagramConn) ReceiveFromWithAddr

func (d *DatagramConn) ReceiveFromWithAddr() ([]byte, *I2PAddr, error)

ReceiveFromWithAddr receives a datagram and returns the payload, sender address, and an error. This method provides more complete sender information than ReceiveFrom, including the destination hash for Datagram3 protocol messages.

For Datagram3 (protocol 20), only the sender's destination hash is available in the protocol, not the full destination. Use addr.IsHashOnly() to check this condition. To reply to a Datagram3 sender, applications need to look up the full destination from a cache or the network database using addr.DestinationHash.

This method blocks until a datagram is received or an error occurs. It respects the read deadline set by SetReadDeadline() or SetDeadline().

Returns an error if:

  • The connection is closed
  • The read deadline has expired
  • The envelope is malformed

func (*DatagramConn) ReceiveFromWithOptions

func (d *DatagramConn) ReceiveFromWithOptions() (*ReceiveResult, error)

ReceiveFromWithOptions receives a datagram and returns a ReceiveResult containing the payload, sender information, and parsed options.

This method provides complete access to all datagram fields, including the options field which is supported by Datagram2 (protocol 19) and Datagram3 (protocol 20).

For protocols that don't support options (Raw, Datagram1), the Options field in the result will be nil. For protocols that support options but didn't include any in the received datagram, the Options field will also be nil.

For Datagram3 (protocol 20), the From field in the result will be nil because only the sender's hash is available. Use FromHash or FromAddr.DestinationHash instead.

This method blocks until a datagram is received or an error occurs. It respects the read deadline set by SetReadDeadline() or SetDeadline().

Example:

result, err := conn.ReceiveFromWithOptions()
if err != nil {
    log.Fatal(err)
}
if result.Options != nil {
    version := result.Options.Get("version")
    log.Printf("Received from peer with version: %s", version)
}

Returns an error if:

  • The connection is closed
  • The read deadline has expired
  • The envelope is malformed
  • Signature verification fails (Datagram1/2)

func (*DatagramConn) RegisterPort

func (d *DatagramConn) RegisterPort(port uint16, handler func([]byte, *i2cp.Destination)) error

RegisterPort registers a handler function for datagrams received on a specific port.

When a datagram arrives with the given destination port number, the handler will be called with the payload and sender's destination. This enables port-based multiplexing of datagram streams over a single I2CP session.

The handler is dispatched in a new goroutine to avoid blocking the receive loop. Handlers should be lightweight and avoid long-running operations.

Parameters:

  • port: The destination port number to listen on (0-65535)
  • handler: Function called when a datagram arrives on this port
  • payload: The datagram payload bytes (after envelope parsing)
  • from: The I2P destination of the sender (may be nil for Raw datagrams)

Returns an error if:

  • The connection is closed
  • The port is already registered
  • The handler function is nil

Example:

conn.RegisterPort(8080, func(payload []byte, from *i2cp.Destination) {
    fmt.Printf("Received %d bytes from %s\n", len(payload), from)
})

func (*DatagramConn) SendTo

func (d *DatagramConn) SendTo(payload []byte, destinationB64 string, port uint16) error

SendTo sends a datagram to the specified I2P destination and port.

The payload is wrapped in a protocol-specific envelope based on the connection's protocol type:

  • Raw (18): payload sent directly, no envelope
  • Datagram3 (20): fromhash + flags + payload
  • Datagram1 (17): from destination + signature + payload
  • Datagram2 (19): from destination + flags + options + signature + payload

The destination parameter should be a valid I2P destination string (base64 encoded). The port parameter is used for application-level routing within I2P.

Returns an error if:

  • The connection is closed
  • The payload exceeds MaxPayloadSize()
  • The write deadline has expired
  • The underlying I2CP session fails to send

func (*DatagramConn) SendToWithOptions

func (d *DatagramConn) SendToWithOptions(payload []byte, destinationB64 string, port uint16, options *Options) error

SendToWithOptions sends a datagram with optional I2P Mapping options.

This method extends DatagramConn.SendTo by allowing the inclusion of options in the datagram envelope. Options are only supported by Datagram2 (protocol 19) and Datagram3 (protocol 20). For other protocols, the options parameter is ignored.

Options can contain arbitrary key/value pairs encoded as an I2P Mapping structure. Common use cases include application-specific metadata, routing hints, or version info.

The options parameter may be nil or empty to send a datagram without options (equivalent to calling DatagramConn.SendTo).

Example:

opts := datagrams.NewOptions(map[string]string{
    "version": "1.0",
    "app": "myapp",
})
err := conn.SendToWithOptions(payload, destB64, port, opts)

Returns an error if:

  • The connection is closed
  • The payload exceeds the maximum size for the protocol type
  • The destination string is invalid
  • The write deadline has expired
  • The underlying I2CP session fails to send

func (*DatagramConn) Session

func (d *DatagramConn) Session() I2CPSession

Session returns the underlying I2CP session. This allows advanced users to access session-level operations if needed.

func (*DatagramConn) SetDeadline

func (d *DatagramConn) SetDeadline(t time.Time) error

SetDeadline sets both read and write deadlines for the connection. This implements the net.PacketConn interface.

A zero value for t means no deadline. After a deadline has been reached, operations will fail with a timeout error.

Note: Deadline support is currently basic - it sets both read and write deadlines to the same value. Use SetReadDeadline/SetWriteDeadline for independent control.

func (*DatagramConn) SetReadDeadline

func (d *DatagramConn) SetReadDeadline(t time.Time) error

SetReadDeadline sets the deadline for future Read operations. A zero value for t means no deadline.

This implements the net.PacketConn interface.

func (*DatagramConn) SetWriteDeadline

func (d *DatagramConn) SetWriteDeadline(t time.Time) error

SetWriteDeadline sets the deadline for future Write operations. A zero value for t means no deadline.

This implements the net.PacketConn interface.

func (*DatagramConn) UnregisterPort

func (d *DatagramConn) UnregisterPort(port uint16) error

UnregisterPort removes the handler for the specified port.

After unregistration, datagrams received on this port will no longer trigger the handler callback. This is useful for graceful shutdown or dynamic port management.

Design notes:

  • Thread-safe: Uses RWMutex to protect the handlers map
  • No-op if not registered: Does not return error if port not registered
  • Immediate effect: Handler won't be called for subsequent receives

Returns an error if:

  • The connection is closed

Example:

conn.UnregisterPort(8080)

func (*DatagramConn) WriteTo

func (d *DatagramConn) WriteTo(p []byte, addr net.Addr) (n int, err error)

WriteTo writes a packet with payload p to addr. WriteTo can be made to time out and return an error after a fixed time limit; see SetDeadline and SetWriteDeadline.

This implements the net.PacketConn interface by wrapping SendTo().

On packet-oriented connections, write timeouts are rare because writes are atomic operations. However, I2P datagrams may experience write delays due to router congestion or tunnel building.

Design notes:

  • Wraps SendTo() to provide standard net.PacketConn semantics
  • Type-asserts net.Addr to I2PAddr for destination extraction
  • Returns full payload length on success (atomic write)
  • Validates address type before attempting send

Returns an error if:

  • addr is not of type *I2PAddr
  • addr has an empty destination string
  • The connection is closed (net.ErrClosed)
  • The write deadline has expired
  • The underlying send operation fails

type I2CPSession

type I2CPSession interface {
	// Destination returns the I2P destination for this session.
	Destination() *i2cp.Destination

	// IsClosed returns true if the session has been closed.
	IsClosed() bool

	// IsOffline returns true if this session uses offline keys (LS2).
	// Sessions with offline keys should use Datagram2 instead of Datagram1.
	// Per I2P specification, Datagram1 does NOT support offline signatures.
	IsOffline() bool

	// SendMessage sends a datagram message to the specified destination.
	// This will be used by the SendTo implementation.
	SendMessage(destination *i2cp.Destination, protocol uint8, srcPort, destPort uint16, payload *i2cp.Stream, nonce uint32) error

	// SendMessageWithContext sends a message with context for cancellation.
	SendMessageWithContext(ctx context.Context, destination *i2cp.Destination, protocol uint8, srcPort, destPort uint16, payload *i2cp.Stream, nonce uint32) error

	// SigningKeyPair returns the Ed25519 signing key pair for this session.
	// This is used to sign authenticated datagrams (Datagram1, Datagram2).
	// Returns error if no signing key pair is available.
	SigningKeyPair() (*i2cp.Ed25519KeyPair, error)
}

I2CPSession is an interface that abstracts the I2CP session operations needed by DatagramConn. This allows for testing without requiring a real I2P router.

The interface includes only the methods DatagramConn actually uses, following the Interface Segregation Principle. This makes testing easier and documents the actual dependencies.

type I2PAddr

type I2PAddr struct {
	// Destination is the I2P destination string (base64-encoded)
	// Empty string represents an unknown or anonymous sender (e.g., Raw datagrams)
	// or a Datagram3 sender where only the hash is known.
	Destination string

	// DestinationHash is the SHA-256 hash of the sender's destination (32 bytes).
	// This is populated for Datagram3 messages where only the hash is included
	// in the protocol. For other protocol types, this may be computed from the
	// Destination field or left as zero.
	// Use HasDestinationHash() to check if this field is populated.
	DestinationHash [32]byte

	// Port is the UDP port number for application-level routing (1-65535)
	Port uint16
}

I2PAddr represents an I2P destination with a port number. It implements the net.Addr interface for compatibility with Go's networking APIs.

I2P destinations are base64-encoded strings that uniquely identify an endpoint in the I2P network. Ports provide application-level multiplexing on top of a single I2CP session.

For Datagram3 (protocol 20), only the DestinationHash is available since the protocol only includes a 32-byte hash of the sender's destination, not the full destination. Applications can use HasDestinationHash() to check if the hash is populated and HasFullDestination() to check if the full destination is available. To reply to a Datagram3 sender, applications need to look up the full destination from a cache or the network database using the hash.

func ParseI2PAddr

func ParseI2PAddr(addr string) (*I2PAddr, error)

ParseI2PAddr parses a string into an I2PAddr. Accepts formats:

  • "destination:port" - full address with destination and port
  • ":port" - port only (destination left empty)
  • "destination" - destination only (port defaults to 0)

Returns an error if the port is invalid or out of range.

func (*I2PAddr) AsNetAddr

func (a *I2PAddr) AsNetAddr() net.Addr

AsNetAddr returns the I2PAddr as a net.Addr interface. This is a convenience method for type assertion-free usage.

func (*I2PAddr) Equal

func (a *I2PAddr) Equal(other *I2PAddr) bool

Equal returns true if two I2P addresses are equal. Compares destination string, destination hash, and port number.

func (*I2PAddr) HasDestinationHash

func (a *I2PAddr) HasDestinationHash() bool

HasDestinationHash returns true if this address has a populated destination hash. This is always true for Datagram3 senders and may be true for other protocols if the hash was computed from the full destination.

func (*I2PAddr) HasFullDestination

func (a *I2PAddr) HasFullDestination() bool

HasFullDestination returns true if this address has a full destination string. When false, the address may have only a hash (for Datagram3) or be empty (for Raw).

func (*I2PAddr) IsHashOnly

func (a *I2PAddr) IsHashOnly() bool

IsHashOnly returns true if only the hash is available without a full destination. This is the case for Datagram3 senders where the protocol only includes the hash.

func (*I2PAddr) Network

func (a *I2PAddr) Network() string

Network returns the network type identifier for I2P addresses. This implements net.Addr.Network().

func (*I2PAddr) String

func (a *I2PAddr) String() string

String returns a human-readable representation of the I2P address. Format: "<destination>:<port>" or "<port>" if destination is unknown. This implements net.Addr.String().

type OfflineSignature

type OfflineSignature struct {
	// Expires is the expiration time of this offline signature authorization.
	// After this time, the transient key should no longer be accepted.
	Expires time.Time

	// TransientSigType is the signature type code for the transient key.
	// This determines the length of TransientPublicKey.
	TransientSigType uint16

	// TransientPublicKey is the public key authorized to sign messages.
	// Length depends on TransientSigType.
	TransientPublicKey []byte

	// Signature is the authorization signature from the destination's key.
	// This proves the destination authorized the transient key.
	// Length depends on the destination's signature type.
	Signature []byte
}

OfflineSignature represents an I2P Offline Signature block used in Datagram2. This allows a transient key to sign messages on behalf of the destination, enabling offline destinations to pre-authorize signing.

Format:

+----+----+----+----+----+----+----+----+
|      expires       |  sigtype  | ...
+----+----+----+----+----+----+----+----+
|   transient_public_key (variable)   |
+----+----+----+----+----+----+----+----+
|   signature of above (variable)     |
+----+----+----+----+----+----+----+----+

expires: 4 bytes, seconds since epoch (unsigned big-endian) sigtype: 2 bytes, signature type of transient key transient_public_key: variable length based on sigtype signature: variable length, signed by destination's key

func OfflineSignatureFromBytes

func OfflineSignatureFromBytes(data []byte, destSigType uint16) (*OfflineSignature, int, error)

OfflineSignatureFromBytes parses an Offline Signature block from binary data. Returns the OfflineSignature, number of bytes consumed, and any error.

Parameters:

  • data: raw bytes containing the offline signature block
  • destSigType: signature type of the destination (determines auth signature length)

func (*OfflineSignature) Bytes

func (o *OfflineSignature) Bytes() []byte

Bytes encodes the OfflineSignature to binary format.

func (*OfflineSignature) IsExpired

func (o *OfflineSignature) IsExpired() bool

IsExpired returns true if the offline signature has expired.

func (*OfflineSignature) Len

func (o *OfflineSignature) Len() int

Len returns the encoded length of the OfflineSignature in bytes.

func (*OfflineSignature) Verify

func (o *OfflineSignature) Verify(dest *i2cp.Destination) error

Verify verifies that the offline signature was signed by the destination's key. This proves the destination authorized the transient key to sign on its behalf.

Per I2P specification, the offline signature is over:

  • expires: 4 bytes (big-endian unsigned seconds since epoch)
  • sigtype: 2 bytes (signature type of transient key)
  • transient_public_key: variable length based on sigtype

Parameters:

  • dest: The destination whose key should have signed this offline signature

Returns nil if verification succeeds, error otherwise.

Note: This library only supports Ed25519 signatures (sigtype 7). Other signature types will return an error.

func (*OfflineSignature) VerifyPayloadSignature

func (o *OfflineSignature) VerifyPayloadSignature(message, signature []byte) bool

VerifyPayloadSignature verifies a payload signature using the transient public key. This should be used for Datagram2 payload verification when an offline signature is present.

Per I2P specification, when offline signatures are used, the payload is signed by the transient key (not the destination's key), allowing offline destinations to pre-authorize signing.

Parameters:

  • message: The data that was signed (for Datagram2: target_hash + flags + options + offline_sig + payload)
  • signature: The signature to verify

Returns true if the signature is valid, false otherwise.

Note: This library only supports Ed25519 transient keys (sigtype 7). Other signature types will return false.

type Options

type Options struct {
	// contains filtered or unexported fields
}

Options represents an I2P Mapping structure for datagram options. This is a wrapper around github.com/go-i2p/common/data.Mapping that provides a simpler interface for datagram-specific use cases.

A Mapping is a set of key/value pairs encoded as:

  • 2-byte size integer (total bytes that follow)
  • Series of String=String; pairs

Each String is 1-byte length followed by UTF-8 data (max 255 bytes). Keys and values should not exceed 255 bytes each.

Per I2P spec: Mappings in signed structures must be sorted by key.

func EmptyOptions

func EmptyOptions() *Options

EmptyOptions returns an empty Options Mapping. When encoded, this produces a 2-byte zero size field.

func NewOptions

func NewOptions(m map[string]string) *Options

NewOptions creates Options from a Go map.

func OptionsFromBytes

func OptionsFromBytes(rawData []byte) (*Options, int, error)

OptionsFromBytes parses an I2P Mapping from binary data using common/data.Mapping. Returns the Options, number of bytes consumed, and any error.

Format:

+----+----+----+----+----+----+----+----+
|  size   | key_string (len + data)| =  |
+----+----+----+----+----+----+----+----+
| val_string (len + data)     | ;  | ...
+----+----+----+----+----+----+----+

func (*Options) Bytes

func (o *Options) Bytes() ([]byte, error)

Bytes encodes the Options as an I2P Mapping. Keys are sorted for signature stability (handled by common/data.Mapping).

Format:

+----+----+----+----+----+----+----+----+
|  size   | key_string (len + data)| =  |
+----+----+----+----+----+----+----+----+
| val_string (len + data)     | ;  | ...
+----+----+----+----+----+----+----+----+

func (*Options) Get

func (o *Options) Get(key string) string

Get retrieves a value by key, returning empty string if not found.

func (*Options) Has

func (o *Options) Has(key string) bool

Has returns true if the key exists in the Options.

func (*Options) IsEmpty

func (o *Options) IsEmpty() bool

IsEmpty returns true if the Options contains no key/value pairs.

func (*Options) Len

func (o *Options) Len() int

Len returns the encoded length of the Options in bytes. This includes the 2-byte size field plus the content.

func (*Options) Set

func (o *Options) Set(key, value string)

Set adds or updates a key/value pair. This invalidates any cached mapping serialization.

func (*Options) ToMap

func (o *Options) ToMap() map[string]string

ToMap returns a copy of the options as a Go map.

type ReceiveResult

type ReceiveResult struct {
	// Payload is the application data extracted from the datagram.
	Payload []byte

	// From is the sender's full I2P destination for authenticated protocols.
	// For Datagram3 (protocol 20), this is nil because only the sender's hash
	// is available. Use FromHash or FromAddr.DestinationHash instead.
	From *i2cp.Destination

	// FromHash is the SHA-256 hash of the sender's destination.
	// Available for Datagram3 and computed from From for other protocols.
	FromHash [32]byte

	// FromAddr is the sender address as an I2PAddr for net.Addr compatibility.
	FromAddr *I2PAddr

	// SrcPort is the source port from the datagram header.
	SrcPort uint16

	// Options contains the I2P Mapping options if present in the datagram.
	// Only Datagram2 (19) and Datagram3 (20) support options.
	// This is nil for protocols that don't support options or when no options
	// were included in the received datagram.
	Options *Options
}

ReceiveResult contains the complete result of receiving a datagram, including optional fields like protocol-specific options.

This struct is returned by DatagramConn.ReceiveFromWithOptions to provide access to all datagram information including the options field which is supported by Datagram2 (protocol 19) and Datagram3 (protocol 20).

For protocols that don't support options (Raw, Datagram1), the Options field will be nil.

Directories

Path Synopsis
examples
echo-client command
Echo client demonstrates sending datagrams and receiving responses.
Echo client demonstrates sending datagrams and receiving responses.
echo-server command
Echo server demonstrates basic datagram send/receive using port-based routing.
Echo server demonstrates basic datagram send/receive using port-based routing.
scripts
container command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL