- Fly.io mengubah pendekatannya untuk mengurangi beban status pada gateway WireGuard sambil mempertahankan komunikasi langsung antara
flyctldan Fly Machines: peer tidak lagi dipasang lebih dulu, melainkan ditambahkan ke kernel saat koneksi terjadi - Alur lama adalah GraphQL API mengirim konfigurasi peer melalui NATS RPC,
wggwdmendaftarkannya ke SQLite dan WireGuard kernel Linux, laluflyctlterhubung - Karena pesan NATS hilang berbarengan dengan pembuatan peer sekali pakai dari pekerjaan CI, ratusan ribu peer yang tidak pernah dipakai ulang menumpuk di gateway, membuat operasi kernel dan pemuatan saat reboot menjadi lambat
- Pendekatan baru menangkap paket
handshake initiationmelalui filter BPF atau jalur penerimaan WebSockets, mendekripsi sebagian handshake Noise untuk mengidentifikasi public key, lalu mengambil hanya peer yang diperlukan melalui API HTTP internal - Setelah diterapkan di operasi produksi selama beberapa minggu, jumlah peer lama hampir hilang, dan gateway mampu menangani konfigurasi peer serta reboot lebih cepat dengan status yang lebih sedikit
Cara Fly.io menggunakan WireGuard
- Fly.io menjalankan container sebagai VM berbasis Firecracker, dan memanfaatkan WireGuard di banyak tempat sebagai bagian dari API pelanggan
- Saat berjalan,
flyctlmembuat stack TCP/IP dengan alamat IPv6-nya sendiri, lalu berkomunikasi langsung dengan Fly Machines di jaringan Fly.io - Pendekatan ini memudahkan fitur seperti remote Docker builder ditampilkan seolah-olah berada di LAN yang sama, tetapi lebih sulit untuk terus dioperasikan secara andal
- Fly.io akhirnya mengubah jalur default menjadi WireGuard-over-WebSockets
Alur provisioning gateway lama
- Fly.io menghubungkan koneksi WireGuard yang masuk ke beberapa server gateway di seluruh dunia ke jaringan privat yang sesuai
- Ketika
flyctlperlu berkomunikasi dengan Fly Machine untuk build container, konsol SSH, penyalinan file, atau proxy layanan, ia menjalankan atau menghubungkan proses agen latar belakang - Saat pertama kali dijalankan, agen membuat konfigurasi peer WireGuard baru dari GraphQL API
- Konfigurasi peer terdiri dari public key dan alamat yang akan dihubungkan
- API mengirim konfigurasi tersebut ke gateway yang sesuai sebagai RPC melalui sistem messaging NATS
wggwddi gateway menerima konfigurasi, menyimpannya ke SQLite, menambahkannya ke kernel lewat library WireGuard Go, lalu merespons API bahwa instalasi selesai- Setelah API mengembalikan konfigurasi pada permintaan GraphQL,
flyctlterhubung sebagai peer WireGuard yang sudah terpasang di gateway
Mengapa arsitektur lama melambat
- NATS memang cepat, tetapi tidak menjamin pengiriman sehingga sulit dipakai sebagai API yang andal
- Fly.io mengurangi penggunaan NATS secara internal; misalnya API internal
flydberalih dari berbasis NATS ke berbasis HTTP - Pengurangan penggunaan NATS memperbaiki gateway WireGuard, tetapi belum cukup
- Fly.io mengurangi penggunaan NATS secara internal; misalnya API internal
- Peer WireGuard yang dibuat setelah
flyctlselesai tetap berada di gateway, dan tidak ada proses untuk membersihkan peer lama- Ada pilihan untuk tidak menghapus peer karena ada kemungkinan deploy lagi keesokan hari atau debugging dengan
fly ssh console - Namun sebagian besar peer dibuat oleh pekerjaan CI tanpa persistent storage, sehingga pada eksekusi berikutnya tidak bisa tersambung kembali dengan peer yang sama dan selalu membuat peer baru
- Ada pilihan untuk tidak menghapus peer karena ada kemungkinan deploy lagi keesokan hari atau debugging dengan
- Akibatnya gateway menyimpan ratusan ribu peer yang mungkin tidak pernah dipakai ulang
- Ketika jumlah peer lama bertambah, operasi WireGuard kernel menjadi sangat lambat
- Proses memuat ulang semua peer ke kernel setelah server gateway reboot sangat lambat
- Beberapa kernel panic juga terjadi
Desain memasang peer ke kernel hanya saat diperlukan
- Menyimpan seluruh riwayat peer WireGuard dalam satu SQLite tidaklah sulit, tetapi mempertahankan semua peer di kernel Linux menjadi bottleneck
- Alih-alih mendorong konfigurasi ke gateway, Fly.io memilih pendekatan di mana gateway mengambil secara on-demand peer yang diperlukan dari API
- Jika peer hanya ditambahkan ke kernel saat klien mencoba terhubung, peer lama bisa dihapus dari kernel kapan saja
- Peer yang telah dihapus cukup diambil dan dipasang lagi pada koneksi berikutnya, sehingga kebutuhan gateway untuk terus menyimpan status jangka panjang berkurang
- Namun WireGuard kernel Linux tidak memiliki API untuk berlangganan event “incoming connection attempt”
Cara implementasi peer JIT WireGuard
- Antarmuka konfigurasi WireGuard di kernel Linux adalah Netlink, dan library kontrol WireGuard Go menggunakan wgctrl-go
- Fly.io memanfaatkan fakta bahwa permintaan koneksi WireGuard adalah paket yang dapat diidentifikasi, lalu membuat event sendiri secara langsung dengan filter BPF dan packet socket
- Pada jalur WireGuard WebSockets, paket WireGuard mentah bisa diperoleh lebih mudah
- Jalur ini mengirim dan menerima paket UDP yang dibingkai melalui koneksi WebSockets tanpa autentikasi dengan antarmuka gateway
- Karena Fly.io memiliki kode daemon tersebut, mereka bisa memasang hook pada fungsi penerimaan paket
- WireGuard tidak memiliki konsep “klien” dan “server”; ia adalah protokol point-to-point yang menghubungkan peer saat mengirim traffic
- Pihak yang terhubung lebih dulu adalah initiator, dan pihak lawannya adalah responder
- Di Fly.io, biasanya
flyctladalah initiator dan gateway adalah responder
- Paket UDP pertama menurut makalah WireGuard adalah
handshake initiation, dan tipe paket dicatat dalam 1 byte plaintext- Fly.io menangkap koneksi masuk dengan filter BPF
udp and dst port 51820 and udp[8] = 1
- Fly.io menangkap koneksi masuk dengan filter BPF
Mengidentifikasi peer dari handshake Noise
- WireGuard berbasis Noise Protocol Framework, dan Noise menyembunyikan identifier untuk identity hiding selama handshake
- Karena itu, pendekatan membaca nilai seperti nama pengguna dari paket lalu langsung mencari konfigurasinya tidak bisa digunakan
- Fly.io menjalankan sebagian enkripsi Noise untuk mendekripsi identitas agar dapat mengidentifikasi permintaan masuk
- Kode ini cukup rumit, tetapi hanya sekitar 200 baris
- Antarmuka Netlink kernel dapat memberikan private key antarmuka kepada proses yang memiliki hak akses, sehingga secret yang diperlukan bisa diperoleh
- Kode terkait dipublikasikan sebagai gist
- Melalui proses ini, gateway bisa memperoleh feed event public key dari pengguna yang mencoba membuat koneksi WireGuard
Optimasi instalasi, cache, dan retry
- Gateway mempertahankan cache rate limit di SQLite, dan ketika menemukan peer baru, ia mengambil dan memasang informasi peer yang sesuai melalui permintaan API HTTP internal
- Logika ini cocok dimasukkan ke daemon kecil yang sebelumnya mengelola WireGuard di gateway
- Peer lama bisa dihapus secara aktif dengan pekerjaan
cron - Pencarian API untuk peer baru mungkin tidak cukup cepat untuk langsung merespons pesan
handshake initiationpertama- WireGuard melakukan retry dengan cepat, sehingga tidak menjadi masalah bagi fungsinya
- Dengan fitur Netlink WireGuard Linux yang diinformasikan Jason Donenfeld, koneksi bisa dibuat lebih cepat
- Dari pesan initiation yang masuk, gateway memperoleh alamat 4-tuple termasuk port sumber sementara milik
flyctl - Gateway memasang peer seolah-olah dirinya adalah initiator dan
flyctladalah responder - Kernel Linux memulai koneksi WireGuard ke sisi
flyctl, dan protokol ini tidak terlalu bergantung pada peran server dan klien - Koneksi baru terbentuk mendekati kecepatan saat peer dapat dipasang
- Dari pesan initiation yang masuk, gateway memperoleh alamat 4-tuple termasuk port sumber sementara milik
Hasil penerapan operasional
- Pendekatan ini telah berjalan di produksi selama beberapa minggu
- Jumlah peer WireGuard lama yang sebelumnya berkisar dari ribuan hingga ratusan ribu per gateway menjadi hampir mendekati 0
- Status yang perlu disimpan gateway berkurang
- Konfigurasi peer menjadi lebih cepat
- Saat reboot, kebutuhan untuk memuat ulang peer yang tidak digunakan ke kernel berkurang
1 komentar
Opini Hacker News
Saya kurang paham pernyataan bahwa WireGuard di kernel Linux tidak memiliki fitur untuk memasang peer saat diperlukan. Sepertinya peer bisa ditambahkan bahkan saat runtime: https://serverfault.com/questions/1101002/wireguard-client-a...
Jika pemahaman saya benar, tahap itu sudah terlambat, dan tampaknya mereka ingin melakukan autentikasi sebelum menambahkan peer agar entri lama tidak tertinggal di antarmuka
Jadi strukturnya tampak seperti menaruh filter eBPF di depan antarmuka, mencoba menghubungi langsung apakah pihak lawan disetujui berdasarkan routing kunci kriptografis, lalu jika lolos menambahkan peer ke antarmuka dan menghapusnya setelah timeout
Semua peer bisa berada di tempat seperti SQLite, lalu dipasang sesuai kebutuhan ketika klien mencoba terhubung
Dari sudut pandang penyedia VPN, API saat ini agak kaku. Memang pada praktiknya hanya sebagian peer yang sedang dipakai pada suatu waktu, tetapi ketika jumlah peer membesar dari ratusan ribu ke puluhan juta, menyimpan semuanya dalam satu instans kernel menjadi tidak mungkin
Jika peer harus dipasang sebelumnya, pada akhirnya ia akan terikat ke mesin server tertentu
Seperti yang dikatakan artikel itu, sekarang pun kita bisa membuat sesuatu yang mirip antarmuka yang dibutuhkan dengan packet capture sederhana, dan berkat Jason yang merancang API dengan baik, arah inisiasi server dan klien bisa dibalik dengan sangat mudah. Meski kernel membuang pesan inisiasi pertama, pengguna akan merasa seolah koneksinya tersambung mulus
Jann Horn mengatakan bahwa satu langkah lebih jauh, paket inisiasi yang ditangkap juga bisa disimpan lalu disuntikkan kembali ke kernel setelah peer dipasang, dan itu juga ide yang cukup bagus
Menurut saya tulisan ini bukan sesuatu yang sampai mengubah hidup, melainkan lebih seperti beberapa trik rapi yang akan bagus untuk diketahui orang
Langkah berikutnya adalah membuat floating peers berdasarkan ini, sehingga peer sepenuhnya dipisahkan dari lokasi. Dengan begitu pengguna tidak perlu peduli peer disetel di region mana, dan ini tampaknya punya manfaat produk nyata, bukan sekadar hiburan untuk para geek
Ungkapan JIT WireGuard terasa agak aneh. Pikiran pertama saya adalah, “kenapa? Bottleneck performanya adalah kriptografi, dan JIT per klien tidak akan membantu di situ”
Kalau saya, mungkin saya akan langsung ke user space. Tinggal pakai sesuatu seperti tokio-uring atau glommio untuk mengejar performa
Jika terus memaksakan di dalam kernel, mereka akan terus menabrak batas karena Linux tidak dibuat untuk menangani jutaan tunnel aktif. Bahkan jutaan koneksi TCP dalam satu kernel saja kadang merepotkan
Setiap batas membutuhkan hack, dan setiap hack melahirkan konfigurasi sistem yang harus diterapkan dan dikelola. Toolchain provisioning server fisik Linux jauh tertinggal dibanding tool untuk pengembangan aplikasi/layanan dan manajemen konfigurasi
Atau mungkin saya bodoh dan salah memahami sesuatu?
Jika ingin membuat peer WireGuard user space di aplikasi Go, boleh lihat proyek eksperimen terbaru https://github.com/dpeckett/noisysockets
Ini dibangun di atas kerja luar biasa wireguard-go, tetapi berusaha membuatnya lebih sederhana untuk dipakai sebagai library dan lebih idiomatis Go
Sepertinya menarik kalau ini dipakai untuk membuat service mesh. Mendukung banyak bahasa akan sulit, tetapi mungkin bisa juga mengimplementasikan socket API
Namun saya belum melihat akselerasi hardware untuk kriptografi WireGuard, jadi dari sisi performa mungkin sulit bersaing dengan mTLS
Sebagai catatan, saat ini saya sedang mencari pekerjaan freelance, jadi jika membutuhkan freelancer Golang di bidang networking berkecepatan tinggi dan aman, silakan hubungi saya
Semacam Magic Wormhole untuk tunnel arbitrer, dan saya berharap ini juga bisa sangat memperbaiki masalah transfer file di jaringan panjang berbandwidth tinggi yang ambruk di 20–30 MB/s
0 - https://github.com/slackhq/nebula
Saya secara umum setuju bahwa untuk pesan point-to-point tunggal, request HTTP langsung bisa lebih andal daripada lewat message queue, tetapi agak mengejutkan bahwa begitu banyak pesan hilang di NATS sampai berdampak besar pada layanan
Jika pesan hilang, bukankah NATS akan mengirim ulang sampai berhasil? Saya penasaran apakah ada yang tahu mengapa mereka mengalami ketidakstabilan yang sampai terasa
Struktur NATS intuitif dan menarik, jadi saya ingin tahu di mana letak masalahnya. JetStream memiliki banyak parameter yang bisa disetel
Misalnya stream berbasis memori dengan jendela deteksi duplikat berbasis waktu, mode push/pull, serta pengaturan kebijakan pengiriman ulang dan acknowledgment
Namun mungkin memang tidak cocok dengan koneksi satu pesan sekali pakai. Bagaimanapun, detail yang lebih spesifik akan sangat berguna
Namun pada akhirnya kami memang tidak membutuhkannya. Lapisan pesan itu bukannya menambah daya ekspresif, malah hanya membuat pengujian dan monitoring lebih sulit
Bagian yang mengatakan “kami menyiapkan peer seolah-olah kamilah initiator dan menjadikan flyctl sebagai responder. Kernel Linux memulai ulang koneksi WireGuard ke sisi flyctl” itu pada dasarnya menambahkan latensi setengah round-trip ke handshake, ya?
Misalnya, apakah alurnya seperti 1) flyctl mengirim Initiation, 2) peer ditambahkan lewat netlink lalu Initiation baru dikirim, 3) Response dikirim dari flyctl?
Jadi langkah 3 tidak ada atau tidak perlu ditunggu, dan kalau inisiasi baru pada langkah 2 dicegah, rasanya sudah pasti tidak akan begitu
1.a) Bob tidak mengangkat teleponnya, tetapi menambahkan nomor dari caller ID ke buku alamat
Saya tidak paham maksud kalimat “setiap kali menjalankan flyctl, CLI kami yang besar dan menyenangkan itu menciptakan stack TCP/IP dari kehampaan, punya alamat IPv6 sendiri, dan berkomunikasi langsung dengan Fly Machines yang berjalan di jaringan kami”
Alasan disebut “menciptakan stack TCP/IP dari kehampaan” adalah karena biasanya sistem operasi menyediakan stack TCP/IP sebagai bagian dari kernel
Di wireguard-go, stack TCP/IP berjalan di ruang pengguna, jadi bisa dibuat di dalam proses ruang pengguna biasa seperti antarmuka baris perintah flyctl
Bagi orang yang sudah lama berkutat dengan sistem, ini bisa terlihat cukup ajaib. Stack TCP/IP ruang pengguna di dalam proses yang benar-benar bisa dipakai relatif masih baru dan terasa segar
Saya penasaran apa yang mencegah paket handshake pertama disuntikkan kembali ke network stack. Kalau begitu sepertinya tidak ada packet loss
Saya juga penasaran tujuan memeriksa
udp[8] = 1di filter eBPFSeperti yang disebutkan di komentar sebelah, filter BPF hanya menangkap paket inisiasi, dan memang itulah perilaku yang diinginkan. Ini versi WireGuard dari mengendus SYN untuk melihat awal koneksi TCP
udp[8] = 1hanya memfilter paket handshake. Tanpa itu, paket data juga akan dikirim ke daemon ruang penggunaSaya tidak yakin apakah handshake pertama bisa diputar ulang, tetapi karena WireGuard mengabaikan klien yang tidak dikenal, mungkin saja bisa
Menarik bahwa secara default WireGuard ditunnel di atas WebSocket. Tidak bagus untuk performa, tetapi rasanya cukup untuk pekerjaan ala DevOps yang memakai flyctl
Saya juga penasaran soal ini ketika memikirkan masa depan QUIC/HTTP3. Kemungkinan operator jaringan memilih memblokir port UDP 443 sama sekali daripada menanganinya dengan benar juga bukan nol
flyctlKalau UDP tidak jalan, benar-benar tidak jalan dan sulit di-debug, jadi default-nya kami pilih ke sisi yang kami tahu pasti bekerja
Agak pahit rasanya karena saya kalah dalam perdebatan soal default mana yang dipilih
Startup saya memakai Fly hampir 1 tahun. Fitur intinya yang mengubah kode menjadi kode yang ter-deploy dalam waktu kurang dari 1 menit benar-benar indah
Menaikkan dan menurunkan node baru untuk backfill juga hanya butuh beberapa detik
Tetapi perusahaannya sendiri terasa agak belum matang. Pernah sekali server API tidak bisa diakses di Fly selama 48 jam, dan saya tidak yakin apakah itu kesalahan konfigurasi saya atau outage “senyap” lainnya
Mereka punya produk “db”, tetapi posisinya seperti “bukan Postgres terkelola”, dan di situ juga terus terjadi putus-sambung
Rasanya aneh mereka menambahkan Postgres sebagai nomina tingkat atas di CLI, tetapi membatasi cakupan fitur yang didukung
Akses ke API layanan inti juga sering down sehingga kami harus menunggu untuk men-deploy perubahan layanan baru
Saya merindukan pengalaman deploy-nya, tetapi jujur saat ini saya lebih puas dengan Cloud Run di GCP. “Kejutan”-nya jauh lebih sedikit dan dokumentasinya jauh lebih matang
Saya heran penyedia VPS hampir tidak memberi dukungan untuk mengurangi latensi layanan backend bagi pengguna. Tidak ada yang mendukung Anycast, dan opsi GeoDNS juga sangat sedikit
Namun GeoDNS menambahkan kompleksitas tersendiri
Saya berharap biaya transfer data Fly.io lebih murah. Saat ini, untuk layanan mirip ngrok yang sedang saya kerjakan, saya harus mengimplementasikan ulang cukup banyak fitur Fly.io secara kikuk
[0]: https://lastlogin.io
[1]: Kode khusus Fly yang diperlukan untuk menjalankan LastLogin secara terdistribusi global kira-kira hanya sebanyak ini: https://github.com/lastlogin-io/obligator/blob/37f75cc861f1b...
Ketika berjalan baik, rasanya benar-benar mulus, tetapi frekuensinya tidak cukup sering
Saya ingin memperkenalkan Netmaker[0] pada kesempatan ini
Saya bukan pihak terkait, hanya pengguna yang puas karena membutuhkan akses privat ke AWS VPC lintas beberapa akun. Saya berharap adopsinya lebih luas
[0] https://www.netmaker.io/
Di tempat kerja sebelumnya, saya mengatur dan mengelola wg di beberapa mesin Windows dan Linux dengan Ansible. Cukup oke, tetapi pada akhirnya agak berantakan
Situsnya terlalu ambigu
Bagian “gateway dengan ratusan ribu peer, dan peer-peer yang tidak akan pernah dipakai lagi” persis seperti yang terlintas saat membaca paragraf-paragraf awal
Ide “Tidak ada panggilan API untuk berlangganan event percobaan koneksi masuk. Tidak apa-apa. Kita buat event sendiri. Permintaan koneksi WireGuard adalah paket dan mudah diidentifikasi, jadi bisa ditangkap secara efisien dengan filter BPF dan packet socket” juga bagus
Katanya, ketika menerima pesan inisiasi masuk, mereka mendapatkan alamat 4-tuple dari koneksi yang diinginkan, termasuk port sumber sementara yang dipakai flyctl, lalu memasang peer seolah-olah kita adalah initiator dan flyctl adalah responder. Saya penasaran apakah ini juga bekerja di balik NAT
{wggwd.fly.io, 12345, clientIP, 23456}Baik paket UDP “initiator” baru maupun respons terhadap pesan inisiasi keluar akan terlihat persis sama bagi UDP NAT di sepanjang jalur
Karena dasar penilaiannya hanya 4-tuple, dan 4-tuple itu sama