GoSuda

gosuda.org/chromenet: Мрежовият стек на Chromium за Go, изграден по възпроизводим начин от фиксирана версия

By Lemon Mint views ...

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 RPC през споделена памет.

1go 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
JA4t13d1516h2_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:

 1package main
 2
 3import (
 4	"fmt"
 5	"io"
 6	"net/http"
 7
 8	"gosuda.org/chromenet"
 9)
10
11func main() {
12	client := &http.Client{Transport: chromenet.NewTransport(chromenet.Options{})}
13
14	resp, err := client.Get("https://example.com/")
15	if err != nil {
16		panic(err)
17	}
18	defer resp.Body.Close()
19
20	body, _ := io.ReadAll(resp.Body)
21	fmt.Println(resp.Proto, resp.StatusCode, len(body))
22}

При първата заявка модулът записва вградения работен двоичен файл за вашата платформа в потребителската кеш директория, проверява 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:
1var ne *client.NetError
2if errors.As(err, &ne) {
3	fmt.Println(ne.Code, ne.Name) // -105 ERR_NAME_NOT_RESOLVED
4}

Опции

ПолеЕфект
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Приема всеки сертификат на сървъра.
PoolEngine сесиите, които да се използват. 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 //netURLRequestContext, 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, xwinthird_party/chromium/SHA256
Версии на MSVC CRT и Windows SDK, изтеглени през xwinthird_party/chromium/WINSDK
Издание на sandbox runtime (rungv), дайджест на guest образа, дата на снимката на Debianscripts/engine/BUILDENV
C runtime на HQQgo.mod и go.sum

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

Engine-ите за Linux и Windows се изграждат в gvx 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/amd6427.9 MB
linux/arm6427.4 MB
darwin/amd6426.2 MB
darwin/arm6423.9 MB
windows/amd6427.3 MB
windows/arm6425.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-а, който го е изградил:

1gh 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, огледално копие на //net/chromenet в монорепото gosuda. Рецептата за изграждане се намира в scripts/engine, engine glue кодът в engine/, а интерфейсът в chrome_net.idl.