---
id: 6e0bce9e22a4bf7d9672c59e67bcf5ec
author: Lemon Mint
title: 'gosuda.org/chromenet: lo stack di rete di Chromium per Go, costruito in modo riproducibile a partire da una release fissata'
description: CGO_ENABLED=0 Un http.RoundTripper in Go che invia richieste attraverso lo stack di rete //net nativo di Chromium (HTTP/1.1, HTTP/2, QUIC/HTTP/3, BoringSSL) all'interno di un worker HQQ isolato, con sorgenti, toolchain e sei binari incorporati fissati tramite SHA-256 e compilati in sandbox gvx.
language: it
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` è un `http.RoundTripper` di Go che invia le richieste attraverso lo stack di rete nativo di Chromium. Lo stack è `//net` di una release ufficiale di Chromium: le sue implementazioni di HTTP/1.1, HTTP/2 e QUIC/HTTP/3, BoringSSL, la configurazione TLS di Chrome e il Chrome Root Store. Viene eseguito in un processo worker separato, `chrome_net_server`, mentre il lato Go rimane puro Go (`CGO_ENABLED=0`), e comunica con il worker tramite RPC a memoria condivisa [HQQ](https://gosuda.org/hqq).

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

## Perché eseguire lo stack di rete di Chromium

Un programma Go che comunica con il web tramite `net/http` si presenta al server come un client Go. È il comportamento predefinito corretto per il traffico tra servizi. È invece sbagliato quando il comportamento del server dipende dal tipo di client che ritiene di avere di fronte: endpoint che richiedono un browser, middleware anti-bot, CDN che servono versioni di protocollo diverse in base alle impronte TLS e HTTP/2, e test che devono riprodurre ciò che un browser reale sperimenta.

La libreria standard di Go non può riprodurre il comportamento a livello di rete di un browser. `crypto/tls` possiede un proprio ordine delle cipher suite e un proprio insieme di estensioni, mentre `net/http` ha le proprie impostazioni HTTP/2. Le librerie che imitano un browser copiando i byte del suo ClientHello continuano a doverli copiare, release dopo release. `chromenet` segue un'altra strada: non imita lo stack di rete di Chromium, ma lo esegue.

Misurato sul motore `darwin/arm64` con un servizio pubblico di analisi delle impronte (`tls.peet.ws`), la configurazione predefinita riporta:

| Proprietà                              | Valore osservato                                                       |
| :------------------------------------- | :--------------------------------------------------------------------- |
| Protocollo negoziato                   | `h2`                                                                   |
| JA4                                    | `t13d1516h2_8daaf6152771_806a8c22fdea`                                 |
| Impronta HTTP/2 (formato Akamai)       | `1:65536;2:0;4:6291456;6:262144\|15663105\|0\|m,a,s,p`                 |
| User-Agent                             | Chrome desktop della versione principale del motore, ad es. `Chrome/157.0.0.0` |

Con `ForceHTTP3`, le richieste verso `cloudflare-quic.com` e `www.google.com` hanno ricevuto risposta tramite HTTP/3. Questi valori provengono da una sola piattaforma in un solo giorno. Il motore è lo stack stesso, quindi cambiano quando cambia la release di Chromium fissata.

## Utilizzo

`NewTransport` restituisce un `http.RoundTripper`. Lo si può usare ovunque si userebbe `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))
}
```

Alla prima richiesta, il modulo scrive il binario worker incorporato per la propria piattaforma nella directory di cache dell'utente, verifica il suo digest SHA-256 con `gosuda.org/hqq/secureexec`, lo avvia con un collegamento al processo padre (`--parent-stdin`, in modo che termini quando termina il processo chiamante) e apre un pool di sessioni. Non è necessario installare nulla.

Alcuni comportamenti seguono quelli di `http.Transport` affinché `http.Client` funzioni senza modifiche:

