---
id: 6e0bce9e22a4bf7d9672c59e67bcf5ec
author: Lemon Mint
title: 'gosuda.org/chromenet: Chromiums netværksstak til Go, bygget reproducerbart fra en fastlåst udgivelse'
description: CGO_ENABLED=0 Go http.RoundTripper, der sender forespørgsler gennem Chromiums egen //net-stak (HTTP/1.1, HTTP/2, QUIC/HTTP/3, BoringSSL) i en isoleret HQQ-worker, med kildekode, toolchains og seks indlejrede binærfiler fastlåst via SHA-256 og bygget i gvx-sandkasser.
language: da
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` er en Go `http.RoundTripper`, der sender forespørgsler gennem Chromiums egen netværksstack. Stacken er `//net` fra en officiel Chromium-udgivelse: dens HTTP/1.1-, HTTP/2- og QUIC/HTTP/3-implementeringer, BoringSSL, Chromes TLS-konfiguration og Chrome Root Store. Den kører i en separat workerproces, `chrome_net_server`, og Go-siden forbliver rent Go (`CGO_ENABLED=0`), idet den kommunikerer med workeren via [HQQ](https://gosuda.org/hqq) shared-memory RPC.

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

## Hvorfor køre Chromiums netværksstack

Et Go-program, der kommunikerer med nettet via `net/http`, præsenterer en Go-klient over for serveren. Det er den rigtige standard for tjeneste-til-tjeneste-trafik. Det er den forkerte, når serverens adfærd afhænger af, hvilken type klient den tror, den kommunikerer med: browserbegrænsede endepunkter, anti-bot-middleware, CDN'er, der leverer forskellige protokolversioner til forskellige TLS- og HTTP/2-fingeraftryk, og tests, der skal reproducere, hvad en rigtig browser oplever.

Go's standardbibliotek kan ikke give dig en browsers wire-adfærd. `crypto/tls` har sin egen rækkefølge af cipher-suites og sit eget sæt af udvidelser, og `net/http` har sine egne HTTP/2-indstillinger. Biblioteker, der efterligner en browser ved at kopiere dens ClientHello-bytes, bliver ved med at kopiere, udgivelse efter udgivelse. `chromenet` går den anden vej: den efterligner ikke Chromium-netværksstacken, den kører den.

Målt på `darwin/arm64`-motoren mod en offentlig fingeraftryks-tjeneste (`tls.peet.ws`) rapporterer standardkonfigurationen:

| Egenskab                           | Observeret værdi                                                      |
| :--------------------------------- | :-------------------------------------------------------------------- |
| Forhandlet protokol                | `h2`                                                                  |
| JA4                                | `t13d1516h2_8daaf6152771_806a8c22fdea`                                |
| HTTP/2-fingeraftryk (Akamai-format) | `1:65536;2:0;4:6291456;6:262144\|15663105\|0\|m,a,s,p`                |
| User-Agent                         | desktop Chrome i motorens hovedversion, f.eks. `Chrome/157.0.0.0`     |

Med `ForceHTTP3` blev forespørgsler til `cloudflare-quic.com` og `www.google.com` besvaret over HTTP/3. Disse tal stammer fra én platform på én dag. Motoren er stacken, så de ændrer sig, når den fastlagte Chromium-udgivelse ændres.

## Brug af pakken

`NewTransport` returnerer en `http.RoundTripper`. Brug den overalt, hvor du ville bruge `http.Transport`:

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

Ved den første forespørgsel skriver modulet den indlejrede workerbinær til din platform i brugerens cache-mappe, verificerer dens SHA-256-digest med `gosuda.org/hqq/secureexec`, starter den med en forælderlivline (`--parent-stdin`, så den afslutter, når dit program afslutter), og åbner en sessionspool. Intet behøver at blive installeret.

Nogle adfærdsmønstre følger `http.Transport`, så `http.Client` virker uændret:

