---
id: 6e0bce9e22a4bf7d9672c59e67bcf5ec
author: Lemon Mint
title: 'gosuda.org/chromenet: Мрежовият стек на Chromium за Go, изграден по възпроизводим начин от фиксирана версия'
description: CGO_ENABLED=0 Go http.RoundTripper, който изпраща заявки през собствения стек //net на Chromium (HTTP/1.1, HTTP/2, QUIC/HTTP/3, BoringSSL) в изолиран HQQ работен процес, като изходният код, инструментите за компилация и шест вградени двоични файла са фиксирани чрез SHA-256 и изградени в gvx сандбокс среди.
language: bg
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` е Go `http.RoundTripper`, който изпраща заявки през собствения мрежов стек на Chromium. Стекът е `//net` от официално издание на Chromium: неговите реализации на HTTP/1.1, HTTP/2 и QUIC/HTTP/3, BoringSSL, TLS конфигурацията на Chrome и Chrome Root Store. Той работи в отделен работен процес, `chrome_net_server`, а Go частта остава чист Go (`CGO_ENABLED=0`) и комуникира с работния процес чрез [HQQ](https://gosuda.org/hqq) RPC през споделена памет.

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

## Защо да се използва мрежовият стек на Chromium

Go програма, която комуникира с уеб чрез `net/http`, се представя пред сървъра като Go клиент. Това е правилният избор по подразбиране за трафик между услуги. Той е грешен, когато поведението на сървъра зависи от това за какъв тип клиент той смята, че комуникира: крайни точки, защитени от браузър, anti-bot middleware, CDN мрежи, които предоставят различни версии на протоколи според различните TLS и HTTP/2 fingerprints, и тестове, които трябва да възпроизвеждат това, което изпитва реален браузър.

Стандартната библиотека на Go не може да осигури мрежовото поведение на браузър. `crypto/tls` има собствен ред на cipher suites и собствен набор от разширения, а `net/http` има собствени HTTP/2 настройки. Библиотеките, които имитират браузър чрез копиране на байтовете на ClientHello, продължават да копират при всяко ново издание. `chromenet` следва друг подход: не имитира мрежовия стек на Chromium, а го изпълнява.

Измерено на `darwin/arm64` engine спрямо публична услуга за fingerprint (`tls.peet.ws`), конфигурацията по подразбиране отчита следното:

| Свойство                           | Наблюдавана стойност                                                  |
| :--------------------------------- | :-------------------------------------------------------------------- |
| Договорен протокол                 | `h2`                                                                  |
| JA4                                | `t13d1516h2_8daaf6152771_806a8c22fdea`                                |
| HTTP/2 fingerprint (Akamai format) | `1:65536;2:0;4:6291456;6:262144\|15663105\|0\|m,a,s,p`                |
| User-Agent                         | десктоп Chrome с основната версия на engine-а, напр. `Chrome/157.0.0.0` |

С `ForceHTTP3` заявките към `cloudflare-quic.com` и `www.google.com` получиха отговор по HTTP/3. Тези стойности са получени на една платформа в един ден. Engine-ът е самият стек, затова те се променят, когато се промени закрепеното издание на Chromium.

## Употреба

`NewTransport` връща `http.RoundTripper`. Използвайте го навсякъде, където бихте използвали `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))
}
```

При първата заявка модулът записва вградения работен двоичен файл за вашата платформа в потребителската кеш директория, проверява SHA-256 дайджеста му с `gosuda.org/hqq/secureexec`, стартира го с родителска връзка (`--parent-stdin`, така че той да приключи, когато приключи вашият процес) и отваря пул от сесии. Нищо не трябва да се инсталира предварително.

Някои поведения следват `http.Transport`, така че `http.Client` работи без промени:

- Transport не следва пренасочвания и не съхранява бисквитки. `http.Client` прави и двете, както и при `http.Transport`.
- Отговорите, компресирани с gzip, deflate, Brotli или zstd, пристигат декодирани, а `resp.Uncompressed` е true.
- Една заявка държи една engine сесия, докато тялото на отговора не бъде затворено. Затваряйте телата.
- Отмяната на контекста на заявката отменя заявката в Chromium.
- Мрежовите грешки са стойности `*client.NetError`, които съдържат кода и името на грешката от Chromium:

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

### Опции

| Поле                          | Ефект                                                                                                    |
| :---------------------------- | :------------------------------------------------------------------------------------------------------- |
| `UserAgent`                   | Заменя по подразбиране Chrome User-Agent. Заглавие `User-Agent` в заявката все още има предимство.      |
| `AcceptLanguage`              | Заменя `en-US,en;q=0.9`.                                                                                 |
| `Proxy`                       | Правила за прокси в Chromium, като `http://127.0.0.1:8080` или `socks5://127.0.0.1:1080`. Празно означава директна връзка. |
| `DisableHTTP2`, `DisableQUIC` | Изключват съответните протоколи.                                                                         |
| `ForceHTTP3`                  | Използва QUIC за всеки origin, без да чака обява в `Alt-Svc`.                                            |
| `InsecureSkipVerify`          | Приема всеки сертификат на сървъра.                                                                      |
| `Pool`                        | Engine сесиите, които да се използват. Nil използва `client.DefaultPool()`, който позволява 64 едновременни заявки. |

Transports с еднакви опции споделят един Chromium `URLRequestContext`, така че connection pools, HTTP/2 сесии и QUIC сесии се използват повторно между заявките. Задаването на `HQQ_CHROME_NET_SERVER=/path/to/chrome_net_server` заменя вградения работен процес, например с локален build.

## Как работи

| Слой                | Какво се изпълнява там                                                                                                                                                                   |
| :------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Вашата програма     | `http.Client` и `chromenet.Transport`, чист Go.                                                                                                                                          |
| Граница на процеса  | HQQ RPC през споделена памет. Интерфейсът е `chrome_net.idl`, компилиран от `hqq-bindgen` в Go клиента и C server glue.                                                                 |
| `chrome_net_server` | Малък C++ слой върху Chromium: една мрежова нишка, която притежава всички `//net` обекти, и нишки за сесии, които блокират при заявка, докато мрежовата нишка не отговори.               |
| Chromium `//net`    | `URLRequestContext`, `URLRequest`, HTTP/2 и QUIC сесии, BoringSSL.                                                                                                                       |

IDL има шест важни операции: създаване на engine от опции `key=value`, стартиране на заявка с нейния заглавен блок (`METHOD URL` плюс редове със заглавия) и дължина на тялото, запис на части от тялото на заявката, изчакване на заглавния блок на отговора, четене на декодираното тяло и унищожаване на дръжките. Дръжката на заявката декларира `@cancel`, така че прекъснато извикване прекратява заявката в Chromium, вместо тя да продължи до край.

Изолирането на стека в работен процес е свойство за безопасност, не само въпрос на опаковане. Мрежовият код на Chromium обработва враждебни входни данни от открития интернет. Ако се срине, вашата Go услуга не се срива заедно с него: извикването завършва с грешка, сесията се изхвърля, а клиентът стартира нов работен процес следващия път, когато е необходим.

## Възпроизводимо изграждане, закрепено чрез SHA-256

Двоичен файл от 25 MB, който съдържа Chromium, е надежден само ако можете точно да кажете какво е вложено в него. `chromenet` следва Chromium Stable по същия начин, по който `gosuda.org/sqlite3` следва SQLite: изходният код никога не се вендорира, а само се закрепва.