- Il Transport non segue i reindirizzamenti e non conserva cookie. `http.Client` esegue entrambe le operazioni, come avviene con `http.Transport`.
- Le risposte compresse con gzip, deflate, Brotli o zstd arrivano già decodificate, e `resp.Uncompressed` è impostato su true.
- Una richiesta mantiene una sessione del motore fino alla chiusura del corpo della risposta. Chiudere sempre i corpi.
- L'annullamento del contesto della richiesta annulla la richiesta anche all'interno di Chromium.
- Gli errori di rete sono valori `*client.NetError` che contengono il codice e il nome dell'errore di Chromium:

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

### Opzioni

| Campo                         | Effetto                                                                                                 |
| :---------------------------- | :------------------------------------------------------------------------------------------------------ |
| `UserAgent`                   | Sostituisce lo User-Agent predefinito di Chrome. Un header `User-Agent` della richiesta ha comunque la precedenza. |
| `AcceptLanguage`              | Sostituisce `en-US,en;q=0.9`.                                                                           |
| `Proxy`                       | Regole proxy di Chromium, come `http://127.0.0.1:8080` o `socks5://127.0.0.1:1080`. Se vuoto, la connessione è diretta. |
| `DisableHTTP2`, `DisableQUIC` | Disattivano i rispettivi protocolli.                                                                    |
| `ForceHTTP3`                  | Usa QUIC per ogni origine senza attendere un annuncio `Alt-Svc`.                                        |
| `InsecureSkipVerify`          | Accetta qualsiasi certificato del server.                                                               |
| `Pool`                        | Sessioni del motore da utilizzare. Se nil, viene usato `client.DefaultPool()`, che consente 64 richieste concorrenti. |

I Transport con opzioni uguali condividono un unico `URLRequestContext` di Chromium, quindi i pool di connessioni, le sessioni HTTP/2 e le sessioni QUIC sono riutilizzati tra le richieste. Impostando `HQQ_CHROME_NET_SERVER=/path/to/chrome_net_server` si sostituisce il worker incorporato, ad esempio con una build locale.

## Come funziona

| Livello               | Cosa viene eseguito                                                                                                                                                                |
| :-------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Il tuo programma      | `http.Client` e `chromenet.Transport`, puro Go.                                                                                                                                    |
| Confine di processo   | RPC a memoria condivisa HQQ. L'interfaccia è `chrome_net.idl`, compilata da `hqq-bindgen` nel client Go e nel codice di collegamento del server C.                                |
| `chrome_net_server`   | Un piccolo livello C++ sopra Chromium: un thread di rete che possiede tutti gli oggetti di `//net`, e thread di sessione che si bloccano su una richiesta finché il thread di rete non risponde. |
| Chromium `//net`      | `URLRequestContext`, `URLRequest`, sessioni HTTP/2 e QUIC, BoringSSL.                                                                                                              |

L'IDL prevede sei operazioni fondamentali: creare un motore a partire da opzioni `key=value`, avviare una richiesta con la sua intestazione (`METHOD URL` più le righe di header) e la lunghezza del corpo, scrivere i blocchi del corpo della richiesta, attendere l'intestazione della risposta, leggere il corpo decodificato e distruggere gli handle. Un handle di richiesta dichiara `@cancel`, quindi una chiamata abbandonata interrompe la richiesta all'interno di Chromium invece di lasciarla proseguire fino al completamento.

Isolare lo stack in un processo worker è una proprietà di sicurezza oltre che di packaging. Il codice di rete di Chromium elabora input ostili provenienti da internet aperta. Se va in crash, il tuo servizio Go non va in crash con esso: la chiamata fallisce con un errore, la sessione viene scartata e il client avvia un nuovo worker la volta successiva in cui ne ha bisogno.

## Build riproducibile, fissata tramite SHA-256

Un binario di 25 MB che contiene Chromium è affidabile solo se è possibile dichiarare con precisione cosa vi è stato incluso. `chromenet` segue Chromium Stable nello stesso modo in cui `gosuda.org/sqlite3` segue SQLite: il sorgente non viene mai incluso nel repository (vendoring), ma soltanto fissato tramite pin.

