Universal Network Protocol - A decentralized routing and identity layer for Doh.js applications.
uNET provides a secure, certificate-based network layer that enables:
uNET uses a two-part address format: {prefix}-{position}
| Position | Meaning | Example |
|---|---|---|
| 0 | Reserved | - |
| 1 | Always the Address Provider itself | 1-1, acme-1 |
| 2+ | Regular addresses issued by that provider | 1-2, 1-3, acme-500 |
Self-Documenting Provenance: From any address like 1-5, you instantly know:
1-1 (the Address Provider for prefix "1")1-1 was authorized by 0-1 (the Prefix Root)Position Allocation: First-come-first-served:
acme-deploydoh.com) - if available, it's yoursnull position to get next available automatically┌─────────────────────────────────────────────────────────────────┐
│ Trust Hierarchy │
│ │
│ 0-1 (PREFIX_ROOT - THE single global trust anchor) │
│ │ │
│ ├──► 1-1 (ADDRESS_PROVIDER for prefix "1") │
│ │ │ │
│ │ ├──► 1-2 (regular address) │
│ │ ├──► 1-3 (could be a VPU) ◄────────────────────┐ │
│ │ └──► 1-4 (another address) │ │
│ │ │ │
│ ├──► acme-1 (ADDRESS_PROVIDER for prefix "acme") │ │
│ │ │ │ │
│ │ └──► acme-500 (address in acme namespace) │ │
│ │ │ │
│ └──► [more address providers...] │ │
│ │ │
│ VPU (1-3) signs membership │ │
│ │ │ │
│ ▼ │ │
│ MEMBERSHIP CERT ────────────────┘ │
│ (parallel to address chain, │
│ proves membership in VPU) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Client (1-2) VPU Provider (1-3) │
│ │ │ │
│ │ ─── RFD Connect ──────────────────► │ │
│ │ (address cert + optional │ │
│ │ membership cert) │ │
│ │ │ │
│ │ ◄── Connection Response ─────────── │ │
│ │ (routing level granted) │ │
│ │ │ │
│ │ ═══ Bidirectional Messages ═══════ │ │
│ │ │ │
└─────────────────────────────────────────────────────────────────┘
uNET uses three types of certificate chains:
Every participant must have a valid address certificate chain:
0-1 (PREFIX_ROOT - self-signed trust anchor)
│
└─► {prefix}-1 (ADDRESS_PROVIDER - position 1 in their prefix)
│
└─► {prefix}-N (ADDRESS - your identity, N >= 2)
Example:
0-1 (Prefix Root)
└─► 1-1 (Address Provider for prefix "1")
└─► 1-5 (Your address)
Proves membership in a specific VPU. This is parallel to the address chain, not hierarchical:
VPU's ADDRESS CERT (e.g., 1-3 - any valid address offering VPU services)
└─► MEMBERSHIP CERT (proves you belong to that VPU)
A node can hold multiple membership certificates (member of many VPUs simultaneously). The address is portable - you keep your address when switching VPUs.
Enables cross-VPU routing. Requires dual signatures from both VPUs:
BRIDGE CERT (signed by VPU A, countersigned by VPU B)
0-1)1-1, acme-1)0-1)1-3)require_user_auth enabled)Configure uNET in your boot.pod.yaml (shared baseline) or pod.yaml (local overrides):
unet:
enabled: true
debug: false
providers_base_path: '/.doh/static/unet/providers' # Base path for provider files
# Trusted prefix roots (array format)
trusted_roots:
- C: https://deploydoh.com # Where to get C prefix root cert
roles:
# Client (connect to VPUs)
client:
enabled: true
identity_path: null # Custom path for identity storage
state_path: null # Custom path for state storage
settings_path: null # Custom path for settings
memberships_path: null # Custom path for membership certs
vpu_connections: [] # VPU endpoints to connect to
vpu_memberships: [] # VPU addresses for phone-home (RFDR)
auto_connect: true # Auto-connect on startup
auto_reconnect: true # Auto-reconnect on disconnect
heartbeat_interval: 30000 # Heartbeat interval in ms
address_provider_endpoint: null # Endpoint to request address from
auto_request_address: true # Auto-request address on startup
preferred_address: null # Request specific address ID (e.g., "mynode")
# Routing hub
vpu_provider:
enabled: false
identity_path: null # Custom path for identity storage
state_path: null # Custom path for state storage
settings_path: null # Custom path for settings
database_name: 'unet' # Database for memberships
max_members: null # Max members (null = unlimited)
max_connections: null # Max concurrent connections
require_membership: true # Require membership for full routing
require_user_auth: true # Require user auth for membership
allow_non_members: false # Allow non-member connections (limited routing)
# VPU address acquisition
address_provider_endpoint: null # Endpoint to request VPU's address
auto_request_address: true # Auto-request address on startup
preferred_address: null # Request specific address ID (e.g., "main-vpu")
# Issue addresses to nodes
address_provider:
enabled: false
identity_path: null # Custom path for identity storage
state_path: null # Custom path for state storage
settings_path: null # Custom path for settings
database_name: 'unet_addresses' # Database for address records
max_addresses: null # Max addresses to issue
# Access control
require_auth: true # Require user auth for address requests
allow_preferred: true # Allow clients to request specific address IDs
# Prefix acquisition
prefix_provider_endpoint: null # Endpoint to request prefix from
auto_request_prefix: true # Auto-request prefix on startup
preferred_prefix: null # Request specific prefix (e.g., "acme")
# Trust root for a prefix namespace
prefix_provider:
enabled: false
identity_path: null # Custom path for identity storage
state_path: null # Custom path for state storage
settings_path: null # Custom path for settings
database_name: 'unet_prefixes' # Database for prefix records
max_providers: null # Max address providers to authorize
# Access control
require_auth: true # Require user auth for provider authorization
allow_preferred: true # Allow providers to request specific prefixes
| Category | Type | Direction | Purpose |
|---|---|---|---|
| APC | apc:request |
- | Request address validation |
apc:announce |
- | Announce address ownership | |
apc:validate |
- | Confirm/reject address claim | |
| R | r:request |
- | Request VPU membership |
r:validate |
- | Accept/reject membership | |
r:capacity |
- | Announce VPU capabilities | |
r:disconnect |
- | Leave VPU cleanly | |
| Tree Routing | rfd:request:up (RFDR) |
↑ UP | Route request goes upstream through tree |
rfd:announce:down (RFDA) |
↓ DOWN | Route announce follows request path back | |
rfd:disconnect:up (RFDD) |
↑ UP | Route disconnect, branch-consumed at routers | |
rfd:flush:down (RFDF) |
↓ DOWN | Flush all knowledge of entity | |
r:capacity:announce (RCA) |
↔ LATERAL | Capacity announcement (neighbor-scope, TTL=1) | |
| DATA | data |
- | Application message |
data:ack |
- | Acknowledgment | |
data:error |
- | Error response | |
| SVC | svc:publish |
- | Announce services |
svc:query |
- | Query for services | |
svc:response |
- | Service query response | |
| Control | ping |
- | Heartbeat request |
pong |
- | Heartbeat response | |
error |
- | Protocol error |
uNET uses directional routing where messages flow through the network tree:
VPU (root)
│
┌────┴────┐
↓ ↓
Router A Router B
│ │
↓ ↓
Client X Client Y
RFDR (Route Request) - Goes UPWARD only
requestPath tracking the route takenRFDA (Route Announce) - Goes DOWNWARD only
requestPath back to requesterRFDD (Route Disconnect) - Goes UPWARD, branch-consumed
RFDF (Route Flush) - Goes DOWNWARD all paths
RCA (Capacity Announce) - Neighbor-scope (TTL=1)
Client connects via socket with:
addressCert (required) - proves identitymembershipCert (optional) - proves VPU membershipVPU validates certificate chains against trusted roots
VPU grants routing level:
full - Full routing (members)limited - Basic routing (non-members)Messages flow bidirectionally through VPU
| Module | Purpose |
|---|---|
unet_protocol.doh.js |
Main Doh module, orchestrates all components |
unet_certs.js |
Certificate creation, signing, validation |
unet_handshakes.js |
Message types and handshake state machine |
unet_prefix_provider.js |
Prefix namespace management |
unet_address_provider.js |
Address certificate issuance |
unet_vpu_provider.js |
VPU routing hub implementation |
unet_vpu_client.js |
Client for connecting to VPUs |
unet_routing.js |
Routing table and path finding |
When unet_protocol is loaded, these routes are available:
| Route | Method | Purpose |
|---|---|---|
/.well-known/unet/prefix-root-key.json |
GET | Public key info for PREFIX_ROOT |
/.well-known/unet/prefix-root.json |
GET | Full PREFIX_ROOT certificate |
/.well-known/unet/address-provider.json |
GET | ADDRESS_PROVIDER certificate |
/.well-known/unet/trusted-roots.json |
GET | List of all trusted roots |
/.well-known/unet/vpu-info.json |
GET | VPU provider info and stats |
/.well-known/unet/network-info.json |
GET | Full network discovery info |
GET /api/unet/stats - Get uNET status and statisticsGET /api/unet/vpu/stats - Get VPU-specific statsGET /api/unet/vpu/connections - List connected nodes (extended info: address, isMember, userId, connectedAt, ip, type, connectionType, reachableVia, isRouter, primaryUpstream, upstreamCount, childCount)POST /api/unet/request-address - Request an address certificateGET /api/unet/address/:address - Get address informationPOST /api/unet/request-provider-cert - Request ADDRESS_PROVIDER authorization from PREFIX_ROOTPOST /api/unet/vpu/connect - Connect to a VPUPOST /api/unet/vpu/disconnect - Disconnect from VPUPOST /api/unet/vpu/request-membership - Request membership with user authPOST /api/unet/message - Send messages through VPUunet:rfd:connect - RFD connection handshakePOST /api/unet/test/broadcast - Send test broadcast to connected nodesThe Cloud Manager includes a uNET dashboard tab showing:
Real-time updates via socket broadcast (no polling required):
unet:state - Full state including vpuStats and vpuConnectionsunet:connection - Connect/disconnect events with addressunet:message - Message activityThe dashboard automatically updates when:
The unet_admin module provides a dedicated admin interface at /unet-admin with:
Client Wizard Steps:
Advanced Options:
mynode)require_auth: true)Provider Wizards:
Each wizard guides through the setup process with real-time validation and capability queries to ensure proper configuration.
| Event | Purpose |
|---|---|
unet:state |
Full state including vpuStats and vpuConnections |
unet:connection |
Connect/disconnect events with address |
unet:message |
Message activity |
| Event | Purpose |
|---|---|
unet:client:connection |
Client connection status changes |
unet:rfd:connect |
RFD connection request |
unet:rfdr |
Route For Destination Request (to VPU) |
unet:rfda |
Route For Destination Announce (from VPU) |
unet:rfdr:reject |
RFDR rejection |
unet:ping / unet:pong |
Heartbeat keep-alive |
Clients can join VPUs through router intermediaries, enabling federated topology without direct connections to all VPUs.
Configuration:
unet:
roles:
client:
vpu_connections:
- https://router.deploydoh.com # Connect through router
vpu_memberships:
- '1-3' # VPU address to phone-home to
How it works:
Providers can be exported as gzipped tarballs for deployment across servers.
Export: Creates a portable archive containing:
Import: Restores provider state on a new server, enabling:
The client tracks detailed connection status for each VPU:
{
endpoint: 'https://vpu.example.com',
vpuAddress: '1-2',
lastHeartbeat: 1705123456789,
lastCommunication: 1705123456789,
lastError: null,
reconnectAttempts: 0,
isMember: true
}
Endpoint Mapping Cache:
{prefix}-{position} encodes the entire trust chain - from any address you know who issued it (the Address Provider at {prefix}-1) without lookups0-1) as the global trust anchor0-1)# boot.pod.yaml
unet:
enabled: true
roles:
prefix_provider:
enabled: true
address_provider:
enabled: true
vpu_provider:
enabled: true
require_membership: false
allow_non_members: true
client:
enabled: true
# boot.pod.yaml
unet:
enabled: true
trusted_roots:
- C: https://deploydoh.com
roles:
client:
enabled: true
vpu_connections:
- https://vpu.deploydoh.com
# boot.pod.yaml
unet:
enabled: true
trusted_roots:
- C: https://deploydoh.com
roles:
client:
enabled: true
vpu_connections:
- https://router.deploydoh.com # Connect through router
vpu_memberships:
- '1-2' # Phone-home to this VPU
# boot.pod.yaml
unet:
enabled: true
roles:
vpu_provider:
enabled: true
require_membership: true
require_user_auth: true
max_members: 1000
max_connections: 5000
client:
enabled: true
auto_reconnect: true
heartbeat_interval: 30000
The VPU client implements robust reconnection:
VPU providers persist membership data to a database, ensuring memberships survive server restarts:
unet.vpu_memberships tableWhen clients connect via socket, the VPU automatically:
unet:connection disconnect eventVPU membership now requires user authentication by default. Users authenticate once with username/password to receive a MEMBERSHIP certificate, which is stored locally and used for subsequent connections.
Client VPU Provider
| |
| POST /api/unet/vpu/request-membership |
| { username, password, addressCert } |
|--------------------------------------->|
| | 1. Validate credentials (Users system)
| | 2. Issue MEMBERSHIP cert with userId
|<---------------------------------------|
| { success, membershipCert, vpuAddress }|
| |
| (Store cert locally) |
| |
| RFD Connect (Socket) |
| { addressCert, membershipCert } |
|--------------------------------------->|
| | 1. Validate cert chains
| | 2. Check userId not suspended
|<---------------------------------------|
| { success, routing: 'full' } |
The MEMBERSHIP certificate contains the userId (username/email) in its claims. On every RFD connection, the VPU validates:
Revocation Model: Validation happens at connection time, not via real-time events. If a user is deleted or locked out, their next connection attempt will be rejected. Existing connections remain active until they disconnect and try to reconnect.
# Set the VPU endpoint in boot.pod.yaml
doh vpu endpoint <url>
# Join a VPU with user authentication (interactive)
doh vpu join
doh vpu join [<url>]
# Join with credentials (non-interactive)
doh vpu join -u <username> -p <password>
doh vpu join <url> -u <username> -p <password>
# Join VPU via router (federated mode)
doh vpu join <vpu-url> <router-url>
doh vpu join <vpu-url> <router-url> -u <username> -p <password>
# Check membership status
doh vpu status
# Reset all uNET data (identities, databases, settings)
doh unet-reset # Interactive confirmation
doh unet-reset --confirm # Skip confirmation (scripting)
Examples:
# Set endpoint and join interactively
doh vpu endpoint https://deploydoh.com
doh vpu join
# Join directly with URL
doh vpu join https://deploydoh.com
# Join with credentials (scripting/CI)
doh vpu join https://deploydoh.com -u admin@site.com -p mypassword
# Join through a router (federated)
doh vpu join https://vpu.deploydoh.com https://router.deploydoh.com -u user@site.com -p pass
# boot.pod.yaml or pod.yaml
unet:
roles:
vpu_provider:
require_user_auth: true # Require user auth (default: ON)
client:
vpu_connections:
- https://deploydoh.com # VPU endpoints to connect to
VPU Provider (require_user_auth):
true (default): Membership requests require valid username/password credentialsfalse: Memberships can be issued without authenticationAddress Provider (require_auth):
true (default): Address requests require valid username/password credentialsfalse: Addresses can be issued without authenticationWhen auth is required, credentials are passed in the request body and validated via the Users system.
# Open VPU (no auth required for membership)
unet:
roles:
vpu_provider:
require_user_auth: false
allow_non_members: true
# Open Address Provider (no auth required for addresses)
unet:
roles:
address_provider:
require_auth: false
doh_modules/unet/
├── README.md # This file
├── unet_protocol.doh.js # Main Doh module
├── unet_certs.js # Certificate system
├── unet_handshakes.js # Message protocol
├── unet_prefix_provider.js # Prefix provider
├── unet_address_provider.js # Address provider
├── unet_vpu_provider.js # VPU provider (with user auth)
├── unet_vpu_client.js # VPU client
├── unet_vpu_anchor_cli.js # VPU membership CLI (doh vpu)
├── unet_routing.js # Routing table
├── unet_provider_export.js # Provider tarball export/import
└── docs/
├── certificates.md # Certificate system details
├── handshakes.md # Protocol message reference
└── providers.md # Provider role details