Skip to content

feat(firebasetunnel): port Firebase Tunnel as proper inbound/outbound pair#29

Open
hiddifydeveloper wants to merge 395 commits into
hiddify:v5from
hiddifydeveloper:firebasetunnel-clean
Open

feat(firebasetunnel): port Firebase Tunnel as proper inbound/outbound pair#29
hiddifydeveloper wants to merge 395 commits into
hiddify:v5from
hiddifydeveloper:firebasetunnel-clean

Conversation

@hiddifydeveloper

@hiddifydeveloper hiddifydeveloper commented Jul 6, 2026

Copy link
Copy Markdown

Summary

Ports the Firebase Realtime Database relay mechanism from Hiddify2/Firebase-Tunnel into hiddify-sing-box as a proper inbound/outbound pair following the same pattern as protocol/anytls and protocol/mieru.

  • firebasetunnel-in (Inbound): accept loop discovers pending sessions via Firebase SSE stream + periodic polling fallback. Validates user, applies rate/session caps, sets metadata.User and calls router.RouteConnectionEx — sing-box handles all routing, outbound selection, and per-user traffic accounting automatically.
  • firebasetunnel-out (Outbound): DialContext creates a session node in Firebase RTDB, waits for the server to activate it, then relays bytes bidirectionally via batched/compressed/optionally-encrypted Firebase chunk queues.

New files

Path Purpose
protocol/firebasetunnel/inbound.go Inbound struct + RegisterInbound + accept loop
protocol/firebasetunnel/outbound.go Outbound struct + RegisterOutbound + DialContext
protocol/firebasetunnel/common.go Shared constants and helpers
protocol/firebasetunnel/firebase.go Firebase REST client (GET/PUT/DELETE + SSE listen)
protocol/firebasetunnel/session.go chunkSender / chunkReceiver with batching, ordering, ack
protocol/firebasetunnel/protocol.go Wire types: sessionMetadata, chunk, path helpers
protocol/firebasetunnel/crypto.go AES-256-GCM with session-ID AAD binding; HMAC-SHA256 integrity
protocol/firebasetunnel/compress.go zstd compress/decompress via lazy sync.Once (no init/panic)
protocol/firebasetunnel/util.go Token bucket, jittered backoff
option/firebasetunnel.go FirebaseTunnelInboundOptions + FirebaseTunnelOutboundOptions

Robustness

  • HMAC-SHA256 integrity on unencrypted chunks (derived from firebase_secret)
  • AES-256-GCM with sessionID+direction+seq as AEAD additional data — prevents cross-session chunk replay
  • Token bucket rate limiting per user (configurable sessions/second)
  • Per-user session caps (global + per-user max concurrent sessions)
  • Collision defense: re-reads CreatedAt before marking session active
  • 1 MiB chunk size cap — server rejects oversized chunks before decryption
  • Graceful drain on Close(): waits up to 5s for active sessions before cancelling context
  • Activation fail-fast: exponential backoff on consecutive poll errors; warns after 3s
  • Loop restart on error: loopWithRestart returns errors (no recover()/panic)

Tests

  • session_test.go — chunk sender/receiver round-trips (plain, encrypted, HMAC, out-of-order, duplicate, backpressure, size cap)
  • endpoint_test.go — E2E relay round-trips using in-process fakeFirebaseServer with SSE support
  • crypto_test.go — encrypt/decrypt, HMAC tag, AAD binding
  • compress_test.go — compress/decompress
  • fuzz_test.goFuzzIngestChunk, FuzzParseSessionMetadata, FuzzDecodeChunkPayload

Docs

protocol/firebasetunnel/docs/ contains architecture, configuration reference, and security model documentation.

🤖 Generated with Claude Code

hiddify-com and others added 30 commits February 15, 2026 14:46
hiddify-com and others added 28 commits May 30, 2026 00:06
…stream

Restore upstream DNS query options, neighbor LookupAddresses, certificate
ExclusiveAnchors, and local DNS exchange on Darwin. Remove the obsolete
darwin-only local transport stub that blocked shared resolver code.

