Skip to content

Initial implementation: clean-room domain fronting library#1

Merged
myleshorton merged 3 commits into
mainfrom
initial-implementation
Mar 20, 2026
Merged

Initial implementation: clean-room domain fronting library#1
myleshorton merged 3 commits into
mainfrom
initial-implementation

Conversation

@myleshorton

Copy link
Copy Markdown
Contributor

Summary

Clean-room rewrite of github.com/getlantern/fronted as a new standalone package with significant architectural improvements:

  • No global state — all state on *Client, logger injected via option
  • context.Context everywhere — single context controls all goroutines; Close() cancels it
  • Atomic config swapsfrontPool.Replace() swaps candidates while preserving state from previously-working fronts (no unbounded append)
  • Check provider mapping before dialing — no wasted TLS connections
  • Channel-based ready queue — simple select in Take(), no sync.Cond or goroutine-per-call
  • Minimal deps — only utls + go-yaml (eliminated keepcurrent, pond, ops)
  • Fully testableDialer and Cache interfaces; pipe-based mock TLS servers, no real CDN hits

File layout

File Purpose
domainfront.go Client, New(), options, background goroutines
config.go Config/Provider/Masquerade/CA types, YAML parsing
front.go front type, frontPool (Take/Return/Replace)
dialer.go TLS dialing with utls, cert verification
roundtrip.go RoundTripper, request rewriting, retry logic
sni.go Deterministic SNI generation from IP hash
cache.go Cache interface, FileCache (JSON), NopCache
fileutil.go File read/write helpers

Accepts the same config format

The library reads the same fronted.yaml.gz (gzipped YAML with trustedcas and providers sections) used by the existing fronted package — drop-in config compatibility.

Test plan

  • 22 unit tests passing, stable across 5 runs
  • Config parsing (YAML, gzip, provider expansion, SNI generation)
  • FrontPool lifecycle (Take/Return/Replace/Close/Candidates)
  • Cache load/save/apply with staleness checking
  • TLS cert verification with generated CA/leaf certs
  • TLS dial via pipe-based mock server (no real network)
  • Full Client lifecycle with mock CDN (HTTP server + TLS)
  • Unmapped host error handling
  • Clean Close() without hanging
  • Integration test with real CloudFront/Akamai (future, build-tagged)

🤖 Generated with Claude Code

Clean-room rewrite of github.com/getlantern/fronted with architectural
improvements: no global state, context-driven lifecycle, atomic config
swaps, channel-based ready queue, and minimal dependencies (utls +
go-yaml only).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new standalone Go module (github.com/getlantern/domainfront) implementing a clean-room domain fronting client library, including config parsing (gzipped YAML), front candidate pooling, uTLS-based TLS dialing/cert verification, an HTTP RoundTripper, caching, and supporting unit tests plus README documentation.

Changes:

  • Added Client lifecycle with background crawler, cache saver, and optional remote config updater.
  • Implemented config model + parsing/expansion (host alias lookup, passthrough patterns, per-country SNI selection).
  • Added front pool (Take/Return/Replace), uTLS dialing/cert verification, request rewriting/round-trip logic, and JSON file cache; included unit tests and README.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
domainfront.go Core Client, options, crawler, cache saver, config updater, config helpers
config.go Config/Provider/Masquerade types, gzip+YAML parsing, provider expansion and lookup
front.go Front candidate state + pool implementation (Replace/Take/Return/candidates)
dialer.go Dialer abstraction + uTLS-based TLS dialing and certificate verification
roundtrip.go http.RoundTripper implementation, request rewriting, connection transport wrapper
sni.go Deterministic SNI selection from arbitrary list using hash
cache.go Cache interface + file-backed JSON cache + cached-state application
fileutil.go File read/write helpers used by FileCache
README.md Package documentation, usage, options, architecture notes
*_test.go Unit tests for config, SNI, dialer/cert verification, pool behavior, and client lifecycle
go.mod / go.sum New module definition and dependency lockfiles

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread roundtrip.go Outdated
Comment thread domainfront.go Outdated
Comment thread domainfront.go
Comment thread domainfront.go
Comment thread domainfront.go Outdated
Comment thread roundtrip.go Outdated
Comment thread roundtrip.go
Comment thread front.go Outdated
Comment thread config.go Outdated
myleshorton and others added 2 commits March 19, 2026 20:27
- Split Return/ReturnSuccess: Return(requeue) no longer updates
  LastSucceeded; only ReturnSuccess marks a real success. Dial failures
  always call Return(false).
- applyConfig returns error: New() fails fast if config has no providers
  or malformed CA certs. CertPool() now returns (*x509.CertPool, error).
- Clamp crawlerConcurrency >= 1 to prevent zero-capacity semaphore deadlock.
- fetchAndApplyConfig uses http.NewRequestWithContext(c.ctx) so shutdown
  cancels in-flight config fetches.
- ParseConfigFromReader limits input to maxConfigSize (50 MB).
- rewriteRequest sets r.Host = originHost so the CDN sees the real
  destination in the Host header, not the fronted domain.
- Request body buffered for retries via GetBody or io.ReadAll fallback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Runs build and tests with -race on every push to main and PR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@myleshorton myleshorton merged commit 894d8c6 into main Mar 20, 2026
1 check passed
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.

2 participants