NetDB Client/Router Isolation - go-i2p/go-i2p
NetDB Client/Router Isolation
Overview
This document describes the NetDB isolation architecture that separates client and router database operations, implementing complete data isolation between I2CP clients and the router’s network database.
Architecture
Components
- StdNetDB - Base implementation that stores both RouterInfos and LeaseSets
- ClientNetDB - Wrapper providing LeaseSet-only operations for clients
- RouterNetDB - Wrapper providing RouterInfo + LeaseSet operations for the router
Isolation Strategy
- Complete Separation: Each I2CP client receives its own isolated
StdNetDBinstance - No Shared State: Clients cannot access each other’s data or the router’s database
- Type Safety: Go’s type system enforces isolation at compile time
- Ephemeral Storage: Client databases are in-memory only (not persisted to disk)
- Persistent Router: The router’s NetDB is persistent and stored on disk
Implementation Details
ClientNetDB
File: lib/netdb/client_netdb.go
Purpose: Isolates client LeaseSet operations from router operations
Operations:
GetLeaseSet(hash)- Retrieve LeaseSet by hashGetLeaseSetBytes(hash)- Get raw LeaseSet dataStoreLeaseSet(leaseSet)- Store a LeaseSetStoreLeaseSet2(leaseSet2)- Store a LeaseSet2GetLeaseSetCount()- Count stored LeaseSets
Key Design:
type ClientNetDB struct {
db *StdNetDB // Own isolated instance
}
Each session creates its own StdNetDB with an empty path (ephemeral):
db := NewStdNetDB("") // Empty path = in-memory only
clientDB := NewClientNetDB(db)
RouterNetDB
File: lib/netdb/router_netdb.go
Purpose: Provide router-specific operations including floodfill functionality
RouterInfo Operations:
GetRouterInfo(hash)- Get RouterInfo by hashStoreRouterInfo(routerInfo)- Store a RouterInfoSelectPeers(count, filter)- Select peers for tunnel buildingSelectFloodfillRouters(count)- Select floodfill routersGetRouterInfoCount()- Count stored RouterInfosGetDatabaseSize()- Get total database size
LeaseSet Operations (for direct router database operations):
GetLeaseSet(hash)- Retrieve LeaseSet for floodfill, detached lookups, and direct operationsGetLeaseSetBytes(hash)- Get raw LeaseSet data for network responsesStoreLeaseSet(leaseSet)- Store LeaseSet from direct database store messagesStoreLeaseSet2(leaseSet2)- Store LeaseSet2 from direct database store messagesGetLeaseSetCount()- Count stored LeaseSets
Key Design:
type RouterNetDB struct {
db *StdNetDB // Router's persistent instance
}
The router creates a persistent StdNetDB with a filesystem path:
db := NewStdNetDB("/path/to/netdb") // Persistent storage
routerDB := NewRouterNetDB(db)
I2CP Session Integration
File: lib/i2cp/session.go
Changes: Each session now creates its own ephemeral ClientNetDB:
func NewSession(id uint16, dest common.Destination, config *config.RouterConfig, _ string) (*Session, error) {
// Create ephemeral in-memory database (empty path)
db := NewStdNetDB("")
return &Session{
id: id,
dest: dest,
clientDB: NewClientNetDB(db),
// ...
}, nil
}
The netDBPath parameter is now ignored (for backward compatibility) since all client databases are ephemeral.
Database Message Routing
The following chart shows all possible paths for database messages and how they are handled:
┌─────────────────────────────────────────────────────────────────────────────┐
│ DATABASE MESSAGE ROUTING │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ MESSAGE TYPE │ SOURCE │ CONTAINS │ HANDLER │
│ ──────────────────────┼──────────────────┼─────────────────┼───────────────│
│ │
│ DatabaseStore │ Network (direct) │ RouterInfo │ RouterNetDB │
│ (DataType=0) │ to router │ │ .StoreRouter │
│ │ │ │ Info() │
│ ──────────────────────┼──────────────────┼─────────────────┼───────────────│
│ │
│ DatabaseStore │ Network (direct) │ LeaseSet │ RouterNetDB │
│ (DataType=1) │ to router │ │ .StoreLeaseS │
│ │ │ │ et() │
│ ──────────────────────┼──────────────────┼─────────────────┼───────────────│
│ │
│ DatabaseStore │ Network (direct) │ LeaseSet2 │ RouterNetDB │
│ (DataType=3) │ to router │ │ .StoreLeaseS │
│ │ │ │ et2() │
│ ──────────────────────┼──────────────────┼─────────────────┼───────────────│
│ │
│ DatabaseStore │ Client session │ LeaseSet │ ClientNetDB │
│ (DataType=1) │ via I2CP │ (own dest) │ .StoreLeaseS │
│ │ │ │ et() │
│ ──────────────────────┼──────────────────┼─────────────────┼───────────────│
│ │
│ DatabaseStore │ Client session │ LeaseSet2 │ ClientNetDB │
│ (DataType=3) │ via I2CP │ (own dest) │ .StoreLeaseS │
│ │ │ │ et2() │
│ ──────────────────────┼──────────────────┼─────────────────┼───────────────│
│ │
│ DatabaseLookup │ Network (direct) │ Hash │ RouterNetDB │
│ (RouterInfo) │ to router │ (RouterInfo) │ .GetRouterIn │
│ │ │ │ fo() │
│ ──────────────────────┼──────────────────┼─────────────────┼───────────────│
│ │
│ DatabaseLookup │ Network (direct) │ Hash │ RouterNetDB │
│ (LeaseSet) │ to router │ (Destination) │ .GetLeaseSet │
│ │ │ │ () │
│ ──────────────────────┼──────────────────┼─────────────────┼───────────────│
│ │
│ DatabaseLookup │ Client session │ Hash │ ClientNetDB │
│ (detached) │ via I2CP │ (Destination) │ .GetLeaseSet │
│ │ │ │ () │
│ ──────────────────────┼──────────────────┼─────────────────┼───────────────│
│ │
│ Local Operation │ Tunnel building │ N/A │ RouterNetDB │
│ (peer selection) │ subsystem │ │ .SelectPeers │
│ │ │ │ () │
│ ──────────────────────┼──────────────────┼─────────────────┼───────────────│
│ │
│ Local Operation │ Floodfill │ N/A │ RouterNetDB │
│ (FF selection) │ subsystem │ │ .SelectFloodi │
│ │ │ │ fillRouters() │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
KEY ISOLATION RULES:
• Client sessions NEVER access RouterInfo (enforced by type system)
• Client sessions ONLY access their own ephemeral LeaseSet storage
• Router NEVER accesses client session databases
• Each client session has completely isolated in-memory database
• Router database is persistent on disk
• Direct network messages always route to RouterNetDB
• I2CP messages from clients route to session's ClientNetDB
Benefits
- Security: Clients cannot access router’s RouterInfo database
- Privacy: Clients cannot see each other’s LeaseSets
- Reliability: Client database corruption doesn’t affect router or other clients
- Simplicity: No complex sharing logic or synchronization needed
- Direct Operations: Router can handle all direct database operations (floodfill, detached lookups, etc.) while maintaining isolation from clients
Testing
Comprehensive test coverage in:
lib/netdb/client_netdb_test.go- ClientNetDB operations and isolationlib/netdb/router_netdb_test.go- RouterNetDB operations including floodfilllib/i2cp/session_test.go- Session isolation and ephemeral storage
All tests verify:
- Correct operation of isolated databases
- Type safety enforcement
- Ephemeral vs persistent storage behavior
- Concurrent access safety
- Floodfill LeaseSet operations
Migration Notes
Breaking Changes
- NewSession signature: The
netDBPathparameter is now ignored “`go // Before: path was used session, err := NewSession(id, dest, config, “/path/to/netdb”)
// After: path is ignored (pass empty string) session, err := NewSession(id, dest, config, “”)
2. **CreateSession signature**: Same as above
```go
// Before
manager.CreateSession(dest, config, "/path/to/netdb")
// After: path ignored
manager.CreateSession(dest, config, "")
Router Integration
When initializing the router, create a RouterNetDB wrapper:
// Create persistent router database
stdDB := netdb.NewStdNetDB("/path/to/router/netdb")
if err := stdDB.Create(); err != nil {
return err
}
// Wrap in RouterNetDB for type safety
routerDB := netdb.NewRouterNetDB(stdDB)
// Use routerDB for all router operations
routerInfo := routerDB.GetRouterInfo(hash)
routerDB.StoreLeaseSet(leaseSet) // Direct database store operation
Future Enhancements
Possible improvements:
- LRU eviction for client databases to limit memory usage
- Configurable TTL for client LeaseSets
- Metrics for database sizes and operations
- Router’s LeaseSet cache optimization for direct operations and detached lookups