gosuda.org/chromenet: Stack Jaringan Chromium untuk Go, Dibangun Secara Reproducible dari Rilis yang Dipatok

gosuda.org/chromenet adalah http.RoundTripper Go yang mengirim permintaan melalui stack jaringan milik Chromium sendiri. Stack tersebut adalah //net dari rilis resmi Chromium: implementasi HTTP/1.1, HTTP/2, dan QUIC/HTTP/3, BoringSSL, konfigurasi TLS Chrome, serta Chrome Root Store. Stack ini berjalan dalam proses worker terpisah, chrome_net_server, dan sisi Go tetap murni Go (CGO_ENABLED=0), berkomunikasi dengan worker melalui RPC shared-memory HQQ.
1go get gosuda.org/chromenet@v0.1.10157081033800
Mengapa Menjalankan Stack Jaringan Chromium
Program Go yang berkomunikasi dengan web menggunakan net/http menampilkan dirinya sebagai klien Go kepada server. Itu adalah default yang tepat untuk lalu lintas antarlayanan. Namun, itu menjadi pilihan yang salah ketika perilaku server bergantung pada jenis klien yang dikiranya sedang dihadapi: endpoint yang hanya dapat diakses dari browser, middleware anti-bot, CDN yang menyajikan versi protokol berbeda untuk sidik jari TLS dan HTTP/2 yang berbeda, serta pengujian yang harus mereproduksi pengalaman browser sungguhan.
Pustaka standar Go tidak dapat memberikan perilaku wire browser. crypto/tls memiliki urutan cipher-suite dan kumpulan ekstensi sendiri, dan net/http memiliki pengaturan HTTP/2 sendiri. Pustaka yang meniru browser dengan menyalin byte ClientHello-nya harus terus disalin ulang setiap rilis. chromenet mengambil jalur lain: ia tidak meniru stack jaringan Chromium, melainkan menjalankannya.
Diukur pada engine darwin/arm64 terhadap layanan fingerprint publik (tls.peet.ws), konfigurasi default melaporkan:
| Properti | Nilai yang teramati |
|---|---|
| Protokol yang dinegosiasikan | h2 |
| JA4 | t13d1516h2_8daaf6152771_806a8c22fdea |
| Fingerprint HTTP/2 (format Akamai) | 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p |
| User-Agent | Chrome desktop dengan versi mayor engine, misalnya Chrome/157.0.0.0 |
Dengan ForceHTTP3, permintaan ke cloudflare-quic.com dan www.google.com dijawab melalui HTTP/3. Angka-angka ini berasal dari satu platform pada satu hari. Engine adalah stack itu sendiri, sehingga angka tersebut berubah ketika rilis Chromium yang di-pin berubah.
Cara Menggunakan
NewTransport mengembalikan http.RoundTripper. Gunakan di mana pun Anda akan menggunakan 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}
Pada permintaan pertama, modul menulis binary worker tertanam untuk platform Anda ke direktori cache pengguna, memverifikasi digest SHA-256-nya dengan gosuda.org/hqq/secureexec, menjalankannya dengan lifeline induk (--parent-stdin, sehingga worker keluar ketika proses Anda keluar), dan membuka session pool. Tidak ada yang perlu diinstal.
Beberapa perilaku mengikuti http.Transport agar http.Client dapat bekerja tanpa perubahan:
- Transport tidak mengikuti redirect dan tidak menyimpan cookie.
http.Clientmelakukan keduanya, sebagaimana halnya denganhttp.Transport. - Respons yang dikompresi dengan gzip, deflate, Brotli, atau zstd diterima dalam keadaan sudah didekode, dan
resp.Uncompressedbernilai true. - Sebuah permintaan menahan satu session engine hingga body respons-nya ditutup. Tutup body.
- Membatalkan context permintaan akan membatalkan permintaan di dalam Chromium.
- Kegagalan jaringan direpresentasikan sebagai nilai
*client.NetErroryang membawa kode dan nama error Chromium:
1var ne *client.NetError
2if errors.As(err, &ne) {
3 fmt.Println(ne.Code, ne.Name) // -105 ERR_NAME_NOT_RESOLVED
4}
Opsi
| Field | Efek |
|---|---|
UserAgent | Menggantikan User-Agent Chrome default. Header permintaan User-Agent tetap diutamakan. |
AcceptLanguage | Menggantikan en-US,en;q=0.9. |
Proxy | Aturan proxy Chromium seperti http://127.0.0.1:8080 atau socks5://127.0.0.1:1080. Nilai kosong berarti koneksi langsung. |
DisableHTTP2, DisableQUIC | Menonaktifkan protokol tersebut. |
ForceHTTP3 | Menggunakan QUIC untuk setiap origin tanpa menunggu advertisement Alt-Svc. |
InsecureSkipVerify | Menerima sertifikat server apa pun. |
Pool | Session engine yang digunakan. Nil memakai client.DefaultPool(), yang mengizinkan 64 permintaan bersamaan. |
Transport dengan opsi yang sama berbagi satu URLRequestContext Chromium, sehingga connection pool, session HTTP/2, dan session QUIC digunakan ulang di antara permintaan. Mengatur HQQ_CHROME_NET_SERVER=/path/to/chrome_net_server menggantikan worker tertanam, misalnya dengan build lokal.
Cara Kerjanya
| Lapisan | Yang berjalan di sana |
|---|---|
| Program Anda | http.Client dan chromenet.Transport, murni Go. |
| Batas proses | RPC shared-memory HQQ. Antarmukanya adalah chrome_net.idl, yang dikompilasi oleh hqq-bindgen menjadi klien Go dan glue server C. |
chrome_net_server | Lapisan C++ kecil di atas Chromium: satu network thread yang memiliki setiap objek //net, dan session thread yang memblokir permintaan hingga network thread memberikan jawaban. |
Chromium //net | URLRequestContext, URLRequest, session HTTP/2 dan QUIC, BoringSSL. |
IDL memiliki enam operasi yang penting: membuat engine dari opsi key=value, memulai permintaan dengan head-nya (METHOD URL ditambah baris header) dan panjang body, menulis potongan body permintaan, menunggu head respons, membaca body yang sudah didekode, dan menghancurkan handle. Handle permintaan mendeklarasikan @cancel, sehingga panggilan yang ditinggalkan akan menginterupsi permintaan di dalam Chromium alih-alih membiarkannya berjalan hingga selesai.
Mengisolasi stack dalam proses worker adalah properti keamanan sekaligus properti pengemasan. Kode jaringan Chromium mem-parsing input berbahaya dari internet terbuka. Jika ia crash, layanan Go Anda tidak ikut crash: pemanggilan gagal dengan error, session dibuang, dan klien memulai worker baru saat dibutuhkan lagi.
Dibangun Secara Reproducible, Di-pin dengan SHA-256
Binary 25 MB yang berisi Chromium hanya dapat dipercaya jika Anda dapat menyatakan dengan tepat apa yang masuk ke dalamnya. chromenet mengikuti Chromium Stable sebagaimana gosuda.org/sqlite3 mengikuti SQLite: sumbernya tidak pernah di-vendor, hanya di-pin.
| Pin | Lokasi |
|---|---|
Rilis Chromium dan arsip sumber -lite resminya | third_party/chromium/VERSION |
| SHA-256 dari arsip dan setiap toolchain yang di-pin oleh rilis tersebut: clang, Rust, GN, ninja, runtime clang Windows, dan xwin | third_party/chromium/SHA256 |
| Versi MSVC CRT dan Windows SDK yang diambil melalui xwin | third_party/chromium/WINSDK |
Rilis runtime sandbox (rungv), digest guest image, tanggal snapshot Debian | scripts/engine/BUILDENV |
| Runtime C HQQ | go.mod dan go.sum |
Pin saat ini adalah Chromium 157.0.8103.38. Unduhan yang tidak cocok dengan pin-nya akan menghentikan build. Rilis ini menerapkan tepat satu patch ke Chromium: di Linux, chrome_net_server dibangun tanpa NSS dan memverifikasi sertifikat server hanya terhadap Chrome Root Store, sehingga binary tidak membutuhkan apa pun selain libc.
Engine Linux dan Windows dibangun di dalam sandbox gvx: proses tanpa hak istimewa dengan guest image yang di-pin dan tanpa daemon host. Engine Windows dikompilasi silang di Linux menggunakan clang-cl dan lld-link milik Chromium terhadap Windows SDK yang diambil xwin dari Microsoft. Engine macOS dibangun secara native pada runner macOS, karena build macOS Chromium membutuhkan Xcode.
Membangun commit yang sama menghasilkan binary yang identik byte demi byte di antara CI run yang terpisah. Binary rilis identik byte dengan binary yang dibangun oleh CI run sebelumnya untuk commit yang memasukkan engine ke monorepo, di keenam platform.
| Platform | Ukuran binary |
|---|---|
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 hanya bergantung pada libc, libm, libdl, libpthread, dan libgcc_s, serta membutuhkan glibc 2.31 atau yang lebih baru.
Melacak Chromium dan Rilis
Workflow terjadwal memeriksa Chromium Stable setiap enam jam. Untuk rilis baru, ia menulis ulang pin, membangun dan menguji keenam engine pada branch chromium/<version>, mengattest binary, lalu melakukan commit pin bersama binary tertanam, digest-nya, dan teks lisensi ke main dalam satu commit. Di monorepo, OWNERS per file hanya memberikan bot workflow akses ke file-file tersebut, sehingga pembaruan masuk tanpa langkah manusia, sementara perubahan pada resep build itu sendiri (pin Windows SDK dan patch) tetap membutuhkan owner.
Setiap binary rilis membawa build provenance attestation dari workflow yang membangunnya:
1gh attestation verify chrome_net_server_linux_amd64 --repo gosuda/chromenet
Teks lisensi untuk setiap komponen yang terdapat dalam binary (Chromium, BoringSSL, QUICHE, dan sisa build graph) dihasilkan oleh tools/licenses/licenses.py milik Chromium sendiri dan dikirimkan bersama rilis sebagai NOTICE_<os>_<arch>.txt.
Batasan
DialWebSocketadalah klien RFC 6455 murni Go dan tidak menggunakan stack Chromium.DialWebTransportadalah implementasi in-process. Ia tidak menjangkau server.- Fingerprint JA4 dan HTTP/2 default adalah milik rilis Chromium yang di-pin. Chromium mengubahnya dari rilis ke rilis, dan
chromenetmengikutinya. - Binary macOS ditandatangani secara ad-hoc dan tidak dinotarisasi.
- Sebuah proses worker memakan memori. Setiap binary engine berukuran 24 hingga 28 MB di disk, dan runtime Chromium memulai thread pool serta network thread.
- Session engine ditahan hingga body respons ditutup. Program yang membocorkan body respons akan menghabiskan pool.
Sumber
Repositori berada di github.com/gosuda/chromenet, dicerminkan dari //net/chromenet di monorepo gosuda. Resep build berada di scripts/engine, glue engine di engine/, dan antarmuka di chrome_net.idl.