GoSuda

gosuda.org/chromenet: Chromiums netværksstak til Go, bygget reproducerbart fra en fastlåst udgivelse

By Lemon Mint views ...

gosuda.org/chromenet er en Go http.RoundTripper, der sender forespørgsler gennem Chromiums egen netværksstack. Stacken er //net fra en officiel Chromium-udgivelse: dens HTTP/1.1-, HTTP/2- og QUIC/HTTP/3-implementeringer, BoringSSL, Chromes TLS-konfiguration og Chrome Root Store. Den kører i en separat workerproces, chrome_net_server, og Go-siden forbliver rent Go (CGO_ENABLED=0), idet den kommunikerer med workeren via HQQ shared-memory RPC.

1go get gosuda.org/chromenet@v0.1.10157081033800

Hvorfor køre Chromiums netværksstack

Et Go-program, der kommunikerer med nettet via net/http, præsenterer en Go-klient over for serveren. Det er den rigtige standard for tjeneste-til-tjeneste-trafik. Det er den forkerte, når serverens adfærd afhænger af, hvilken type klient den tror, den kommunikerer med: browserbegrænsede endepunkter, anti-bot-middleware, CDN'er, der leverer forskellige protokolversioner til forskellige TLS- og HTTP/2-fingeraftryk, og tests, der skal reproducere, hvad en rigtig browser oplever.

Go's standardbibliotek kan ikke give dig en browsers wire-adfærd. crypto/tls har sin egen rækkefølge af cipher-suites og sit eget sæt af udvidelser, og net/http har sine egne HTTP/2-indstillinger. Biblioteker, der efterligner en browser ved at kopiere dens ClientHello-bytes, bliver ved med at kopiere, udgivelse efter udgivelse. chromenet går den anden vej: den efterligner ikke Chromium-netværksstacken, den kører den.

Målt på darwin/arm64-motoren mod en offentlig fingeraftryks-tjeneste (tls.peet.ws) rapporterer standardkonfigurationen:

EgenskabObserveret værdi
Forhandlet protokolh2
JA4t13d1516h2_8daaf6152771_806a8c22fdea
HTTP/2-fingeraftryk (Akamai-format)1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p
User-Agentdesktop Chrome i motorens hovedversion, f.eks. Chrome/157.0.0.0

Med ForceHTTP3 blev forespørgsler til cloudflare-quic.com og www.google.com besvaret over HTTP/3. Disse tal stammer fra én platform på én dag. Motoren er stacken, så de ændrer sig, når den fastlagte Chromium-udgivelse ændres.

Brug af pakken

NewTransport returnerer en http.RoundTripper. Brug den overalt, hvor du ville bruge http.Transport:

 1package main
 2
 3import (
 4	"fmt"
 5	"io"
 6	"net/http"
 7
 8	"gosuda.org/chromenet"
 9)
10
11func main() {
12	client := &http.Client{Transport: chromenet.NewTransport(chromenet.Options{})}
13
14	resp, err := client.Get("https://example.com/")
15	if err != nil {
16		panic(err)
17	}
18	defer resp.Body.Close()
19
20	body, _ := io.ReadAll(resp.Body)
21	fmt.Println(resp.Proto, resp.StatusCode, len(body))
22}

Ved den første forespørgsel skriver modulet den indlejrede workerbinær til din platform i brugerens cache-mappe, verificerer dens SHA-256-digest med gosuda.org/hqq/secureexec, starter den med en forælderlivline (--parent-stdin, så den afslutter, når dit program afslutter), og åbner en sessionspool. Intet behøver at blive installeret.

Nogle adfærdsmønstre følger http.Transport, så http.Client virker uændret:

  • Transporten følger ikke omdirigeringer og gemmer ingen cookies. http.Client gør begge dele, ligesom med http.Transport.
  • Svar komprimeret med gzip, deflate, Brotli eller zstd ankommer afkodet, og resp.Uncompressed er true.
  • En forespørgsel holder én motorsession, indtil svarets body lukkes. Luk bodies.
  • Annullering af forespørgslens context annullerer forespørgslen inde i Chromium.
  • Netværksfejl er *client.NetError-værdier, der indeholder Chromium-fejlkoden og -navnet:
1var ne *client.NetError
2if errors.As(err, &ne) {
3	fmt.Println(ne.Code, ne.Name) // -105 ERR_NAME_NOT_RESOLVED
4}

Indstillinger

FeltEffekt
UserAgentErstatter standard-Chrome-User-Agent. En User-Agent-requestheader har stadig forrang.
AcceptLanguageErstatter en-US,en;q=0.9.
ProxyChromium-proxyregler såsom http://127.0.0.1:8080 eller socks5://127.0.0.1:1080. Tom betyder direkte.
DisableHTTP2, DisableQUICSlår protokollerne fra.
ForceHTTP3Bruger QUIC for alle origins uden at vente på en Alt-Svc-annoncering.
InsecureSkipVerifyAccepterer ethvert servercertifikat.
PoolMotorsessioner, der skal bruges. Nil bruger client.DefaultPool(), som tillader 64 samtidige forespørgsler.

Transporter med ens indstillinger deler én Chromium URLRequestContext, så forbindelsespuljer, HTTP/2-sessioner og QUIC-sessioner genbruges på tværs af forespørgsler. Sætter du HQQ_CHROME_NET_SERVER=/path/to/chrome_net_server, erstattes den indlejrede worker, for eksempel med en lokal build.

Hvordan det virker