| Закрепване                                                                                                                              | Къде се намира                 |
| :-------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------- |
| Издание на Chromium и неговият официален `-lite` архив с изходния код                                                                  | `third_party/chromium/VERSION` |
| SHA-256 на архива и на всеки инструментариум, закрепен от това издание: clang, Rust, GN, ninja, Windows clang runtime, xwin           | `third_party/chromium/SHA256`  |
| Версии на MSVC CRT и Windows SDK, изтеглени през xwin                                                                                  | `third_party/chromium/WINSDK`  |
| Издание на sandbox runtime (`rungv`), дайджест на guest образа, дата на снимката на Debian                                             | `scripts/engine/BUILDENV`      |
| C runtime на HQQ                                                                                                                        | `go.mod` и `go.sum`            |

Текущото закрепване е Chromium `157.0.8103.38`. Изтегляне, което не съвпада със закрепването си, спира изграждането. Изданието прилага точно една корекция към Chromium: на Linux `chrome_net_server` се изгражда без NSS и проверява сертификатите на сървъра само спрямо Chrome Root Store, така че двоичният файл не се нуждае от нищо друго освен libc.

Engine-ите за Linux и Windows се изграждат в [gvx](https://gosuda.org/blog/posts/gvx-cross-platform-linux-containers-z48916d01) sandbox среди: непривилегировани процеси с закрепен guest образ и без host daemon. Engine-ите за Windows се кросскомпилират на Linux с clang-cl и lld-link на Chromium срещу Windows SDK, който xwin изтегля от Microsoft. Engine-ите за macOS се изграждат нативно на macOS runner, защото изграждането на Chromium за macOS изисква Xcode.

Повторното изграждане на същия commit даде побайтово идентични двоични файлове в отделни CI изпълнения. Двоичните файлове на издаденото издание са побайтово идентични с тези, изградени от по-ранно CI изпълнение за commit-а, който е въвел engine-а в монорепото, на всичките шест платформи.

| Платформа       | Размер на двоичния файл |
| :-------------- | ----------------------: |
| `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 |

Engine-ите за Linux зависят само от `libc`, `libm`, `libdl`, `libpthread` и `libgcc_s` и изискват glibc 2.31 или по-нова версия.

### Проследяване на Chromium и издания

Планиран workflow проверява Chromium Stable на всеки шест часа. За ново издание той пренаписва закрепванията, изгражда и тества всичките шест engine-а в клон `chromium/<version>`, атестира двоичните файлове и комитва закрепванията заедно с вградените двоични файлове, техните дайджести и текстовете на лицензите в `main` с един commit. В монорепото файловете `OWNERS` дават на ботa на workflow-а достъп точно до тези файлове, така че актуализацията се внася без човешка стъпка, докато промените в самата рецепта за изграждане (закрепването на Windows SDK и корекцията) все още изискват собственик.

Всяко издадено двоично съдържа атестация за произход на билда от workflow-а, който го е изградил:

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

Текстовете на лицензите за всичко, което съдържа всеки двоичен файл (Chromium, BoringSSL, QUICHE и останалата част от build графа), се генерират от `tools/licenses/licenses.py` на самия Chromium и се разпространяват с изданието като `NOTICE_<os>_<arch>.txt`.

## Ограничения

- `DialWebSocket` е чист Go клиент по RFC 6455 и не използва стека на Chromium.
- `DialWebTransport` е имплементация в същия процес. Той не достига до сървър.
- JA4 и HTTP/2 fingerprints по подразбиране са тези на закрепеното издание на Chromium. Chromium ги променя от издание към издание, а `chromenet` ги следва.
- macOS двоичните файлове са подписани ad-hoc и не са нотаризирани.
- Работният процес харчи памет. Всеки engine двоичен файл е с размер 24 до 28 MB на диска, а runtime-ът на Chromium стартира пул от нишки и мрежова нишка.
- Engine сесия се държи, докато тялото на отговора не бъде затворено. Програма, която не затваря телата на отговорите, ще изчерпи пула.

## Източник

Хранилището е [github.com/gosuda/chromenet](https://github.com/gosuda/chromenet), огледално копие на `//net/chromenet` в монорепото gosuda. Рецептата за изграждане се намира в `scripts/engine`, engine glue кодът в `engine/`, а интерфейсът в `chrome_net.idl`.
