---
id: 6e0bce9e22a4bf7d9672c59e67bcf5ec
author: Lemon Mint
title: 'gosuda.org/chromenet: Chromiums netwerkstack voor Go, reproduceerbaar gebouwd vanuit een vastgezette release'
description: CGO_ENABLED=0 Go http.RoundTripper die verzoeken verstuurt via de eigen //net-stack van Chromium (HTTP/1.1, HTTP/2, QUIC/HTTP/3, BoringSSL) in een geïsoleerde HQQ-worker, met broncode, toolchains en zes ingebedde binaire bestanden die via SHA-256 zijn vastgepind en zijn gebouwd in gvx-sandboxes.
language: nl
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` is een Go `http.RoundTripper` die verzoeken verstuurt via de eigen netwerkstack van Chromium. De stack is `//net` uit een officiële Chromium-release: de HTTP/1.1-, HTTP/2- en QUIC/HTTP/3-implementaties, BoringSSL, de TLS-configuratie van Chrome en de Chrome Root Store. Deze draait in een apart workerproces, `chrome_net_server`, en de Go-kant blijft pure Go (`CGO_ENABLED=0`), die met de worker communiceert via [HQQ](https://gosuda.org/hqq) shared-memory RPC.

```bash
go get gosuda.org/chromenet@v0.1.10157081033800
```

## Waarom de netwerkstack van Chromium draaien

Een Go-programma dat via `net/http` met het web communiceert, presenteert zich aan de server als een Go-client. Dat is de juiste standaard voor service-naar-service-verkeer. Het is de verkeerde keuze wanneer het gedrag van de server afhangt van wat voor soort client de server denkt te zien: browsergebonden endpoints, anti-botmiddleware, CDN's die verschillende protocolversies serveren aan verschillende TLS- en HTTP/2-fingerprints, en tests die precies moeten reproduceren wat een echte browser ervaart.

De standaardbibliotheek van Go kan het netwerkgedrag van een browser op de draad niet nabootsen. `crypto/tls` heeft een eigen volgorde van cipher suites en een eigen extensieset, en `net/http` heeft eigen HTTP/2-instellingen. Bibliotheken die een browser imiteren door de ClientHello-bytes te kopiëren, blijven kopiëren, release na release. `chromenet` kiest een andere route: het imiteert de Chromium-netwerkstack niet, maar draait haar.

Gemeten op de `darwin/arm64`-engine tegen een publieke fingerprintdienst (`tls.peet.ws`), rapporteert de standaardconfiguratie:

| Eigenschap                         | Geobserveerde waarde                                                   |
| :--------------------------------- | :--------------------------------------------------------------------- |
| Onderhandeld protocol              | `h2`                                                                   |
| JA4                                | `t13d1516h2_8daaf6152771_806a8c22fdea`                                 |
| HTTP/2-fingerprint (Akamai-formaat) | `1:65536;2:0;4:6291456;6:262144\|15663105\|0\|m,a,s,p`                 |
| User-Agent                         | desktop Chrome van de hoofdversie van de engine, bijvoorbeeld `Chrome/157.0.0.0` |

Met `ForceHTTP3` werden verzoeken naar `cloudflare-quic.com` en `www.google.com` beantwoord via HTTP/3. Deze cijfers komen van één platform op één dag. De engine is de stack, dus ze veranderen wanneer de vastgepinde Chromium-release verandert.

## Gebruik

`NewTransport` retourneert een `http.RoundTripper`. Gebruik deze waar je anders `http.Transport` zou gebruiken:

```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))
}
```

Bij het eerste verzoek schrijft de module het ingebedde workerbinaire bestand voor jouw platform naar de gebruikerscachemap, verifieert de SHA-256-digest met `gosuda.org/hqq/secureexec`, start het met een ouderlijke levenslijn (`--parent-stdin`, zodat het afsluit wanneer je proces afsluit), en opent een sessiepool. Er hoeft niets geïnstalleerd te worden.

Enkele gedragingen volgen `http.Transport`, zodat `http.Client` ongewijzigd werkt:

- De Transport volgt geen redirects en bewaart geen cookies. `http.Client` doet beide, zoals bij `http.Transport`.
- Responses die met gzip, deflate, Brotli of zstd zijn gecomprimeerd, komen gedecodeerd aan, en `resp.Uncompressed` is true.
- Een verzoek houdt één engine-sessie vast totdat de responsebody is gesloten. Sluit bodies.
- Het annuleren van de requestcontext annuleert het verzoek binnen Chromium.
- Netwerkfouten zijn `*client.NetError`-waarden die de Chromium-foutcode en -naam bevatten:

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

### Opties

| Veld                          | Effect                                                                                                    |
| :---------------------------- | :-------------------------------------------------------------------------------------------------------- |
| `UserAgent`                   | Vervangt de standaard Chrome User-Agent. Een `User-Agent`-requestheader wint nog steeds.                  |
| `AcceptLanguage`              | Vervangt `en-US,en;q=0.9`.                                                                                |
| `Proxy`                       | Chromium-proxyregels zoals `http://127.0.0.1:8080` of `socks5://127.0.0.1:1080`. Leeg betekent direct.  |
| `DisableHTTP2`, `DisableQUIC` | Schakelen de protocollen uit.                                                                             |
| `ForceHTTP3`                  | Gebruikt QUIC voor elke origin zonder te wachten op een `Alt-Svc`-advertentie.                            |
| `InsecureSkipVerify`          | Accepteert elk servercertificaat.                                                                         |
| `Pool`                        | Te gebruiken engine-sessies. Nil gebruikt `client.DefaultPool()`, dat 64 gelijktijdige verzoeken toestaat. |

Transports met gelijke opties delen één Chromium `URLRequestContext`, zodat connectiepools, HTTP/2-sessies en QUIC-sessies tussen verzoeken worden hergebruikt. Het instellen van `HQQ_CHROME_NET_SERVER=/path/to/chrome_net_server` vervangt de ingebedde worker, bijvoorbeeld door een lokale build.

## Hoe het werkt

| Laag                | Wat daar draait                                                                                                                                                                                                  |
| :------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Je programma        | `http.Client` en `chromenet.Transport`, pure Go.                                                                                                                                                                 |
| Procesgrens         | HQQ shared-memory RPC. De interface is `chrome_net.idl`, gecompileerd door `hqq-bindgen` naar de Go-client en de C-serverglue.                                                                                  |
| `chrome_net_server` | Een kleine C++-laag bovenop Chromium: één netwerkthread die elk `//net`-object bezit, en sessiethreads die blokkeren op een verzoek totdat de netwerkthread antwoordt.                                          |
| Chromium `//net`    | `URLRequestContext`, `URLRequest`, HTTP/2- en QUIC-sessies, BoringSSL.                                                                                                                                           |

De IDL heeft zes operaties die ertoe doen: een engine maken uit `key=value`-opties, een verzoek starten met zijn head (`METHOD URL` plus headerregels) en bodylengte, requestbody-chunks schrijven, wachten op de responsehead, de gedecodeerde body lezen, en de handles vernietigen. Een requesthandle declareert `@cancel`, zodat een verlaten aanroep het verzoek binnen Chromium onderbreekt in plaats van het tot voltooiing te laten lopen.

Het isoleren van de stack in een workerproces is evenzeer een veiligheidseigenschap als een verpakkingskwestie. De netwerkcode van Chromium parseert vijandige invoer van het open internet. Als die crasht, crasht je Go-service niet mee: de aanroep faalt met een fout, de sessie wordt weggegooid, en de client start de volgende keer dat hij die nodig heeft een nieuwe worker.

## Reproduceerbaar gebouwd, vastgepind met SHA-256

Een binair bestand van 25 MB dat Chromium bevat, is alleen betrouwbaar als je precies kunt zeggen wat erin is gegaan. `chromenet` volgt Chromium Stable op dezelfde manier als `gosuda.org/sqlite3` SQLite volgt: de broncode wordt nooit gevendored, alleen vastgepind.

