GoSuda

gosuda.org/chromenet: Chromiumin verkkopino Go-kielelle, rakennettu toistettavasti lukitusta julkaisusta

By Lemon Mint views ...

gosuda.org/chromenet on Go-kielinen http.RoundTripper, joka lähettää pyynnöt Chromiumin omaa verkkopinoa käyttäen. Pino on //net virallisesta Chromium-julkaisusta: sen HTTP/1.1-, HTTP/2- ja QUIC/HTTP/3-toteutukset, BoringSSL, Chromen TLS-konfiguraatio ja Chrome Root Store. Se toimii erillisessä worker-prosessissa, chrome_net_server, ja Go-puoli pysyy puhtaana Go-koodina (CGO_ENABLED=0), kommunikoiden workerin kanssa HQQ -jaetun muistin RPC:n kautta.

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

Miksi ajaa Chromiumin verkkopinoa

Go-ohjelma, joka kommunikoi verkossa net/http:n avulla, esittäytyy palvelimelle Go-asiakkaana. Tämä on oikea oletus palvelusta toiseen -liikenteessä. Se on kuitenkin väärä valinta silloin, kun palvelimen käyttäytyminen riippuu siitä, minkälaiseksi asiakkaaksi se luulee yhteyden muodostajaa: selainrajoitetut päätepisteet, botinvastaiset välikerrokset, CDN-palvelut, jotka tarjoavat eri protokollaversioita eri TLS- ja HTTP/2-sormenjälkien mukaan, sekä testit, joiden on toistettava se, mitä todellinen selain kokee.

Go-standardikirjasto ei pysty tarjoamaan selaimen verkkokäyttäytymistä. crypto/tls:llä on oma salausjoukkojen järjestyksensä ja laajennusjoukkonsa, ja net/http:llä on omat HTTP/2-asetuksensa. Kirjastot, jotka jäljittelevät selainta kopioimalla sen ClientHello-tavut, joutuvat kopioimaan niitä jokaisen julkaisun jälkeen uudelleen. chromenet valitsee toisen tien: se ei jäljittele Chromiumin verkkopinoa, vaan ajaa sitä.

Mitattuna darwin/arm64-moottorilla julkista sormenjälkipalvelua (tls.peet.ws) vastaan oletuskonfiguraatio raportoi seuraavaa:

OminaisuusHavaittu arvo
Neuvoteltu protokollah2
JA4t13d1516h2_8daaf6152771_806a8c22fdea
HTTP/2-sormenjälki (Akamai-muoto)1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p
User-Agenttyöpöydän Chrome moottorin pääversiolla, esim. Chrome/157.0.0.0

ForceHTTP3-asetuksella pyynnöt osoitteisiin cloudflare-quic.com ja www.google.com saivat vastauksen HTTP/3:lla. Nämä luvut ovat peräisin yhdeltä alustalta yhdeltä päivältä. Moottori on itse pino, joten luvut muuttuvat, kun kiinnitetty Chromium-julkaisu muuttuu.

Käyttö

NewTransport palauttaa http.RoundTripper-rajapinnan toteuttavan olion. Sitä voi käyttää kaikkialla, missä käytettäisiin http.Transport:ia:

 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}

Ensimmäisellä pyynnöllä moduuli kirjoittaa upotetun worker-binäärin alustallesi käyttäjän välimuistihakemistoon, varmistaa sen SHA-256-tiivisteen gosuda.org/hqq/secureexec-paketilla, käynnistää sen vanhempiprosessiin sidotulla elinyhteydellä (--parent-stdin, jolloin se sulkeutuu, kun prosessisi sulkeutuu) ja avaa istuntoaltaan. Mitään ei tarvitse asentaa.

Muutamat toiminnot noudattavat http.Transport:n käyttäytymistä, jotta http.Client toimii muuttumattomana:

  • Transport ei seuraa uudelleenohjauksia eikä säilytä evästeitä. http.Client tekee molemmat, kuten http.Transport:n kanssa.
  • Gzip-, deflate-, Brotli- tai zstd-pakatut vastaukset saapuvat purettuina, ja resp.Uncompressed on tosi.
  • Pyyntö pitää yhtä moottori-istuntoa varattuna, kunnes vastauksen runko suljetaan. Sulje rungot.
  • Pyynnön kontekstin peruuttaminen peruuttaa pyynnön Chromiumin sisällä.
  • Verkkovirheet ovat *client.NetError-arvoja, jotka sisältävät Chromiumin virhekoodin ja nimen:
1var ne *client.NetError
2if errors.As(err, &ne) {
3	fmt.Println(ne.Code, ne.Name) // -105 ERR_NAME_NOT_RESOLVED
4}

Asetukset

KenttäVaikutus
UserAgentKorvaa oletusarvoisen Chrome-User-Agentin. Pyynnön User-Agent-otsake voittaa edelleen.
AcceptLanguageKorvaa arvon en-US,en;q=0.9.
ProxyChromiumin välityspalvelusäännöt, kuten http://127.0.0.1:8080 tai socks5://127.0.0.1:1080. Tyhjä tarkoittaa suoraa yhteyttä.
DisableHTTP2, DisableQUICKytkevät protokollat pois päältä.
ForceHTTP3Käyttää QUIC:iä jokaiselle origolle odottamatta Alt-Svc-ilmoitusta.
InsecureSkipVerifyHyväksyy minkä tahansa palvelinsertifikaatin.
PoolKäytettävät moottori-istunnot. Nil käyttää client.DefaultPool()-altaa, joka sallii 64 samanaikaista pyyntöä.

Transportit, joilla on samat asetukset, jakavat yhden Chromiumin URLRequestContext-kontekstin, joten yhteysaltaat, HTTP/2-istunnot ja QUIC-istunnot käytetään uudelleen pyyntöjen välillä. Asettamalla HQQ_CHROME_NET_SERVER=/path/to/chrome_net_server upotettu worker korvataan, esimerkiksi paikallisella käännöksellä.

Toimintaperiaate

KerrosMitä siellä ajetaan
Ohjelmasihttp.Client ja chromenet.Transport, puhdasta Go-koodia.
ProsessirajaHQQ-jaetun muistin RPC. Rajapinta on chrome_net.idl, jonka hqq-bindgen kääntää Go-asiakkaaksi ja C-palvelinliimakoodiksi.
chrome_net_serverOhut C++-kerros Chromiumin päällä: yksi verkkosäie, joka omistaa kaikki //net-oliot, sekä istuntosäikeet, jotka odottavat pyyntöä, kunnes verkkosäie vastaa.
Chromium //netURLRequestContext, URLRequest, HTTP/2- ja QUIC-istunnot, BoringSSL.

IDL:ssä on kuusi merkittävää operaatiota: luo moottori key=value-asetuksista, aloita pyyntö sen otsakkeella (METHOD URL plus otsakerivit) ja rungon pituudella, kirjoita pyynnön rungon osia, odota vastauksen otsaketta, lue purettu vastausrunko ja tuhoa kahvat. Pyyntökahva määrittelee @cancel-attribuutin, jolloin hylätty kutsu keskeyttää pyynnön Chromiumin sisällä sen sijaan, että se jatkuisi loppuun.

Pinon eristäminen worker-prosessiin on turvaominaisuus yhtä lailla kuin pakkausteknillinen ratkaisu. Chromiumin verkkokoodi jäsentää vihamielistä syötettä avoimesta internetistä. Jos se kaatuu, Go-palvelusi ei kaadu mukana: kutsu päättyy virheeseen, istunto hylätään, ja asiakas käynnistää uuden workerin seuraavan kerran, kun sitä tarvitaan.

Reprodusoituva rakentaminen, kiinnitys SHA-256:lla

25 MB:n binääri, joka sisältää Chromiumin, on luotettava vain, jos voit sanoa tarkasti, mitä siihen on kuulunut. chromenet seuraa Chromium Stable -julkaisua samalla tavalla kuin gosuda.org/sqlite3 seuraa SQLite:a: lähdekoodia ei koskaan vendoroida, vaan se ainoastaan kiinnitetään.

KiinnitysSijainti
Chromium-julkaisu ja sen virallinen -lite-lähdearkistothird_party/chromium/VERSION
Arkiston ja jokaisen työkaluketjun, jonka julkaisu kiinnittää (clang, Rust, GN, ninja, Windowsin clang-ajonaika, xwin), SHA-256third_party/chromium/SHA256
MSVC CRT:n ja Windows SDK:n versiot, jotka haetaan xwin:n kauttathird_party/chromium/WINSDK
Sandbox-ajonaikan (rungv) julkaisu, vierasjärjestelmän kuvan tiiviste, Debianin snapshot-päivämääräscripts/engine/BUILDENV
HQQ C -ajonaikago.mod ja go.sum

