How mhr-cfw-go Uses Go to Route Traffic Through Domain Fronting
mhr-cfw-go is a local proxy written in Go that forwards your traffic through Google’s infrastructure using domain fronting. It sits on your machine, intercepts HTTP and HTTPS connections, and rewrites them so the SNI field shows a benign Google domain while the encrypted request is routed to a Google Apps Script endpoint. The goal: dodge deep packet inspection systems that block traffic based on SNI or DNS.
What makes this project worth reading as a Go developer is the sheer number of non-trivial networking pieces wired together. MITM TLS proxying, HTTP/2 transport customization, runtime certificate generation, concurrent IP scanning. And somehow the internal package layout keeps it all readable.
Project structure and entry point
The entry point lives at cmd/mhr-cfw/main.go. From there, the application delegates to an interactive setup wizard at internal/setup/wizard.go and a TUI menu at internal/tui/menu.go. Configuration is handled through internal/config/config.go, which reads and writes a local config file storing the selected CDN IP, SNI hostname, mode, and Apps Script ID.
The actual networking happens across a handful of internal packages: internal/proxy/proxy_server.go runs the local proxy listener, internal/mitm/mitm.go handles man-in-the-middle TLS interception, internal/fronter/fronter.go manages the domain-fronting logic, and internal/h2/h2_transport.go sets up custom HTTP/2 transports.
How the domain fronting works in Go
Domain fronting exploits a gap between two layers of a TLS+HTTP request. The TLS handshake includes an SNI field that network middleboxes can see. The HTTP Host header, encrypted inside the TLS tunnel, tells the actual server where to route the request. These two values don’t have to match. That’s the whole trick.
internal/fronter/fronter.go is where this happens. The proxy connects to a Google CDN IP with a TLS ServerName set to something like a legitimate Google domain. DPI systems see the SNI and think you’re visiting Google. But the HTTP request inside that tunnel is aimed at script.google.com, using the configured Apps Script ID in a /macros/s/<script_id>/exec URL. That script-side endpoint forwards the traffic to the real destination.
In Go, this means configuring a tls.Config with a specific ServerName and dialing a specific IP rather than resolving DNS. The crypto/tls package makes this almost too easy — you set the fields on the config struct and pass it to a dialer. The interesting part is combining this with HTTP/2.
Custom HTTP/2 transport
internal/h2/h2_transport.go sets up a custom http2.Transport from Go’s golang.org/x/net/http2 package. This is necessary because the standard http.Transport negotiates HTTP/2 automatically over TLS, but mhr-cfw-go needs direct control over the TLS connection parameters, specifically the SNI and the target IP.
When you use Go’s http2.Transport directly, you bypass the automatic ALPN negotiation that net/http does. You supply your own DialTLS function that establishes the connection to the CDN IP with the fronted SNI. This is a pattern worth knowing if you ever need to control TLS parameters at the transport level:
// Generic pattern for custom HTTP/2 TLS dialing in Go
transport := &http2.Transport{
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
conn, err := tls.Dial(network, cdnIP+":443", &tls.Config{
ServerName: frontedDomain,
NextProtos: []string{"h2"},
})
return conn, err
},
}
The NextProtos: []string{"h2"} part is the one that’ll bite you if you forget it. It tells the TLS handshake to negotiate HTTP/2 via ALPN. Without it, the connection falls back to HTTP/1.1 and the whole transport setup silently breaks.
If you’ve worked with Go’s HTTP stack before, you might know that context plays a big role in managing request lifetimes through these transports.
MITM TLS proxy for HTTPS traffic
For the proxy to intercept HTTPS traffic, it has to perform a man-in-the-middle attack on TLS connections from the local machine. internal/mitm/mitm.go handles this.
Here’s the flow:
- The local browser sends a
CONNECTrequest to the proxy. - The proxy generates a TLS certificate for the requested domain on the fly.
- The proxy completes a TLS handshake with the browser using that certificate.
- It decrypts the request, rewrites it through the domain-fronting transport, and sends the response back.
Certificate generation happens in internal/cert/installer.go. The proxy generates a root CA certificate and installs it into the system trust store. Then for each incoming HTTPS connection, it dynamically creates a leaf certificate signed by that root CA using Go’s crypto/x509 and crypto/rsa packages.
Runtime certificate generation is well-documented in Go. The standard library gives you everything:
// Generating a self-signed CA certificate in Go
caTemplate := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "mhr-cfw-go CA"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
}
caKey, _ := rsa.GenerateKey(rand.Reader, 2048)
caCertDER, _ := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey)
The internal/cert/installer.go file also deals with OS-specific trust store installation. On Linux, that means writing the PEM file into /usr/local/share/ca-certificates/ and running update-ca-certificates, as documented in the project’s Linux/README.md.
IP scanning with goroutines
internal/scanner/scanner.go implements an IP scanner that finds working Google CDN IPs. It probes a range of addresses to find ones that accept TLS connections with the fronted SNI.
This is a textbook use case for Go’s concurrency model. The scanner fans out goroutines to test IPs in parallel, collecting results through channels. Standard stuff for Go network tools. If you’re interested in how Go handles configurable concurrent code, functional options are another pattern that shows up often in this kind of networking work.
The scanning matters because not all Google CDN IPs work for domain fronting. Some are geographically closer, some are blocked, some don’t support the right TLS configuration. The scanner automates finding one that works so you don’t have to guess.
The codec layer
internal/codec/codec.go handles encoding and decoding traffic between the local proxy and the remote Apps Script endpoint. The remote side needs to know the original destination, so the proxy encodes this metadata into the request. The codec package handles the serialization format for this communication.
Patterns worth borrowing
A few things from this project that transfer well to other Go work:
Go’s crypto/tls package lets you control every aspect of a TLS connection. When you need to connect to one IP but present a different SNI, you set ServerName on the tls.Config and dial the IP directly. This isn’t just useful for VPN-like tools. Any Go HTTP client that needs certificate pinning or custom TLS behavior uses this same approach.
The crypto/x509 package is powerful enough to build a full CA and issue certificates at runtime. If you’re building any kind of proxy, testing tool, or mTLS infrastructure in Go, this is the package to learn well.
And if you’ve only ever used http.DefaultTransport, spend some time with http2.Transport. It gives you direct control over how HTTP/2 connections are established, which matters the moment you need specific TLS or connection behavior. Understanding how Go handles HTTP at this level pays off repeatedly.
Reading order for the source
mhr-cfw-go is compact but technically dense. MITM proxying, domain fronting, HTTP/2 transport customization, concurrent IP scanning, all in Go, all using the standard library or x/net. The clean separation into internal packages (mitm, fronter, h2, scanner, cert, codec) means you can read each piece on its own without getting lost in the rest. I’d start with fronter.go and h2_transport.go to understand the core trick, then work outward from there.