---
id: 6e0bce9e22a4bf7d9672c59e67bcf5ec
author: Lemon Mint
title: 'gosuda.org/chromenet: Chromiumin verkkopino Go-kielelle, rakennettu toistettavasti lukitusta julkaisusta'
description: CGO_ENABLED=0 Go http.RoundTripper, joka lähettää pyynnöt Chromiumin oman //net-pinon kautta (HTTP/1.1, HTTP/2, QUIC/HTTP/3, BoringSSL) eristetyssä HQQ-työntekijässä, ja jossa lähdekoodi, työkaluketjut sekä kuusi upotettua binääriä on kiinnitetty SHA-256-tiivisteillä ja rakennettu gvx-sandboxeissa.
language: fi
date: 2026-11-09T00:00:00Z
path: /chromenet
go_package: gosuda.org/chromenet
go_repourl: https://github.com/gosuda/chromenet.git
category: network
---

`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](https://gosuda.org/hqq) -jaetun muistin RPC:n kautta.

```bash
go 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:

| Ominaisuus                         | Havaittu arvo                                                         |
| :--------------------------------- | :-------------------------------------------------------------------- |
| Neuvoteltu protokolla              | `h2`                                                                  |
| JA4                                | `t13d1516h2_8daaf6152771_806a8c22fdea`                                |
| HTTP/2-sormenjälki (Akamai-muoto)  | `1:65536;2:0;4:6291456;6:262144\|15663105\|0\|m,a,s,p`                |
| User-Agent                         | työ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:

```go
package main

import (
	"fmt"
	"io"
	"net/http"

	"gosuda.org/chromenet"
)

func main() {
	client := &http.Client{Transport: chromenet.NewTransport(chromenet.Options{})}

	resp, err := client.Get("https://example.com/")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Proto, resp.StatusCode, len(body))
}
```

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:

```go
var ne *client.NetError
if errors.As(err, &ne) {
	fmt.Println(ne.Code, ne.Name) // -105 ERR_NAME_NOT_RESOLVED
}
```

### Asetukset

| Kenttä                        | Vaikutus                                                                                                  |
| :---------------------------- | :-------------------------------------------------------------------------------------------------------- |
| `UserAgent`                   | Korvaa oletusarvoisen Chrome-User-Agentin. Pyynnön `User-Agent`-otsake voittaa edelleen.                 |
| `AcceptLanguage`              | Korvaa arvon `en-US,en;q=0.9`.                                                                            |
| `Proxy`                       | Chromiumin välityspalvelusäännöt, kuten `http://127.0.0.1:8080` tai `socks5://127.0.0.1:1080`. Tyhjä tarkoittaa suoraa yhteyttä. |
| `DisableHTTP2`, `DisableQUIC` | Kytkevät protokollat pois päältä.                                                                         |
| `ForceHTTP3`                  | Käyttää QUIC:iä jokaiselle origolle odottamatta `Alt-Svc`-ilmoitusta.                                     |
| `InsecureSkipVerify`          | Hyväksyy minkä tahansa palvelinsertifikaatin.                                                             |
| `Pool`                        | Kä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

| Kerros              | Mitä siellä ajetaan                                                                                                                                                                  |
| :------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Ohjelmasi           | `http.Client` ja `chromenet.Transport`, puhdasta Go-koodia.                                                                                                                          |
| Prosessiraja        | HQQ-jaetun muistin RPC. Rajapinta on `chrome_net.idl`, jonka `hqq-bindgen` kääntää Go-asiakkaaksi ja C-palvelinliimakoodiksi.                                                        |
| `chrome_net_server` | Ohut C++-kerros Chromiumin päällä: yksi verkkosäie, joka omistaa kaikki `//net`-oliot, sekä istuntosäikeet, jotka odottavat pyyntöä, kunnes verkkosäie vastaa.                        |
| Chromium `//net`    | `URLRequestContext`, `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.

| Kiinnitys                                                                                                                | Sijainti                       |
| :----------------------------------------------------------------------------------------------------------------------- | :----------------------------- |
| Chromium-julkaisu ja sen virallinen `-lite`-lähdearkisto                                                                 | `third_party/chromium/VERSION` |
| Arkiston ja jokaisen työkaluketjun, jonka julkaisu kiinnittää (clang, Rust, GN, ninja, Windowsin clang-ajonaika, xwin), SHA-256 | `third_party/chromium/SHA256`  |
| MSVC CRT:n ja Windows SDK:n versiot, jotka haetaan xwin:n kautta                                                         | `third_party/chromium/WINSDK`  |
| Sandbox-ajonaikan (`rungv`) julkaisu, vierasjärjestelmän kuvan tiiviste, Debianin snapshot-päivämäärä                    | `scripts/engine/BUILDENV`      |
| HQQ C -ajonaika                                                                                                          | `go.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](https://gosuda.org/blog/posts/gvx-cross-platform-linux-containers-z48916d01)-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.

| Alusta          | Binäärin koko |
| :-------------- | ------------: |
| `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-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:

```bash
gh 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](https://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`.