| Pin                                                                                                                      | Dove                           |
| :----------------------------------------------------------------------------------------------------------------------- | :----------------------------- |
| Release di Chromium e relativo archivio sorgente ufficiale `-lite`                                                       | `third_party/chromium/VERSION` |
| SHA-256 dell'archivio e di ogni toolchain fissata da quella release: clang, Rust, GN, ninja, il runtime clang per Windows, xwin | `third_party/chromium/SHA256`  |
| Versioni di MSVC CRT e Windows SDK ottenute tramite xwin                                                                 | `third_party/chromium/WINSDK`  |
| La release del runtime sandbox (`rungv`), il digest dell'immagine guest, la data dello snapshot Debian                   | `scripts/engine/BUILDENV`      |
| Il runtime C di HQQ                                                                                                      | `go.mod` e `go.sum`            |

Il pin attuale è Chromium `157.0.8103.38`. Un download che non corrisponde al proprio pin interrompe la build. La release applica esattamente una patch a Chromium: su Linux, `chrome_net_server` viene compilato senza NSS e verifica i certificati dei server esclusivamente con il Chrome Root Store, in modo che il binario non richieda nulla oltre alla libc.

I motori Linux e Windows vengono compilati all'interno di sandbox [gvx](https://gosuda.org/blog/posts/gvx-cross-platform-linux-containers-z48916d01): processi senza privilegi con un'immagine guest fissata e nessun daemon sull'host. I motori Windows vengono compilati in cross-compilazione su Linux con clang-cl e lld-link di Chromium, con il Windows SDK che xwin scarica da Microsoft. I motori macOS vengono compilati in modo nativo su un runner macOS, perché la build di Chromium per macOS richiede Xcode.

Rieseguire la build dello stesso commit ha prodotto binari identici byte per byte in esecuzioni CI separate. I binari di release sono identici byte per byte a quelli che una precedente esecuzione CI aveva compilato per il commit con cui il motore è entrato nel monorepo, su tutte e sei le piattaforme.

| Piattaforma     | Dimensione del binario |
| :-------------- | ---------------------: |
| `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 |

I motori Linux dipendono solo da `libc`, `libm`, `libdl`, `libpthread` e `libgcc_s`, e richiedono glibc 2.31 o successiva.

### Monitoraggio di Chromium e delle release

Un workflow pianificato controlla Chromium Stable ogni sei ore. In caso di nuova release, riscrive i pin, compila e testa tutti e sei i motori su un branch `chromium/<version>`, certifica i binari e include in un unico commit su `main` i pin insieme ai binari incorporati, ai loro digest e ai testi delle licenze. Nel monorepo, i file `OWNERS` per file concedono al bot del workflow esattamente questi file, in modo che l'aggiornamento venga integrato senza un intervento umano, mentre le modifiche alla ricetta di build stessa (il pin del Windows SDK e la patch) richiedono ancora un responsabile.

Ogni binario di release reca un'attestazione di provenienza della build, emessa dal workflow che lo ha compilato:

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

I testi delle licenze di ciò che ogni binario contiene (Chromium, BoringSSL, QUICHE e il resto del grafo di build) sono generati dallo script `tools/licenses/licenses.py` di Chromium e distribuiti con la release come `NOTICE_<os>_<arch>.txt`.

## Limiti

- `DialWebSocket` è un client puro Go conforme a RFC 6455 e non utilizza lo stack di Chromium.
- `DialWebTransport` è un'implementazione in-process. Non raggiunge un server.
- Le impronte JA4 e HTTP/2 predefinite sono quelle della release di Chromium fissata. Chromium le modifica da una release all'altra, e `chromenet` le segue.
- I binari macOS sono firmati ad-hoc e non sono notarizzati.
- Un processo worker comporta un costo in memoria. Ogni binario del motore occupa da 24 a 28 MB su disco, e il runtime di Chromium avvia un pool di thread e un thread di rete.
- Una sessione del motore resta occupata fino alla chiusura del corpo della risposta. Un programma che non chiude i corpi delle risposte esaurirà il pool.

## Sorgente

Il repository è [github.com/gosuda/chromenet](https://github.com/gosuda/chromenet), replicato da `//net/chromenet` nel monorepo di gosuda. La ricetta di build si trova in `scripts/engine`, il codice di collegamento del motore in `engine/`, e l'interfaccia in `chrome_net.idl`.
