GoSuda

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

By Lemon Mint views ...

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:

EgenskapObserverat värde
Förhandlat protokollh2
JA4t13d1516h2_8daaf6152771_806a8c22fdea
HTTP/2-fingeravtryck (Akamai-format)1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p
User-AgentChrome 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.Client gör båda, precis som med http.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ältEffekt
UserAgentErsätter den standardmässiga Chrome User-Agent. En User-Agent-förfrågningshuvud har fortfarande företräde.
AcceptLanguageErsätter en-US,en;q=0.9.
ProxyChromiums proxyregler, till exempel http://127.0.0.1:8080 eller socks5://127.0.0.1:1080. Tomt värde innebär direkt anslutning.
DisableHTTP2, DisableQUICStänger av protokollen.
ForceHTTP3Använder QUIC för alla origins utan att vänta på en Alt-Svc-annonsering.
InsecureSkipVerifyAccepterar alla servercertifikat.
PoolMotorsessioner 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

LagerVad som körs där
Ditt programhttp.Client och chromenet.Transport, ren Go.
ProcessgränsHQQ shared-memory RPC. Gränssnittet är chrome_net.idl, som kompileras av hqq-bindgen till Go-klienten och C-serverlimmet.
chrome_net_serverEtt 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 //netURLRequestContext, 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.

FastlagtVar
Chromium-version och dess officiella -lite-källarkivthird_party/chromium/VERSION
SHA-256 för arkivet och för varje verktygskedja som versionen fastlägger: clang, Rust, GN, ninja, Windows clang-runtime, xwinthird_party/chromium/SHA256
MSVC CRT- och Windows SDK-versioner som hämtas via xwinthird_party/chromium/WINSDK
Versionen av sandbox-körmiljön (rungv), gästavbildningens digest och Debian-snapshotdatumetscripts/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.

PlattformBinärstorlek
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-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 chromenet fö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.