LagHvad der kører der
Dit programhttp.Client og chromenet.Transport, rent Go.
ProcesgrænseHQQ shared-memory RPC. Interfacet er chrome_net.idl, kompileret af hqq-bindgen til Go-klienten og C-serverglue.
chrome_net_serverEt lille C++-lag oven på Chromium: én netværkstråd, der ejer alle //net-objekter, og sessiontråde, der blokerer på en forespørgsel, indtil netværkstråden svarer.
Chromium //netURLRequestContext, URLRequest, HTTP/2- og QUIC-sessioner, BoringSSL.

IDL'en har seks operationer, der betyder noget: oprette en motor ud fra key=value-indstillinger, starte en forespørgsel med dens hoved (METHOD URL plus headerlinjer) og body-længde, skrive chunks af request-body, vente på svarets hoved, læse den afkodede body og destruere handles. En requesthandle erklærer @cancel, så et opgivet kald afbryder forespørgslen inde i Chromium i stedet for at lade den køre til ende.

At isolere stacken i en workerproces er lige så meget en sikkerhedsegenskab som en pakningsmæssig. Chromiums netværkskode parser fjendtligt input fra det åbne internet. Hvis den crasher, crasher din Go-tjeneste ikke med: kaldet fejler med en fejl, sessionen kasseres, og klienten starter en ny worker, næste gang den får brug for en.

Reproducerbart bygget, fastlåst via SHA-256

En 25 MB binær fil, der indeholder Chromium, er kun til at stole på, hvis du præcist kan sige, hvad der er gået ind i den. chromenet følger Chromium Stable, ligesom gosuda.org/sqlite3 følger SQLite: kildekoden vendoreres aldrig, den fastlåses kun.

FastlåsningHvor
Chromium-udgivelsen og dens officielle -lite-kildearkivthird_party/chromium/VERSION
SHA-256 for arkivet og for hver værktøjskæde, som udgivelsen fastlåser: clang, Rust, GN, ninja, Windows clang-runtime, xwinthird_party/chromium/SHA256
MSVC CRT- og Windows SDK-versioner hentet via xwinthird_party/chromium/WINSDK
Sandbox-runtime (rungv)-udgivelsen, gæstebilledets digest, Debian-snapshotdatoenscripts/engine/BUILDENV
HQQ C-runtimego.mod og go.sum

Den nuværende fastlåsning er Chromium 157.0.8103.38. En download, der ikke matcher sin fastlåsning, stopper bygget. Udgivelsen anvender præcis ét patch på Chromium: på Linux bygges chrome_net_server uden NSS, og servercertifikater verificeres kun mod Chrome Root Store, så binæren kræver intet ud over libc.

Linux- og Windows-motorerne bygges inde i gvx-sandkasser: uprivilegerede processer med et fastlåst gæstebillede og ingen daemon på værten. Windows-motorerne krydskompileres på Linux med Chromiums clang-cl og lld-link mod Windows SDK, som xwin henter fra Microsoft. macOS-motorerne bygges nativt på en macOS-runner, fordi Chromiums macOS-build kræver Xcode.

Genbygning af samme commit gav byte-identiske binære filer på tværs af separate CI-kørsler. Udgivelsesbinærerne er byte-identiske med dem, en tidligere CI-kørsel byggede for den commit, der indførte motoren i monorepoet, på alle seks platforme.

PlatformBinær størrelse
linux/amd6427.9 MB
linux/arm6427.4 MB
darwin/amd6426.2 MB
darwin/arm6423.9 MB
windows/amd6427.3 MB
windows/arm6425.8 MB

Linux-motorerne afhænger kun af libc, libm, libdl, libpthread og libgcc_s og kræver glibc 2.31 eller nyere.

Sporing af Chromium og udgivelser

En planlagt workflow kontrollerer Chromium Stable hver sjette time. For en ny udgivelse omskriver den fastlåsningerne, bygger og tester alle seks motorer på en chromium/<version>-gren, attesterer binærerne og committer fastlåsningerne sammen med de indlejrede binærer, deres digests og licensteksterne til main i én commit. I monorepoet giver per-fil OWNERS workflowets bot præcis disse filer, så opdateringen lander uden et menneskeligt trin, mens ændringer i selve byggeopskriften (Windows SDK-fastlåsningen og patchen) stadig kræver en ejer.

Hver udgivelsesbinær bærer en build provenance-attestation fra den workflow, der byggede den:

1gh attestation verify chrome_net_server_linux_amd64 --repo gosuda/chromenet

Licensteksterne for hvad hver binær indeholder (Chromium, BoringSSL, QUICHE og resten af byggegrafen) genereres af Chromiums egen tools/licenses/licenses.py og leveres med udgivelsen som NOTICE_<os>_<arch>.txt.

Begrænsninger

  • DialWebSocket er en ren Go-RFC 6455-klient og bruger ikke Chromium-stacken.
  • DialWebTransport er en in-process-implementering. Den når ikke en server.
  • De indbyggede JA4- og HTTP/2-fingeraftryk er dem fra den fastlåste Chromium-udgivelse. Chromium ændrer dem fra udgivelse til udgivelse, og chromenet følger med.
  • macOS-binærerne er ad-hoc-signerede og ikke notariserede.
  • En workerproces koster hukommelse. Hver motorbinær fylder 24 til 28 MB på disk, og Chromiums runtime starter en trådpulje og en netværkstråd.
  • En motorsession holdes, indtil svarets body lukkes. Et program, der lækker svar-bodies, vil udtømme puljen.

Kilde

Repositoryet er github.com/gosuda/chromenet, spejlet fra //net/chromenet i gosuda-monorepoet. Byggeopskriften ligger i scripts/engine, motorglue i engine/, og interfacet i chrome_net.idl.