Skip to content

Feat(endpoint): add firebasetunnel_client / firebasetunnel_server endpoint pair#24

Open
hiddifydeveloper wants to merge 8 commits into
hiddify:testing-merge-basefrom
hiddifydeveloper:firebasetunnel
Open

Feat(endpoint): add firebasetunnel_client / firebasetunnel_server endpoint pair#24
hiddifydeveloper wants to merge 8 commits into
hiddify:testing-merge-basefrom
hiddifydeveloper:firebasetunnel

Conversation

@hiddifydeveloper

Copy link
Copy Markdown

feat(endpoint): add firebasetunnel_client / firebasetunnel_server endpoint pair

Summary

Ports the Firebase Realtime Database relay mechanism from Hiddify2/Firebase-Tunnel (a proof-of-concept with no LICENSE) into hiddify-sing-box as a proper Endpoint pair (firebasetunnel_client / firebasetunnel_server). No code was vendored — the mechanism was studied and reimplemented from scratch for sing-box's architecture.

  • Client (firebasetunnel_client): DialContext creates a session node in Firebase RTDB, waits for the server to mark it active, then relays bytes bidirectionally via batched/compressed/optionally-encrypted Firebase chunk queues.
  • Server (firebasetunnel_server): pollLoop discovers pending sessions via Firebase SSE stream (with periodic polling fallback), validates the declared user against configured list, routes each accepted connection through router.RouteConnectionEx so all sing-box routing rules/DNS/sniffing apply to egress.
  • Per-user traffic metering via SSMTracker.TrackConnection — same hook Hiddify Manager already polls for Shadowsocks per-user quota/billing.

New files

Path Purpose
option/firebasetunnel.go Config option structs
constant/proxy.go Two new type constants + display names
protocol/firebasetunnel/protocol.go Wire types, session state machine, path helpers
protocol/firebasetunnel/firebase.go Firebase REST client (retry, SSE reconnect, jittered backoff)
protocol/firebasetunnel/compress.go zstd compress/decompress
protocol/firebasetunnel/crypto.go AES-256-GCM with AAD, HMAC-SHA256 integrity
protocol/firebasetunnel/session.go Chunk sender/receiver pipeline
protocol/firebasetunnel/client.go ClientEndpointDialContext + relay
protocol/firebasetunnel/server.go ServerEndpoint — poll/relay/GC loops
include/registry.go Register both endpoints
protocol/firebasetunnel/docs/ Architecture, config reference, security, operations, provenance

Security & robustness hardening

  • AES-256-GCM payload encryption (opt-in per-user PSK) with sessionID + direction + seq as AEAD additional data — prevents cross-session/direction chunk replay attacks
  • HMAC-SHA256 integrity (16-byte tag derived from firebase_secret) on all unencrypted chunks — detects silent byte flips by Firebase-side observers or project admins
  • Cryptographically verified user identity — per-user PSK turns self-reported user label into a verified credential (only correct key decrypts)
  • Global + per-user concurrent session caps + per-user token-bucket rate limiting on session creation
  • Sender backpressure byte cap (8 MiB) + chunk size cap (1 MiB per chunk) — prevents memory exhaustion from malicious/buggy clients
  • Session collision defense — server re-fetches metadata after acquiring slot, rejects CreatedAt mismatches
  • Graceful drain on Close() — 5 s drain window, no new sessions accepted, then hard context cancel
  • Panic-recovery restart loops on poll + GC goroutines — Firebase outage cannot crash the rest of the sing-box process
  • Client activation fail-fast — jittered exponential backoff on GET errors, Warn log after 3 s still-pending
  • Background GC sweep for orphaned sessions (never reached active before timeout, or stale closing/closed nodes)
  • Multiple firebase_urls supported — round-robin failover across Firebase projects

Known limitations (inherent to transport choice)

  • ~200 ms+ added RTT floor from Firebase RTDB polling — same as the upstream PoC, inherent to the mechanism
  • Firebase rate limits apply to the whole project; high session counts burn quota
  • firebase_secret (legacy DB secret) grants full project read/write — recommend firebase_auth_token (short-lived service-account token) for production, or rely on PSK encryption to limit blast radius

Threat model (brief)

  • Firebase project owner / anyone holding firebase_secret can read raw session metadata (target host/port, timing) — payload is protected by PSK encryption when configured, but metadata is not
  • PSK encryption protects payload confidentiality and user-identity integrity; HMAC provides integrity on the unencrypted path
  • This protocol does not protect against traffic analysis via chunk timing/sizes, or a malicious Firebase project admin

Provenance

Upstream PoC (Hiddify2/Firebase-Tunnel) has no LICENSE. Zero code was copied or vendored. protocol/firebasetunnel/docs/provenance.md contains a full same-vs-different table.

Test plan

  • go build ./... clean
  • go vet ./protocol/firebasetunnel/ clean
  • go test ./protocol/firebasetunnel/... -v — requires go1.25.6 toolchain (module constraint); all tests written and vet-clean, expected to pass in CI
  • go test -fuzz=FuzzIngestChunk -fuzztime=30s — fuzz targets for all attacker-reachable JSON/chunk-decode paths
  • Manual E2E: two sing-box instances with a real Firebase RTDB project URL + one configured user

