Skip to content

Network & Transport

@xnet/network handles getting data between peers. It sits below the sync layer — sync decides what to send, network decides how to get it there.

The package provides:

  • A libp2p node with WebRTC and WebSocket transports
  • A layered security stack (connection gating, rate limiting, peer scoring, auto-blocking)
  • A custom sync protocol over libp2p streams
  • A y-webrtc provider for real-time document sync
┌─────────────────────────────┐
│ y-webrtc provider │ Real-time document sync
├─────────────────────────────┤
│ Custom sync protocol │ /xnet/sync/1.0.0
├─────────────────────────────┤
│ libp2p │ Peer management, DHT
├─────────────────────────────┤
│ WebRTC · WebSocket · Relay │ Transports
├─────────────────────────────┤
│ Noise encryption · Yamux mux │ Security + multiplexing
└─────────────────────────────┘
  • WebRTC — Browser-to-browser peer connections via data channels
  • WebSocket — Signaling server connections, relay fallback
  • Circuit Relay v2 — NAT traversal via relay nodes when direct P2P fails
  • Noise protocol — Authenticated encryption for all connections
  • Yamux — Stream multiplexing over a single connection
flowchart TD
  A["Incoming connection"] --> B["1. Connection Limits\n(max connections, per-IP)"]
  B -->|allowed| C["2. Connection Gating\n(denylist, allowlist)"]
  C -->|allowed| D["3. Rate Limiting\n(token bucket per peer)"]
  D -->|allowed| E["4. Peer Scoring\n(reputation: -100 to +100)"]
  E -->|above threshold| F["5. Auto-Blocking\n(ban on repeated violations)"]
  F -->|passed| G["Process message"]

  B -->|denied| X["Reject"]
  C -->|denied| X
  D -->|exceeded| X
  E -->|below threshold| X
  F -->|blocked| X

Five layers of defense, each operating independently:

The ConnectionTracker enforces hard limits:

LimitDefaultStrictRelaxed
Max connections10050200
Per peer214
Per IP5310
Max pending201050
Streams per connection10050200
Connections per minute301560

Three presets: DEFAULT_LIMITS, STRICT_LIMITS (mobile/constrained), RELAXED_LIMITS (trusted network).

The DefaultConnectionGater intercepts connections at four points:

  1. Before accepting — Check IP denylist
  2. Before dialing — Check peer denylist
  3. After handshake — Check limits, denylist, allowlist bypass
  4. Before stream — Check stream limits

Allowlisted peers bypass all connection limits.

Token bucket rate limiting at two levels:

Per-peer sync rate:

const limiter = new SyncRateLimiter() // 10 tokens/sec, 50 capacity
limiter.canSync(peerId) // consume 1 token
limiter.penalize(peerId, 'major') // reduce rate to 0.5x
limiter.restore(peerId) // reset to default

Per-protocol rate:

ProtocolRateCapacity
/xnet/sync/1.0.010/sec50
/xnet/changes/1.0.020/sec100
/xnet/query/1.0.05/sec20

Penalized peers get dynamically reduced rates.

Reputation-based scoring inspired by GossipSub v1.1. Peers start at 0, range is -100 to +100:

Positive factors:

FactorWeight
Sync success+0.5
Valid change+0.1
Uptime (per minute)+0.01 (cap +10)
Low latency (<100ms)+5

Negative factors:

FactorWeight
Sync failure-2
Invalid signature-50
Invalid data-10
Rate limit violation-5

Thresholds: warn at 0, throttle at -20, disconnect at -50. Scores decay by 1% per minute toward zero.

The AutoBlocker bans peers that exceed event count thresholds:

EventCountWindowBlock Duration
Invalid signature360s24 hours
Rate limit exceeded1060s1 hour
Connection flood2060s1 hour
Invalid data55 min12 hours

Blocks auto-expire. The auto-blocker integrates with the peer scorer — when a peer’s score drops below the disconnect threshold, it’s automatically blocked for 1 hour.

PeerAccessControl provides workspace-scoped access with this priority:

global deny > workspace deny > allowlist mode > default allow

When allowlist mode is enabled for a workspace, only explicitly allowlisted peers can sync. Deny entries support expiration.

All security events are logged in a fail2ban-compatible format:

2026-02-04T12:00:00.000Z XNET_SECURITY: type=invalid_signature severity=high peer=a1b2c3d4 action=blocked

Peer IDs are hashed for privacy. Events route by severity: critical/high to console.error, medium to console.warn, low to console.log.

The /xnet/sync/1.0.0 protocol provides structured document sync over libp2p streams:

  1. Requester sends a sync-request with the local Yjs state vector
  2. Responder replies with a sync-response containing the diff
  3. Requester applies the update via Y.applyUpdate()

Messages are serialized with msgpack and length-prefixed.