- Transporten følger ikke omdirigeringer og gemmer ingen cookies. `http.Client` gør begge dele, ligesom med `http.Transport`.
- Svar komprimeret med gzip, deflate, Brotli eller zstd ankommer afkodet, og `resp.Uncompressed` er true.
- En forespørgsel holder én motorsession, indtil svarets body lukkes. Luk bodies.
- Annullering af forespørgslens context annullerer forespørgslen inde i Chromium.
- Netværksfejl er `*client.NetError`-værdier, der indeholder Chromium-fejlkoden og -navnet:

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

### Indstillinger

| Felt                          | Effekt                                                                                                  |
| :---------------------------- | :------------------------------------------------------------------------------------------------------ |
| `UserAgent`                   | Erstatter standard-Chrome-User-Agent. En `User-Agent`-requestheader har stadig forrang.                 |
| `AcceptLanguage`              | Erstatter `en-US,en;q=0.9`.                                                                             |
| `Proxy`                       | Chromium-proxyregler såsom `http://127.0.0.1:8080` eller `socks5://127.0.0.1:1080`. Tom betyder direkte. |
| `DisableHTTP2`, `DisableQUIC` | Slår protokollerne fra.                                                                                 |
| `ForceHTTP3`                  | Bruger QUIC for alle origins uden at vente på en `Alt-Svc`-annoncering.                                 |
| `InsecureSkipVerify`          | Accepterer ethvert servercertifikat.                                                                    |
| `Pool`                        | Motorsessioner, der skal bruges. Nil bruger `client.DefaultPool()`, som tillader 64 samtidige forespørgsler. |

Transporter med ens indstillinger deler én Chromium `URLRequestContext`, så forbindelsespuljer, HTTP/2-sessioner og QUIC-sessioner genbruges på tværs af forespørgsler. Sætter du `HQQ_CHROME_NET_SERVER=/path/to/chrome_net_server`, erstattes den indlejrede worker, for eksempel med en lokal build.

## Hvordan det virker

| Lag                 | Hvad der kører der                                                                                                                                                                  |
| :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Dit program         | `http.Client` og `chromenet.Transport`, rent Go.                                                                                                                                    |
| Procesgrænse        | HQQ shared-memory RPC. Interfacet er `chrome_net.idl`, kompileret af `hqq-bindgen` til Go-klienten og C-serverglue.                                                                |
| `chrome_net_server` | Et lille C++-lag oven på Chromium: én netværkstråd, der ejer alle `//net`-objekter, og sessiontråde, der blokerer på en forespørgsel, indtil netværkstråden svarer.                 |
| Chromium `//net`    | `URLRequestContext`, `URLRequest`, HTTP/2- og QUIC-sessioner, BoringSSL.                                                                                                            |

IDL'en har seks operationer, der betyder noget: oprette en motor ud fra `key=value`-indstillinger, starte en forespørgsel med dens hoved (`METHOD URL` plus headerlinjer) og body-længde, skrive chunks af request-body, vente på svarets hoved, læse den afkodede body og destruere handles. En requesthandle erklærer `@cancel`, så et opgivet kald afbryder forespørgslen inde i Chromium i stedet for at lade den køre til ende.

At isolere stacken i en workerproces er lige så meget en sikkerhedsegenskab som en pakningsmæssig. Chromiums netværkskode parser fjendtligt input fra det åbne internet. Hvis den crasher, crasher din Go-tjeneste ikke med: kaldet fejler med en fejl, sessionen kasseres, og klienten starter en ny worker, næste gang den får brug for en.

## Reproducerbart bygget, fastlåst via SHA-256

En 25 MB binær fil, der indeholder Chromium, er kun til at stole på, hvis du præcist kan sige, hvad der er gået ind i den. `chromenet` følger Chromium Stable, ligesom `gosuda.org/sqlite3` følger SQLite: kildekoden vendoreres aldrig, den fastlåses kun.