Co-authored-by: Cursor <cursoragent@cursor.com>
Bring back upstream Tailscale exit-node and SSH session RPC definitions,
generated protobuf code, and daemon helpers removed during the merge so
libbox and started_service compile against the current testing branch.

Co-authored-by: Cursor <cursoragent@cursor.com>
Re-add upstream TLSSpoof metadata to InboundContext while keeping hiddify
tunnel routing fields (TunnelSource, RealOutbound, etc.) required by the
fork's route and monitoring logic.

Co-authored-by: Cursor <cursoragent@cursor.com>
Preserve fork-specific DNS types, monitoring/unified-delay settings,
WARP cache storage, tunnel routing rules, and related option fields
needed by hiddify protocols without altering upstream core interfaces.

Co-authored-by: Cursor <cursoragent@cursor.com>
…upstream API

Add WARP/binary cache helpers via LoadRuleSet delegation and per-outbound
traffic accounting through additive Manager methods, keeping upstream
PushUploaded/PushDownloaded signatures unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: xream <1210282+xream@users.noreply.github.com>
Import MASQUE transport, outbound, TLS client, and build-tag stubs from
shtorm-7/sing-box-extended (c307b8d + b953954 refactor), without admin
panel, provider, parser, or clash changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Wire merged upstream and extended-fork protocol work into the existing
adapter surface without breaking core interfaces.

- MASQUE: cloudflare EnrollKey/GetProfile4471, cache via StoreMASQUEConfig
  and LoadBinary/SaveBinary, connect-ip-go replace, masque_client TLS init
- VLESS encryption: NewUpstreamContextHandler, tls.NewClient logger arg,
  starifly/sing-vmess replace for vision/canSplice APIs
- Restore DNSTTOptions in v2ray transport and upstream xhttp option fixes

Co-authored-by: Cursor <cursoragent@cursor.com>
Add Snell and shadowsocks simple-obfs examples, fix configs that failed
sing-box check, and add examples/check.sh to validate all JSON examples
(with_gvisor,with_wireguard,with_awg,with_masque build tags).

Co-authored-by: Cursor <cursoragent@cursor.com>
These nested submodules have no entry in .gitmodules, breaking recursive
submodule checkout. They are the native GUI client apps, not needed for
the Go core build. Removed so hiddify-core CI can clone recursively.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: remove clients/android + clients/apple gitlinks (broken submodules)
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.
- 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>
…ctor

clientKey → key to match merged Endpoint struct field name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
….Once

init() with panic is disallowed per project conventions. Replace with
lazy initialization via sync.Once on first use; initialization errors
are propagated to callers rather than crashing the process.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the single `Endpoint` struct (with `if srv != nil` branching)
with a clean inbound/outbound separation following the mieru/anytls
pattern:

- `inbound.go`  — `Inbound` struct embedding `inbound.Adapter`; accept
  loop discovers Firebase sessions and routes each via
  `router.RouteConnectionEx` after setting `metadata.User`; sing-box
  handles traffic accounting automatically — no SSMTracker needed.
- `outbound.go` — `Outbound` struct embedding `outbound.Adapter`;
  `DialContext` creates sessions and relays data through Firebase.
- `common.go`   — shared constants and helper functions.

`loopWithRestart` replaces `runWithRecovery`: loop function returns
`error`; the outer loop restarts on non-nil error without using
`recover()` or `panic`.

Option structs split into `FirebaseTunnelInboundOptions` and
`FirebaseTunnelOutboundOptions`. Type constants renamed to
`TypeFirebaseTunnelInbound` / `TypeFirebaseTunnelOutbound`.
`include/registry.go` updated to call `RegisterInbound` / `RegisterOutbound`
instead of the old endpoint registrations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@hiddifydeveloper hiddifydeveloper changed the title feat(endpoint): add firebasetunnel_client / firebasetunnel_server endpoint pair feat(firebasetunnel): port Firebase Tunnel as proper inbound/outbound pair Jul 11, 2026
@hiddify-com hiddify-com changed the base branch from testing-merge-base to v5 July 14, 2026 13:23
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.

9 participants