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

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:
| Egenskab | Observeret værdi |
|---|---|
| Forhandlet protokol | h2 |
| JA4 | t13d1516h2_8daaf6152771_806a8c22fdea |
| HTTP/2-fingeraftryk (Akamai-format) | 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p |
| User-Agent | desktop 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.Clientgør begge dele, ligesom medhttp.Transport. - Svar komprimeret med gzip, deflate, Brotli eller zstd ankommer afkodet, og
resp.Uncompresseder 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
| Felt | Effekt |
|---|---|
UserAgent | Erstatter standard-Chrome-User-Agent. En User-Agent-requestheader har stadig forrang. |
AcceptLanguage | Erstatter en-US,en;q=0.9. |
Proxy | Chromium-proxyregler såsom http://127.0.0.1:8080 eller socks5://127.0.0.1:1080. Tom betyder direkte. |
DisableHTTP2, DisableQUIC | Slår protokollerne fra. |
ForceHTTP3 | Bruger QUIC for alle origins uden at vente på en Alt-Svc-annoncering. |
InsecureSkipVerify | Accepterer ethvert servercertifikat. |
Pool | Motorsessioner, 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
| Lag | Hvad der kører der |
|---|---|
| Dit program | http.Client og chromenet.Transport, rent Go. |
| Procesgrænse | HQQ shared-memory RPC. Interfacet er chrome_net.idl, kompileret af hqq-bindgen til Go-klienten og C-serverglue. |
chrome_net_server | Et 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 //net | URLRequestContext, 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åsning | Hvor |
|---|---|
Chromium-udgivelsen og dens officielle -lite-kildearkiv | third_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, xwin | third_party/chromium/SHA256 |
| MSVC CRT- og Windows SDK-versioner hentet via xwin | third_party/chromium/WINSDK |
Sandbox-runtime (rungv)-udgivelsen, gæstebilledets digest, Debian-snapshotdatoen | scripts/engine/BUILDENV |
| HQQ C-runtime | go.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.
| Platform | Binær størrelse |
|---|---|
linux/amd64 | 27.9 MB |
linux/arm64 | 27.4 MB |
darwin/amd64 | 26.2 MB |
darwin/arm64 | 23.9 MB |
windows/amd64 | 27.3 MB |
windows/arm64 | 25.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
DialWebSocketer en ren Go-RFC 6455-klient og bruger ikke Chromium-stacken.DialWebTransporter 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
chromenetfø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.