| Fastlåsning                                                                                                                                  | Hvor                           |
| :------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------- |
| Chromium-udgivelsen og dens officielle `-lite`-kildearkiv                                                                                    | `third_party/chromium/VERSION` |
| SHA-256 for arkivet og for hver værktøjskæde, som udgivelsen fastlåser: clang, Rust, GN, ninja, Windows clang-runtime, xwin                 | `third_party/chromium/SHA256`  |
| MSVC CRT- og Windows SDK-versioner hentet via xwin                                                                                           | `third_party/chromium/WINSDK`  |
| Sandbox-runtime (`rungv`)-udgivelsen, gæstebilledets digest, Debian-snapshotdatoen                                                           | `scripts/engine/BUILDENV`      |
| HQQ C-runtime                                                                                                                                | `go.mod` og `go.sum`           |

Den nuværende fastlåsning er Chromium `157.0.8103.38`. En download, der ikke matcher sin fastlåsning, stopper bygget. Udgivelsen anvender præcis ét patch på Chromium: på Linux bygges `chrome_net_server` uden NSS, og servercertifikater verificeres kun mod Chrome Root Store, så binæren kræver intet ud over libc.

Linux- og Windows-motorerne bygges inde i [gvx](https://gosuda.org/blog/posts/gvx-cross-platform-linux-containers-z48916d01)-sandkasser: uprivilegerede processer med et fastlåst gæstebillede og ingen daemon på værten. Windows-motorerne krydskompileres på Linux med Chromiums clang-cl og lld-link mod Windows SDK, som xwin henter fra Microsoft. macOS-motorerne bygges nativt på en macOS-runner, fordi Chromiums macOS-build kræver Xcode.

Genbygning af samme commit gav byte-identiske binære filer på tværs af separate CI-kørsler. Udgivelsesbinærerne er byte-identiske med dem, en tidligere CI-kørsel byggede for den commit, der indførte motoren i monorepoet, på alle seks platforme.

| Platform        | Binær størrelse |
| :-------------- | --------------: |
| `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-motorerne afhænger kun af `libc`, `libm`, `libdl`, `libpthread` og `libgcc_s` og kræver glibc 2.31 eller nyere.

### Sporing af Chromium og udgivelser

En planlagt workflow kontrollerer Chromium Stable hver sjette time. For en ny udgivelse omskriver den fastlåsningerne, bygger og tester alle seks motorer på en `chromium/<version>`-gren, attesterer binærerne og committer fastlåsningerne sammen med de indlejrede binærer, deres digests og licensteksterne til `main` i én commit. I monorepoet giver per-fil `OWNERS` workflowets bot præcis disse filer, så opdateringen lander uden et menneskeligt trin, mens ændringer i selve byggeopskriften (Windows SDK-fastlåsningen og patchen) stadig kræver en ejer.

Hver udgivelsesbinær bærer en build provenance-attestation fra den workflow, der byggede den:

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

Licensteksterne for hvad hver binær indeholder (Chromium, BoringSSL, QUICHE og resten af byggegrafen) genereres af Chromiums egen `tools/licenses/licenses.py` og leveres med udgivelsen som `NOTICE_<os>_<arch>.txt`.

## Begrænsninger

- `DialWebSocket` er en ren Go-RFC 6455-klient og bruger ikke Chromium-stacken.
- `DialWebTransport` er en in-process-implementering. Den når ikke en server.
- De indbyggede JA4- og HTTP/2-fingeraftryk er dem fra den fastlåste Chromium-udgivelse. Chromium ændrer dem fra udgivelse til udgivelse, og `chromenet` følger med.
- macOS-binærerne er ad-hoc-signerede og ikke notariserede.
- En workerproces koster hukommelse. Hver motorbinær fylder 24 til 28 MB på disk, og Chromiums runtime starter en trådpulje og en netværkstråd.
- En motorsession holdes, indtil svarets body lukkes. Et program, der lækker svar-bodies, vil udtømme puljen.

## Kilde

Repositoryet er [github.com/gosuda/chromenet](https://github.com/gosuda/chromenet), spejlet fra `//net/chromenet` i gosuda-monorepoet. Byggeopskriften ligger i `scripts/engine`, motorglue i `engine/`, og interfacet i `chrome_net.idl`.