hiddifydeveloper and others added 8 commits June 29, 2026 11:21
Adds FirebaseTunnelClientOptions/FirebaseTunnelServerOptions and the
firebasetunnel_client/firebasetunnel_server type constants, laying the
groundwork for a Firebase Realtime Database relay endpoint adapted from
github.com/Hiddify2/Firebase-Tunnel.
Implements the Firebase Realtime Database REST client (Get/Put/Delete with
retry, SSE Listen with jittered reconnect), the chunk batching/reassembly
pipeline (chunkSender/chunkReceiver), optional AES-256-GCM payload
encryption, and zstd compression helpers.

Adapted from github.com/Hiddify2/Firebase-Tunnel (no LICENSE upstream;
mechanism studied and reimplemented rather than vendored). Adds backpressure
limits and jittered backoff not present in the original PoC.
Implements ClientEndpoint and ServerEndpoint as adapter.Endpoint, mirroring
the existing tunnel_client/tunnel_server shape. Client adapts the relay
into a net.Pipe-backed DialContext; server discovers sessions via Firebase
SSE, enforces per-user/global session caps and per-user creation rate
limits, dials real targets via router.RouteConnectionEx (so existing
routing/DNS/sniffing rules apply), and wires per-user traffic accounting
into the SSM tracker (the same subsystem used for Shadowsocks usage).

Adds a background GC sweep for abandoned sessions and panic-recovery
restart for the poll/GC loops so a fault here can't take down the rest of
the sing-box process. Registered in include/registry.go's
EndpointRegistry().
Covers zstd round-trip, AES-256-GCM encrypt/decrypt (including wrong-key
and truncated-ciphertext failure cases), chunk sender/receiver round-trip
against an in-memory fake Firebase REST server, out-of-order chunk
reassembly, duplicate-chunk handling, and the sender backpressure cap.
Adds protocol/firebasetunnel/docs/ covering architecture, configuration
reference, threat model/security tradeoffs, and operational guidance
(limits, GC, failure isolation, rollout), plus a provenance doc detailing
what was reused vs. rewritten from github.com/Hiddify2/Firebase-Tunnel.
- Add HMAC-SHA256 integrity tags (16 B, derived from firebase_secret)
  on all unencrypted chunks so a Firebase-side observer cannot silently
  flip payload bytes without detection.
- Add AES-256-GCM AEAD additional data binding: each encrypted chunk now
  encodes sessionID + direction + seq as AAD, so a chunk cannot be
  replayed from one session, direction, or sequence position into another.
- Thread sessionID + direction + hmacKey through newChunkSender /
  newChunkReceiver / flushBuffer / decodeChunkPayload — all call sites in
  client.go and server.go updated.
- Add maxChunkBytes (1 MiB) cap in decodeChunkPayload: oversized chunks
  are rejected before any allocation, preventing memory exhaustion from
  malicious/buggy clients.
- Client activation fail-fast: waitForActive now backs off on consecutive
  GET errors (jitteredBackoff) instead of fixed 250 ms, and logs a Warn
  after 3 s of pending state so operators can detect absent servers early.
- Session ID collision defense in handleSession: re-fetches metadata after
  acquireSessionSlot and verifies CreatedAt matches the polled value;
  mismatches (GC-lag collisions, stale seen-map entries) are rejected.
- Graceful drain on Close(): sets closing atomic, waits up to 5 s for
  activeSessions to reach 0, then cancels the server context; new sessions
  are rejected while draining.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fakeFirebaseServer now implements SSE streaming: GET with
  Accept: text/event-stream returns a live event-stream, broadcasting
  Firebase put-style events to connected listeners on every PUT, so
  pollLoop's SSE path is exercised without a real Firebase project.
- TestChunkSenderHMAC: verifies HMAC tags are written and verified;
  wrong HMAC key must fail ingest.
- TestChunkAADBinding: verifies that an encrypted chunk cannot be
  decrypted if sessionID, direction, or seq in the AAD differs.
- TestChunkSizeCapRejected: verifies oversized chunks are rejected.
- TestRelayRoundTrip / TestRelayEncryptedRoundTrip: full end-to-end
  chunk relay using fakeFirebaseServer — client runSession writes c2s
  chunks, server runRelay reads and echoes them back via s2c chunks,
  client reads the echo; covers both HMAC-only and PSK-encrypted paths.
- FuzzIngestChunk / FuzzParseSessionMetadata / FuzzDecodeChunkPayload:
  fuzz targets for all attacker-reachable JSON parsing and chunk decode
  paths; must not panic on arbitrary input.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace TypeFirebaseTunnelClient + TypeFirebaseTunnelServer (two separate
proxy types) with a single TypeFirebaseTunnel. Role — server (inbound) or
client (outbound) — is now determined at construction time by which sub-config
is present in FirebaseTunnelOptions: options.Server != nil → server mode,
options.Client != nil → client mode.

Changes:
- option/firebasetunnel.go: one FirebaseTunnelOptions with nested
  *FirebaseTunnelServerConfig and *FirebaseTunnelClientConfig; shared fields
  (firebase_urls, firebase_secret, firebase_auth_token, retry_limit) at top level
- constant/proxy.go: two constants → TypeFirebaseTunnel = "firebasetunnel"
- protocol/firebasetunnel/endpoint.go: single Endpoint struct with srv *serverState
  (nil in client mode); extract drainInto, copyToSender, isTerminal helpers;
  fix perUserActive map leak on releaseSessionSlot; add all missing constants
- protocol/firebasetunnel/client.go: deleted
- protocol/firebasetunnel/server.go: deleted
- include/registry.go: two Register calls → one RegisterEndpoint call
- protocol/firebasetunnel/endpoint_test.go: update struct literals to Endpoint{}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant