1 poin oleh GN⁺ 2024-03-14 | 1 komentar | Bagikan ke WhatsApp
  • Fly.io mengubah pendekatannya untuk mengurangi beban status pada gateway WireGuard sambil mempertahankan komunikasi langsung antara flyctl dan 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, wggwd mendaftarkannya ke SQLite dan WireGuard kernel Linux, lalu flyctl terhubung
  • 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 initiation melalui 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, flyctl membuat 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 flyctl perlu 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
  • wggwd di 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, flyctl terhubung 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 flyd beralih dari berbasis NATS ke berbasis HTTP
    • Pengurangan penggunaan NATS memperbaiki gateway WireGuard, tetapi belum cukup
  • Peer WireGuard yang dibuat setelah flyctl selesai 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
  • 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 flyctl adalah 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

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 initiation pertama
    • 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 flyctl adalah 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

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

 
GN⁺ 2024-03-14
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

    • Pada akhirnya yang diinginkan adalah Netlink API yang membuat WireGuard kernel mengalirkan daftar kunci publik yang terlihat dalam pesan initiator. Dalam jangka menengah, Jason juga tampaknya ingin menyediakan fitur seperti ini, dan dengan feed tersebut tidak perlu memasang satu pun peer WireGuard sebelumnya
      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
    • Sepertinya ini dilakukan untuk menghindari alternatif menjalankan WireGuard di luar kernel. Linux kernel tidak memiliki fitur untuk melakukan routing terlebih dahulu berdasarkan alamat kriptografis, tetapi mereka tidak ingin keluar dari kernel, jadi bisa dibilang memasukkannya lewat hack
      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

    • Saya punya impian mengambil proyek WireGuard user space, menukar kunci WireGuard dengan PAKE di relay depan, lalu setelah itu membuat tunnel langsung lewat hole punching
      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
    • Saya penasaran apakah Noisy Transport kurang lebih mirip dengan Nebula [0] dari Slack, atau apakah saya yang bingung
      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

    • Saya sangat penasaran dengan detail lebih lanjut. Para maintainer NATS mungkin juga begitu
      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
    • Saya tidak bermaksud menjelekkan NATS. Kemungkinan besar kami yang memakainya secara keliru
      Namun pada akhirnya kami memang tidak membutuhkannya. Lapisan pesan itu bukannya menambah daya ekspresif, malah hanya membuat pengujian dan monitoring lebih sulit
    • Kalau yang dipakai adalah core NATS, setahu saya karena bukan JetStream, memang tidak ada opsi pengiriman ulang sama sekali
  • 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?

    • Dari yang saya baca, kedua peer sama-sama “mengira” bahwa merekalah yang memulai, tetapi dalam praktiknya sepertinya tidak terlalu relevan
      Jadi langkah 3 tidak ada atau tidak perlu ditunggu, dan kalau inisiasi baru pada langkah 2 dicegah, rasanya sudah pasti tidak akan begitu
    • Secara umum benar. Kalau dibayangkan “Bob” punya kebijakan hanya boleh menelepon nomor yang ada di buku alamatnya, bisa dilihat seperti ini
      1. Alice menelepon Bob
        1.a) Bob tidak mengangkat teleponnya, tetapi menambahkan nomor dari caller ID ke buku alamat
      2. Bob menelepon balik nomor itu, yaitu Alice
      3. Alice mengangkat, lalu keduanya mengobrol dengan bahagia
  • 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”

    • Pada dasarnya maksudnya memakai WireGuard ruang pengguna seperti implementasi Go. Ini kebalikan dari WireGuard di dalam kernel
      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
    • Terkait hal itu, saya menulis artikel terpisah lengkap: https://fly.io/blog/our-user-mode-wireguard-year/
    • Maksudnya memakai WireGuard
    • Saya sulit membayangkan CLI yang besar dan layak dicintai
  • 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] = 1 di filter eBPF

    • Tidak ada yang mencegah. Itu ide bagus
      Seperti 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] = 1 hanya memfilter paket handshake. Tanpa itu, paket data juga akan dikirim ke daemon ruang pengguna
      Saya tidak yakin apakah handshake pertama bisa diputar ulang, tetapi karena WireGuard mengabaikan klien yang tidak dikenal, mungkin saja bisa
    • Terdengar seperti helper NFQUEUE yang melepas paket setelah menambahkan kunci
  • 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

    • WireGuard native tentu juga bisa dipakai, dan ada opsi konfigurasinya di flyctl
      Kalau 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

    • Pengalaman deploy-nya luar biasa, tetapi bagi saya fitur pembunuh Fly.io adalah fitur seperti jaringan Anycast, FLY_REPLAY, dan LiteFS. Semua ini membuat clustering menjadi sangat mudah
      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...
    • Fly terlihat bagus, tetapi saya belum sempat mencobanya langsung. Namun Cloud Run dari GCP termasuk tiga besar alat infrastruktur/deploy favorit saya, jadi standarnya memang cukup tinggi
    • Pengalaman saya hampir sama. Saya memakai Fly selama 1 tahun lalu pindah ke GCP satu-dua bulan lalu, dan dalam kasus kami memilih GKE karena ada alasannya
      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/

    • Apakah Netmaker itu seperti Tailscale? Dari melihat situsnya saja, saya kurang paham apa pembeda-nya
    • Sepertinya Netmaker atau alat serupa mengelola key untuk kita, dan kalau begitu pengelolaannya akan jauh lebih mudah
      Di tempat kerja sebelumnya, saya mengatur dan mengelola wg di beberapa mesin Windows dan Linux dengan Ansible. Cukup oke, tetapi pada akhirnya agak berantakan
    • Tidak bisakah ini dilakukan secara AWS-native dengan private link atau VPC peering? Saya kurang paham bidang ini, jadi tidak mengerti manfaat Netmaker
    • Apakah ini platform VPN umum? Saya penasaran apakah mirip dengan Tailscale dan semacamnya
      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

    • Bekerja. Karena UDP NAT hanya mengetahui 4-tuple. Misalnya bentuknya seperti {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
    • Jika paket kembali ke IP/port yang sama dan dibuat dari IP/port yang sama, itu akan bekerja dengan menembus NAT