gosuda.org/chromenet: Síťový zásobník Chromia pro Go, sestavený reprodukovatelným způsobem z pevně zafixovaného vydání

gosuda.org/chromenet je Go http.RoundTripper, který odesílá požadavky přes vlastní síťový zásobník Chromium. Tím zásobníkem je //net z oficiálního vydání Chromium: jeho implementace HTTP/1.1, HTTP/2 a QUIC/HTTP/3, BoringSSL, konfigurace TLS prohlížeče Chrome a Chrome Root Store. Běží v samostatném pracovním procesu chrome_net_server, zatímco část v Go zůstává čistě v Go (CGO_ENABLED=0) a s pracovním procesem komunikuje přes HQQ shared-memory RPC.
1go get gosuda.org/chromenet@v0.1.10157081033800
Proč spouštět síťový zásobník Chromium
Program v Go, který komunikuje s webem pomocí net/http, se vůči serveru prezentuje jako klient v Go. To je správný výchozí přístup pro komunikaci mezi službami. Je však nevhodný tam, kde chování serveru závisí na tom, za jaký typ klienta server klienta považuje: endpointy vyžadující prohlížeč, anti-bot middleware, CDN, které různým TLS a HTTP/2 otiskům posílají různé verze protokolů, a testy, které musejí reprodukovat to, co zažívá skutečný prohlížeč.
Standardní knihovna Go nemůže poskytnout drátové chování prohlížeče. crypto/tls má vlastní pořadí cipher suites a sadu rozšíření a net/http má vlastní nastavení HTTP/2. Knihovny, které napodobují prohlížeč kopírováním bajtů jeho ClientHello, je musejí kopírovat znovu při každém vydání. chromenet jde opačnou cestou: nenapodobuje síťový zásobník Chromium, ale přímo jej spouští.
Měřeno na enginu darwin/arm64 proti veřejné službě pro fingerprinting (tls.peet.ws) výchozí konfigurace hlásí následující hodnoty:
| Vlastnost | Naměřená hodnota |
|---|---|
| Vyjednaný protokol | h2 |
| JA4 | t13d1516h2_8daaf6152771_806a8c22fdea |
| HTTP/2 fingerprint (formát Akamai) | 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p |
| User-Agent | desktopový Chrome s hlavní verzí enginu, např. Chrome/157.0.0.0 |
S ForceHTTP3 byly požadavky na cloudflare-quic.com a www.google.com zodpovězeny přes HTTP/3. Tyto hodnoty pochází z jedné platformy a z jednoho dne. Engine je samotný zásobník, takže se hodnoty změní, když se změní připnuté vydání Chromium.
Použití
NewTransport vrací http.RoundTripper. Používejte jej všude, kde byste použili 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}
Při prvním požadavku modul zapíše vloženou binárku pracovního procesu pro vaši platformu do uživatelského cache adresáře, ověří její SHA-256 digest pomocí gosuda.org/hqq/secureexec, spustí ji s rodičovskou smyčkou života (--parent-stdin, takže skončí, když skončí váš proces) a otevře session pool. Nic není třeba instalovat.
Některé vlastnosti odpovídají http.Transport, aby http.Client fungoval beze změn:
- Transport nesleduje přesměrování a neukládá cookies.
http.Clientobojí dělá stejně jako uhttp.Transport. - Odpovědi komprimované pomocí gzip, deflate, Brotli nebo zstd přicházejí dekódované a
resp.Uncompressedje true. - Požadavek drží jednu session enginu, dokud není uzavřeno tělo odpovědi. Těla zavírejte.
- Zrušení kontextu požadavku zruší požadavek uvnitř Chromium.
- Síťová selhání jsou hodnoty
*client.NetErrornesoucí kód a název chyby Chromium:
1var ne *client.NetError
2if errors.As(err, &ne) {
3 fmt.Println(ne.Code, ne.Name) // -105 ERR_NAME_NOT_RESOLVED
4}
Možnosti
| Pole | Účinek |
|---|---|
UserAgent | Nahradí výchozí User-Agent Chrome. Hlavička User-Agent v požadavku má stále přednost. |
AcceptLanguage | Nahradí en-US,en;q=0.9. |
Proxy | Pravidla proxy Chromium, např. http://127.0.0.1:8080 nebo socks5://127.0.0.1:1080. Prázdná hodnota znamená přímé připojení. |
DisableHTTP2, DisableQUIC | Vypnou příslušné protokoly. |
ForceHTTP3 | Použije QUIC pro každý origin bez čekání na oznámení Alt-Svc. |
InsecureSkipVerify | Přijme libovolný certifikát serveru. |
Pool | Session enginu k použití. Nil použije client.DefaultPool(), který povoluje 64 souběžných požadavků. |
Transporty se shodnými možnostmi sdílejí jeden Chromium URLRequestContext, takže connection pooly, HTTP/2 sessions a QUIC sessions se znovu používají napříč požadavky. Nastavení HQQ_CHROME_NET_SERVER=/path/to/chrome_net_server nahradí vloženého pracovního procesu, například lokálním buildem.
Jak to funguje
| Vrstva | Co zde běží |
|---|---|
| Váš program | http.Client a chromenet.Transport, čistě v Go. |
| Hranice procesu | HQQ shared-memory RPC. Rozhraní je chrome_net.idl, které hqq-bindgen zkompiluje do klienta v Go a do C serverového glue kódu. |
chrome_net_server | Malá vrstva v C++ nad Chromium: jedno síťové vlákno, které vlastní všechny objekty //net, a session vlákna, která blokují na požadavku, dokud jim síťové vlákno neodpoví. |
Chromium //net | URLRequestContext, URLRequest, HTTP/2 a QUIC sessions, BoringSSL. |
IDL má šest operací, na kterých záleží: vytvořit engine z možností ve tvaru key=value, zahájit požadavek jeho hlavičkou (METHOD URL plus řádky hlaviček) a délkou těla, zapisovat části těla požadavku, čekat na hlavičku odpovědi, číst dekódované tělo a zrušit handly. Handle požadavku deklaruje @cancel, takže opuštěné volání přeruší požadavek uvnitř Chromium a nenechá jej běžet až do konce.
Izolace zásobníku v pracovním procesu je stejně tak bezpečnostní vlastností jako otázkou balení. Síťový kód Chromium zpracovává nedůvěryhodný vstup z otevřeného internetu. Pokud spadne, nespadne s ním i vaše služba v Go: volání skončí chybou, session se zahodí a klient při příští potřebě spustí nový pracovní proces.
Reprodukovatelně sestaveno, připnuto pomocí SHA-256
Binárka o velikosti 25 MB, která obsahuje Chromium, je důvěryhodná pouze tehdy, pokud lze přesně říci, co do ní vstoupilo. chromenet sleduje stabilní vydání Chromium stejným způsobem, jakým gosuda.org/sqlite3 sleduje SQLite: zdrojový kód se nikdy nevendoruje, pouze se připíná.
| Připnutí | Kde |
|---|---|
Vydání Chromium a jeho oficiální archiv zdrojového kódu -lite | third_party/chromium/VERSION |
| SHA-256 archivu a každého toolchainu, který vydání připíná: clang, Rust, GN, ninja, runtime clang pro Windows, xwin | third_party/chromium/SHA256 |
| Verze MSVC CRT a Windows SDK stahované přes xwin | third_party/chromium/WINSDK |
Vydání sandbox runtime (rungv), digest guest image, datum snapshotu Debianu | scripts/engine/BUILDENV |
| C runtime HQQ | go.mod a go.sum |
Aktuálním připnutím je Chromium 157.0.8103.38. Stažení, které neodpovídá svému připnutí, build zastaví. Vydání aplikuje na Chromium přesně jednu záplatu: na Linuxu se chrome_net_server sestavuje bez NSS a ověřuje certifikáty serverů pouze proti Chrome Root Store, takže binárka nepotřebuje nic nad rámec libc.
Linuxové a Windows enginy se sestavují uvnitř sandboxů gvx: neprivilegovaných procesů s připnutým guest image a bez hostitelského démona. Windows enginy se křížově kompilují na Linuxu pomocí clang-cl a lld-link z Chromium proti Windows SDK, které xwin stahuje od Microsoftu. Enginy pro macOS se sestavují nativně na macOS runneru, protože build Chromia pro macOS vyžaduje Xcode.
Opětovné sestavení stejného commitu dalo bajtově identické binárky napříč samostatnými CI běhy. Vydané binárky jsou bajtově identické s těmi, které předchozí CI běh sestavil pro commit, jímž byl engine začleněn do monorepa, na všech šesti platformách.
| Platforma | Velikost binárky |
|---|---|
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 |
Linuxové enginy závisí pouze na libc, libm, libdl, libpthread a libgcc_s a vyžadují glibc 2.31 nebo novější.
Sledování Chromium a vydání
Naplánovaný workflow kontroluje stabilní vydání Chromium každých šest hodin. Pro nové vydání přepíše připnutí, sestaví a otestuje všech šest enginů ve větvi chromium/<version>, attestuje binárky a commitne připnutí společně s vloženými binárkami, jejich digesty a texty licencí do main jedním commitem. V monorepu soubory OWNERS přesně těmto souborům udělují bota workflow, takže aktualizace projde bez lidského kroku, zatímco změny samotného návodu na sestavení (připnutí Windows SDK a záplata) stále vyžadují vlastníka.
Každá vydaná binárka nese attestaci původu sestavení od workflow, které ji vytvořilo:
1gh attestation verify chrome_net_server_linux_amd64 --repo gosuda/chromenet
Texty licencí toho, co každá binárka obsahuje (Chromium, BoringSSL, QUICHE a zbytek celého build grafu), generuje vlastní nástroj Chromium tools/licenses/licenses.py a jsou vydávány spolu s binárkou jako NOTICE_<os>_<arch>.txt.
Omezení
DialWebSocketje čistě Go klient podle RFC 6455 a nepoužívá zásobník Chromium.DialWebTransportje implementace běžící v rámci procesu. Na server nedosáhne.- Výchozí JA4 a HTTP/2 fingerprinty jsou fingerprinty připnutého vydání Chromium. Chromium je mezi vydáními mění a
chromenetje sleduje. - Binárky pro macOS jsou podepsány ad-hoc a nejsou notarizovány.
- Pracovní proces stojí paměť. Každá binárka enginu má na disku 24 až 28 MB a runtime Chromium spouští thread pool a síťové vlákno.
- Session enginu je držena, dokud není uzavřeno tělo odpovědi. Program, který těla odpovědí neuzavírá, vyčerpá pool.
Zdroj
Repozitář je github.com/gosuda/chromenet, zrcadlený z //net/chromenet v monorepu gosuda. Build návod se nachází v scripts/engine, glue kód enginu v engine/ a rozhraní v chrome_net.idl.