| Pin                                                                                                                                                | Waar                           |
| :------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------- |
| Chromium-release en het officiële `-lite`-bronarchief                                                                                              | `third_party/chromium/VERSION` |
| SHA-256 van het archief en van elke toolchain die die release vastlegt: clang, Rust, GN, ninja, de Windows clang-runtime, xwin                     | `third_party/chromium/SHA256`  |
| MSVC CRT- en Windows SDK-versies die via xwin worden opgehaald                                                                                     | `third_party/chromium/WINSDK`  |
| De release van de sandbox-runtime (`rungv`), de digest van het gastimage, de Debian-snapshotdatum                                                  | `scripts/engine/BUILDENV`      |
| De HQQ C-runtime                                                                                                                                   | `go.mod` en `go.sum`           |

De huidige pin is Chromium `157.0.8103.38`. Een download die niet overeenkomt met zijn pin, stopt de build. De release past precies één patch toe op Chromium: op Linux wordt `chrome_net_server` gebouwd zonder NSS en worden servercertificaten alleen geverifieerd tegen de Chrome Root Store, zodat de binary niets anders nodig heeft dan libc.

De Linux- en Windows-engines worden gebouwd binnen [gvx](https://gosuda.org/blog/posts/gvx-cross-platform-linux-containers-z48916d01)-sandboxes: niet-geprivilegieerde processen met een vastgepind gastimage en zonder host-daemon. De Windows-engines worden op Linux cross-gecompileerd met clang-cl en lld-link van Chromium, tegen het Windows SDK dat xwin van Microsoft ophaalt. De macOS-engines worden native gebouwd op een macOS-runner, omdat de macOS-build van Chromium Xcode nodig heeft.

Het opnieuw bouwen van dezelfde commit leverde byte-identieke binaries op over afzonderlijke CI-runs. De releasebinaries zijn byte-identiek aan die welke een eerdere CI-run bouwde voor de commit die de engine in de monorepo introduceerde, op alle zes platforms.

| Platform        | Binaire grootte |
| :-------------- | --------------: |
| `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 |

De Linux-engines hangen alleen af van `libc`, `libm`, `libdl`, `libpthread` en `libgcc_s`, en hebben glibc 2.31 of nieuwer nodig.

### Chromium en releases volgen

Een ingeplande workflow controleert elke zes uur op Chromium Stable. Bij een nieuwe release herschrijft hij de pins, bouwt en test hij alle zes engines op een `chromium/<version>`-branch, attesteert hij de binaries en commit hij de pins samen met de ingebedde binaries, hun digests en de licentieteksten naar `main` in één commit. In de monorepo geven per-bestand-`OWNERS` de bot van de workflow precies die bestanden, zodat de update zonder menselijke stap landt, terwijl wijzigingen aan het buildrecept zelf (de Windows SDK-pin en de patch) nog steeds een eigenaar nodig hebben.

Elke releasebinary draagt een build provenance attestation van de workflow die hem bouwde:

```bash
gh attestation verify chrome_net_server_linux_amd64 --repo gosuda/chromenet
```

De licentieteksten van wat elke binary bevat (Chromium, BoringSSL, QUICHE en de rest van de buildgraaf) worden gegenereerd door `tools/licenses/licenses.py` van Chromium zelf en met de release meegeleverd als `NOTICE_<os>_<arch>.txt`.

## Beperkingen

- `DialWebSocket` is een pure-Go RFC 6455-client en gebruikt de Chromium-stack niet.
- `DialWebTransport` is een in-process implementatie. Het bereikt geen server.
- De standaard JA4- en HTTP/2-fingerprints zijn die van de vastgepinde Chromium-release. Chromium wijzigt ze van release tot release, en `chromenet` volgt dat.
- De macOS-binaries zijn ad-hoc gesigneerd en niet genotariseerd.
- Een workerproces kost geheugen. Elke engine-binary is 24 tot 28 MB op schijf, en de runtime van Chromium start een threadpool en een netwerkthread.
- Een engine-sessie wordt vastgehouden totdat de responsebody is gesloten. Een programma dat responsebodies lekt, put de pool uit.

## Bron

De repository is [github.com/gosuda/chromenet](https://github.com/gosuda/chromenet), gespiegeld vanuit `//net/chromenet` in de gosuda-monorepo. Het buildrecept staat in `scripts/engine`, de engineglue in `engine/`, en de interface in `chrome_net.idl`.
