gosuda.org/chromenet: Chromiums nätverksstack för Go, byggd reproducerbart från en fastlagd version

gosuda.org/chromenet är en Go-implementation av http.RoundTripper som skickar förfrågningar genom Chromiums egen nätverksstack. Stacken är //net från en officiell Chromium-version: dess implementationer av HTTP/1.1, HTTP/2 och QUIC/HTTP/3, BoringSSL, Chromes TLS-konfiguration och Chrome Root Store. Den körs i en separat arbetsprocess, chrome_net_server, och Go-sidan förblir ren Go (CGO_ENABLED=0) och kommunicerar med arbetsprocessen via HQQ shared-memory RPC.
1go get gosuda.org/chromenet@v0.1.10157081033800
Varför köra Chromiums nätverksstack
Ett Go-program som kommunicerar med webben via net/http framstår för servern som en Go-klient. Det är ett rimligt standardval för tjänst-till-tjänst-trafik. Det är däremot ett olämpligt val när serverns beteende beror på vilken typ av klient den tror sig kommunicera med: webbläsarspärrade slutpunkter, middleware mot botar, CDN:er som levererar olika protokollversioner till olika TLS- och HTTP/2-fingeravtryck, samt tester som måste återskapa vad en verklig webbläsare upplever.
Go:s standardbibliotek kan inte återge en webbläsares beteende på nätverksnivå. crypto/tls har en egen ordning för chiffersviter och en egen uppsättning tillägg, och net/http har egna HTTP/2-inställningar. Bibliotek som efterliknar en webbläsare genom att kopiera dess ClientHello-byte måste fortsätta kopiera, version efter version. chromenet tar en annan väg: den efterliknar inte Chromiums nätverksstack utan kör den.
Vid mätning på darwin/arm64-motorn mot en offentlig fingeravtryckstjänst (tls.peet.ws) rapporterar standardkonfigurationen följande:
| Egenskap | Observerat värde |
|---|---|
| Förhandlat protokoll | h2 |
| JA4 | t13d1516h2_8daaf6152771_806a8c22fdea |
| HTTP/2-fingeravtryck (Akamai-format) | 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p |
| User-Agent | Chrome för skrivbordet med motorns huvudversion, t.ex. Chrome/157.0.0.0 |
Med ForceHTTP3 besvarades förfrågningar till cloudflare-quic.com och www.google.com över HTTP/3. Dessa värden kommer från en plattform vid ett tillfälle. Motorn är själva stacken, så värdena ändras när den fastlagda Chromium-versionen ändras.
Användning
NewTransport returnerar en http.RoundTripper. Använd den överallt där du annars skulle använda 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}
Vid den första förfrågan skriver modulen den inbäddade arbetsbinären för din plattform till användarens cachekatalog, verifierar dess SHA-256-digest med gosuda.org/hqq/secureexec, startar den med en överordnad livlina (--parent-stdin, så att den avslutas när ditt program avslutas) och öppnar en sessionspool. Ingenting behöver installeras.
Några beteenden följer http.Transport så att http.Client fungerar oförändrat:
- Transporten följer inte omdirigeringar och lagrar inga cookies.
http.Clientgör båda, precis som medhttp.Transport. - Svar komprimerade med gzip, deflate, Brotli eller zstd levereras avkodade, och
resp.Uncompressedär true. - En förfrågan håller en motorsession tills dess svarskropp stängs. Stäng kropparna.
- Att avbryta förfrågans kontext avbryter förfrågan inne i Chromium.
- Nätverksfel är
*client.NetError-värden som innehåller Chromiums felkod och felnamn:
1var ne *client.NetError
2if errors.As(err, &ne) {
3 fmt.Println(ne.Code, ne.Name) // -105 ERR_NAME_NOT_RESOLVED
4}
Alternativ
| Fält | Effekt |
|---|---|
UserAgent | Ersätter den standardmässiga Chrome User-Agent. En User-Agent-förfrågningshuvud har fortfarande företräde. |
AcceptLanguage | Ersätter en-US,en;q=0.9. |
Proxy | Chromiums proxyregler, till exempel http://127.0.0.1:8080 eller socks5://127.0.0.1:1080. Tomt värde innebär direkt anslutning. |
DisableHTTP2, DisableQUIC | Stänger av protokollen. |
ForceHTTP3 | Använder QUIC för alla origins utan att vänta på en Alt-Svc-annonsering. |
InsecureSkipVerify | Accepterar alla servercertifikat. |
Pool | Motorsessioner som ska användas. Nil använder client.DefaultPool(), som tillåter 64 samtidiga förfrågningar. |
Transporter med lika alternativ delar en Chromium-URLRequestContext, så att anslutningspooler, HTTP/2-sessioner och QUIC-sessioner återanvänds mellan förfrågningar. Genom att ange HQQ_CHROME_NET_SERVER=/path/to/chrome_net_server ersätts den inbäddade arbetsbinären, till exempel med en lokal build.
Så fungerar det
| Lager | Vad som körs där |
|---|---|
| Ditt program | http.Client och chromenet.Transport, ren Go. |
| Processgräns | HQQ shared-memory RPC. Gränssnittet är chrome_net.idl, som kompileras av hqq-bindgen till Go-klienten och C-serverlimmet. |
chrome_net_server | Ett litet C++-lager ovanpå Chromium: en nätverkstråd som äger alla //net-objekt, och sessionstrådar som blockerar på en förfrågan tills nätverkstråden svarar. |
Chromium //net | URLRequestContext, URLRequest, HTTP/2- och QUIC-sessioner, BoringSSL. |
IDL:en har sex operationer som är viktiga: skapa en motor från key=value-alternativ, starta en förfrågan med dess huvud (METHOD URL plus huvudrader) och kroppens längd, skriva delar av förfrågningskroppen, vänta på svarshuvudet, läsa den avkodade svarskroppen och förstöra handtagen. Ett förfrågningshandtag deklarerar @cancel, så att ett övergivet anrop avbryter förfrågan inne i Chromium i stället för att låta den köras till slut.
Att isolera stacken i en arbetsprocess är lika mycket en säkerhetsegenskap som en paketeringsfråga. Chromiums nätverkskod tolkar fientlig indata från det öppna internet. Om den kraschar kraschar inte din Go-tjänst med: anropet misslyckas med ett fel, sessionen kasseras och klienten startar en ny arbetsprocess nästa gång den behövs.
Reproducerbart byggd, fastlagd med SHA-256
En 25 MB stor binär som innehåller Chromium är bara tillförlitlig om du exakt kan ange vad som ingick i den. chromenet följer Chromium Stable på samma sätt som gosuda.org/sqlite3 följer SQLite: källkoden vendoreras aldrig, den bara fastlags.
| Fastlagt | Var |
|---|---|
Chromium-version och dess officiella -lite-källarkiv | third_party/chromium/VERSION |
| SHA-256 för arkivet och för varje verktygskedja som versionen fastlägger: clang, Rust, GN, ninja, Windows clang-runtime, xwin | third_party/chromium/SHA256 |
| MSVC CRT- och Windows SDK-versioner som hämtas via xwin | third_party/chromium/WINSDK |
Versionen av sandbox-körmiljön (rungv), gästavbildningens digest och Debian-snapshotdatumet | scripts/engine/BUILDENV |
| HQQ:s C-körmiljö | go.mod och go.sum |
Den aktuella fastläggningen är Chromium 157.0.8103.38. En nedladdning som inte stämmer med sin fastläggning stoppar bygget. Versionen tillämpar exakt en patch på Chromium: på Linux byggs chrome_net_server utan NSS, och servercertifikat verifieras enbart mot Chrome Root Store, så att binären inte behöver något utöver libc.
Linux- och Windows-motorerna byggs inuti gvx-sandlådor: processer utan privilegier, med en fastlagd gästavbildning och ingen värddaemon. Windows-motorerna korskompileras på Linux med Chromiums clang-cl och lld-link mot Windows SDK som xwin hämtar från Microsoft. macOS-motorerna byggs nativt på en macOS-runner, eftersom Chromiums macOS-build kräver Xcode.
Ombyggnad av samma commit gav bitidentiska binärer över separata CI-körningar. Releasebinärerna är bitidentiska med de som en tidigare CI-körning byggde för den commit som införde motorn i monorepot, på alla sex plattformar.
| Plattform | Binärstorlek |
|---|---|
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-motorerna beror enbart på libc, libm, libdl, libpthread och libgcc_s, och kräver glibc 2.31 eller nyare.
Följa Chromium och releaser
Ett schemalagt arbetsflöde kontrollerar Chromium Stable var sjätte timme. För en ny release skriver det om fastläggningarna, bygger och testar alla sex motorer på en gren chromium/<version>, attesterar binärerna och committar fastläggningarna tillsammans med de inbäddade binärerna, deras digester och licenstexterna till main i en enda commit. I monorepot ger filvisa OWNERS-filer arbetsflödets bot exakt dessa filer, så uppdateringen landar utan ett manuellt steg. Ändringar av själva byggreceptet (Windows SDK-fastläggningen och patchen) kräver fortfarande en ägare.
Varje releasebinär bär en byggprovenienattestation från arbetsflödet som byggde den:
1gh attestation verify chrome_net_server_linux_amd64 --repo gosuda/chromenet
Licenstexterna för vad varje binär innehåller (Chromium, BoringSSL, QUICHE och resten av byggkedjan) genereras av Chromiums egna tools/licenses/licenses.py och levereras med releasen som NOTICE_<os>_<arch>.txt.
Begränsningar
DialWebSocketär en ren Go-klient för RFC 6455 och använder inte Chromium-stacken.DialWebTransportär en implementation i samma process. Den når inte en server.- Standardfingeravtrycken för JA4 och HTTP/2 är de för den fastlagda Chromium-versionen. Chromium ändrar dem från version till version, och
chromenetföljer efter. - macOS-binärerna är ad-hoc-signerade och inte notariserade.
- En arbetsprocess kostar minne. Varje motorbinär är 24 till 28 MB på disk, och Chromiums körmiljö startar en trådpool och en nätverkstråd.
- En motorsession hålls tills svarskroppen stängs. Ett program som läcker svarskroppar kommer att tömma poolen.
Källa
Repositoryt finns på github.com/gosuda/chromenet och speglas från //net/chromenet i gosuda-monorepot. Byggreceptet finns i scripts/engine, motorlimmet i engine/ och gränssnittet i chrome_net.idl.