Nykyinen kiinnitys on Chromium 157.0.8103.38. Lataus, joka ei vastaa kiinnitystään, pysäyttää käännöksen. Julkaisu soveltaa Chromiumiin täsmälleen yhtä paikkaa: Linuxissa chrome_net_server käännetään ilman NSS:ää, ja palvelinsertifikaatit tarkistetaan vain Chrome Root Storea vastaan, joten binääri ei tarvitse muuta kuin libc:n.

Linux- ja Windows-moottorit rakennetaan gvx-hiekkalaatikoissa: etuoikeuksettomissa prosesseissa, joilla on kiinnitetty vierasjärjestelmän kuva eikä isäntäpuolen daemonia. Windows-moottorit käännetään ristiin Linuxissa Chromiumin clang-cl:llä ja lld-link:llä Windows SDK:ta vasten, jonka xwin hakee Microsoftilta. macOS-moottorit rakennetaan natiivisti macOS-ajurilla, koska Chromiumin macOS-käännös vaatii Xcoden.

Saman commitin uudelleenrakentaminen tuotti tavu tavulta identtisiä binäärejä eri CI-ajoissa. Julkaisubinäärit ovat tavu tavulta identtisiä niiden kanssa, jotka aiempi CI-ajo rakensi commitille, joka toi moottorin monorepoon, kaikilla kuudella alustalla.

AlustaBinäärin koko
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-moottorit riippuvat vain kirjastoista libc, libm, libdl, libpthread ja libgcc_s, ja vaativat glibc 2.31:n tai uudemman version.

Chromiumin ja julkaisujen seuranta

Ajastettu työnkulku tarkistaa Chromium Stablen kuuden tunnin välein. Uuden julkaisun kohdalla se kirjoittaa kiinnitykset uudelleen, rakentaa ja testaa kaikki kuusi moottoria chromium/<version>-haarassa, varmentaa binäärit ja tekee yhden commitin, joka sisältää kiinnitykset yhdessä upotettujen binäärien, niiden tiivisteiden ja lisenssitekstien kanssa main-haaraan. Monorepossa tiedostokohtaiset OWNERS-tiedostot myöntävät työnkulun botille täsmälleen nämä tiedostot, joten päivitys etenee ilman ihmisen vaihetta. Muutokset itse rakennusohjeeseen (Windows SDK:n kiinnitys ja paikka) vaativat kuitenkin edelleen omistajan hyväksynnän.

Jokainen julkaisubinääri kantaa rakennusprovenienssin todistuksen siitä työnkulusta, joka sen rakensi:

1gh attestation verify chrome_net_server_linux_amd64 --repo gosuda/chromenet

Kunkin binäärin sisältämien komponenttien lisenssitekstit (Chromium, BoringSSL, QUICHE ja muu rakennusgraafi) generoi Chromiumin oma tools/licenses/licenses.py, ja ne toimitetaan julkaisun mukana tiedostoina NOTICE_<os>_<arch>.txt.

Rajoitukset

  • DialWebSocket on puhdasta Go-koodia oleva RFC 6455 -asiakas eikä käytä Chromiumin pinoa.
  • DialWebTransport on prosessin sisäinen toteutus. Se ei saavuta palvelinta.
  • Oletusarvoiset JA4- ja HTTP/2-sormenjäljet ovat kiinnitetyn Chromium-julkaisun sormenjälkiä. Chromium muuttaa niitä julkaisusta toiseen, ja chromenet seuraa muutoksia.
  • macOS-binäärit on allekirjoitettu ad hoc -tavalla eikä niitä ole notarisoitu.
  • Worker-prosessi kuluttaa muistia. Kukin moottoribinääri on levyllä 24–28 MB, ja Chromiumin ajonaika käynnistää säiepoolin ja verkkosäikeen.
  • Moottori-istunto pidetään varattuna, kunnes vastausrunko suljetaan. Ohjelma, joka vuotaa vastausrunkoja, tyhjentää altaan.

Lähde

Repositorio on github.com/gosuda/chromenet, joka peilataan gosuda-monorepon kohdasta //net/chromenet. Rakennusohje sijaitsee hakemistossa scripts/engine, moottoriliima hakemistossa engine/ ja rajapinta tiedostossa chrome_net.idl.