- NAT IPv4 biasanya membedakan tujuan balasan berdasarkan port TCP/UDP, tetapi ICMP echo milik
pingtidak memiliki port, sehingga kuncinya adalah nilai apa yang dipakai Linux sebagai kunci pemetaan - Eksperimen mereproduksi konfigurasi NAT dari 192.168.99.0/24 ke 10.0.100.0/24 dengan membuat
client1,client2,natbox, danservermenggunakan network namespace, sertaiptablesMASQUERADE - Dengan membandingkan RFC 792 dan packet capture, terlihat bahwa Identifier dan Sequence Number pada ICMP echo dipakai untuk mencocokkan request dan response, dan pada jalur ICMP
SOCK_DGRAMLinux, local port socket dimasukkan sebagai ID - Jika dua klien memakai ICMP ID
999yang sama, netfilter akan mengubah ICMP ID salah satunya menjadi nilai acak untuk menghindari benturan, lalu saat balasan datang nilainya dikembalikan ke IP dan ID klien semula - NAT Linux, bahkan untuk ICMP tanpa port, menyimpan status arah asal dan arah balasan dalam tuple conntrack, lalu memakai ICMP ID sebagai kunci yang bisa dimanipulasi agar balasan dipetakan ke host internal yang benar
Lingkungan eksperimen dan konfigurasi NAT
- Untuk meniru beberapa perangkat dalam satu mesin Linux, digunakan network namespace
- Dua klien,
natboxyang berperan sebagai router NAT, dan server dibuat sebagai namespace terpisah, dengan jaringan privat dan jaringan sisi server dipisahkanclient1:192.168.99.1/24client2:192.168.99.2/24- antarmuka internal
natbox:192.168.99.3/24 - antarmuka eksternal
natbox:10.0.100.1/24 server:10.0.100.2/24
ip,iptables,tcpdump, dan lainnya dijalankan sebagai root pada Fedora 38 Server VM dengan Linux kernel 6.2.9- Kedua klien terhubung ke bridge
br0, dannatboxterhubung masing-masing ke bridge dan veth pair ke sisi server - Rute default klien diset ke
192.168.99.3agar trafik menuju server melewatinatbox - Di
natbox, packet forwarding diaktifkan dengannet.ipv4.ip_forward=1, lalu aturan MASQUERADE ditambahkan ke chainPOSTROUTINGpada tabelnatmilikiptablesip netns exec natbox iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
NAT ICMP dilihat lewat packet capture
- Paket ICMP ditangkap dengan
tcpdump -n icmpdi namespaceclient1danserver - Di sisi klien terlihat echo request
192.168.99.1 > 10.0.100.2dan echo reply10.0.100.2 > 192.168.99.1 - Di sisi server, alamat IP sumber dari request yang sama berubah menjadi
10.0.100.1, yang menegaskan bahwa NAT menulis ulang alamat sumber ke IP eksternal miliknatbox - Request ICMP dari klien yang berbeda memiliki field id yang berbeda
- Pada contoh,
client1memakai ID31428 client2memakai ID33391
- Pada contoh,
- Pengamatan ini menunjukkan bahwa
natboxdapat memanfaatkan field ID saat mengembalikan respons ICMP ke klien internal
RFC 792 dan ICMP ID pada ping
- ICMP adalah protokol lama yang didefinisikan dalam RFC 792, diterbitkan pada 1981
- Pesan ICMP echo dan echo reply memiliki Type, Code, Checksum, Identifier, Sequence Number, dan Data
- Type membedakan echo request dan echo reply
- Type untuk echo request adalah
8 - Pada kutipan RFC, echo reply tertulis sebagai
1 - Code adalah
0
- Type untuk echo request adalah
- RFC 792 menjelaskan bahwa Identifier dan Sequence Number dapat dipakai untuk mencocokkan echo request dan reply
- Identifier dapat dipakai untuk mengidentifikasi sesi seperti port pada TCP/UDP, dan Sequence Number dapat bertambah untuk setiap echo request
- Karena RFC tidak mengatur bagaimana ID harus dipilih dalam praktik, perlu melihat source code implementasinya, yaitu
ping
Cara ID ditentukan di iputils ping
- Perintah
pingtermasuk dalam paket iputils - Komentar di sekitar
ping4_send_probemenjelaskan bahwa saat membuat ICMP echo request, field ID adalah angka acak dan Sequence Number adalah bilangan bulat yang terus bertambah - Di dalam
pingada fieldidentpadastruct ping_rts- Nilai default-nya adalah
-1 - Bisa ditimpa lewat opsi CLI
-edengan nilai dari0sampaiIDENTIFIER_MAX, yaitu0xFFFF
- Nilai default-nya adalah
- Jika
rts->ident == -1,pingmelakukan bind socket dengan tipeSOCK_DGRAMdan protokolIPPROTO_ICMP - Menurut penjelasan socket
IPPROTO_ICMPdi Linux, header ICMP akan diperiksa dan dirapikan saatsend()dipanggil, dan id diisi dengan nomor local port socket - Jika
pingtidak menentukan source port, kernel Linux akan memilih port kosong secara acak, dan port itulah yang tampaknya dipakai sebagai ID paket ICMP
Saat ICMP ID yang sama bertabrakan
- Dari dua klien,
ping -e 999digunakan untuk mengirim ping ke server dengan ICMP ID 999 yang sama - Dari hasil capture di server, request dari satu klien tetap memakai ID
999, tetapi request klien lain berubah menjadi ID30218 - Perangkat NAT mengubah salah satu ID agar kombinasi IP eksternal yang sama dan ICMP ID yang sama tidak bentrok
- Untuk mencari lokasi penanganan benturan ini, source code di direktori
net/netfilterLinux diperiksa untuk menemukan bagian yang memakai fieldidpada ICMP
Peran netfilter, conntrack, dan NAT
- Subsystem kernel yang mengimplementasikan aturan
iptablesadalah netfilter - Karena aturan MASQUERADE menjalankan NAT, implementasi NAT untuk ICMP juga ada di dalam netfilter
nf_nat_setup_infodinf_nat_core.cmemanggilget_unique_tuple, yang kemudian berlanjut kenf_nat_l4proto_unique_tuple- Di
nf_nat_l4proto_unique_tupleada kasusIPPROTO_ICMPyang merujuk ketuple->src.u.icmp.id nf_nat_manip_pktdinf_nat_proto.cmemanggilnf_nat_ipv4_manip_pktdanl4proto_manip_pkt, lalu untuk ICMP akan memanggilicmp_manip_pkticmp_manip_pktmenulis ICMP ID yang sebenarnya ke paket melaluihdr->un.echo.id = tuple->src.u.icmp.id
Cara ICMP direpresentasikan dalam tuple conntrack
- Connection pada netfilter tidak hanya berarti koneksi TCP, tetapi juga status yang menghubungkan paket keluar dan paket masuk untuk protokol tanpa koneksi seperti UDP atau ICMP
nf_connmemilikituplehash[IP_CT_DIR_MAX]IP_CT_DIR_ORIGINAL: arah paket keluarIP_CT_DIR_REPLY: arah respons masuk
- Setiap
nf_conntrack_tuple_hashmemilikinf_conntrack_tupleyang mengidentifikasi connection - Tuple dibagi menjadi
srcyang bisa dimanipulasi dandstyang tidak berubahsrcberisi alamat IP dan field khusus protokol- Untuk ICMP, field khusus protokolnya adalah
__be16 id dstberisi alamat IP yang tidak diubah serta ICMPtypedancode
- NAT menyimpan di dalam connection bagaimana paket keluar diubah, lalu membalikkan perubahan itu pada paket respons
Jalur kode yang memilih ICMP ID
- Saat
natboxmenerima ICMP echo,nf_nat_setup_infomembuat connection baru dan memutuskan apakah alamat IP sumber serta ICMP ID perlu diubah - Setelah itu, untuk setiap paket ICMP,
nf_nat_manip_pktakan menetapkan alamat IP sumber atau tujuan serta ICMP ID berdasarkan nilai yang tersimpan di connection get_unique_tupleadalah jalur utama untuk memilih tuple NAT yang tersediafind_best_ips_protomenulis ulang alamat IP sumbernf_nat_used_tuplememeriksa apakah tuple itu sudah dipakai, dan jika belum maka tuple saat ini dikembalikan apa adanya- Karena itu, jika ICMP ID dua klien berbeda, ID tersebut dipertahankan juga pada paket hasil NAT
- Jika tuple itu sudah dipakai,
nf_nat_l4proto_unique_tupledipanggil untuk menjalankan NAT spesifik protokol
- Untuk ICMP,
tuple->src.u.icmp.iddipilih sebagai kunci target NAT find_free_idmembuat ID acak denganget_random_u16(), menyesuaikannya ke rentang ICMP ID yang valid, lalu memeriksa apakah ID itu sudah dipakai- Rentang ID default adalah seluruh rentang ID, dan pada aturan
iptablesMASQUERADE bisa ditentukan rentang seperti100-200lewat--to-ports - Jika tuple yang belum dipakai tidak dapat ditemukan, ID duplikat akan tetap berada di connection, dan kemudian
__nf_conntrack_confirmmendeteksi duplikasi itu lalu menjatuhkan paket
Memverifikasi perilaku kernel dengan bpftrace
- Untuk memverifikasi pemahaman tentang cara kerja netfilter tersebut, digunakan
bpftrace - Fungsi kernel yang dilacak adalah
nf_nat_setup_infodannf_nat_manip_pkt kprobemelacak saat fungsi dipanggil, dankretprobemelacak saat fungsi mengembalikan hasil- Karena
kretprobetidak bisa langsung mengakses argumen fungsi, argumen disimpan lebih dulu di BPF map saat masuk lalu dibaca lagi saat return struct sk_buffadalah struktur yang dipakai kernel Linux untuk merepresentasikan paketbswapdipakai untuk mengubah network byte order yang berupa big endian menjadi little endianntopmengubah alamat IP menjadi string- Berkat BPF Type Format (BTF) pada kernel Linux modern, program BPF dapat merujuk ke struktur kernel seperti
sk_buffdannf_conntanpa menyertakan header - Program
bpftraceini diuji pada Linux kernel 6.2.9, dan perilakunya mungkin berbeda pada versi kernel lain
Hasil pelacakan dan kesimpulan
- Saat dua klien mengirim ping dengan ICMP ID
999yang sama,nf_nat_setup_infodipanggil satu kali untuk masing-masing klien - Untuk klien pertama
192.168.99.1, baik original tuple maupun reply tuple sama-sama mempertahankan ICMP ID999 - Untuk klien kedua
192.168.99.2, ICMP ID pada reply tuple ditulis ulang menjadi32809 nf_nat_manip_pktmengganti alamat IP sumber menjadi10.0.100.1pada echo request denganNF_NAT_MANIP_SRC, dan pada respons mengembalikan alamat IP tujuan ke IP klien asal denganNF_NAT_MANIP_DST- ICMP ID pada paket respons juga dipulihkan dari nilai hasil NAT ke nilai asli yang dikirim klien
- Timeout conntrack ICMP default dapat dilihat di
/proc/sys/net/netfilter/nf_conntrack_icmp_timeout, dan nilai default yang diamati adalah 30 detik - Jika klien tidak mengirim paket selama lebih dari 30 detik,
nf_nat_setup_infoakan dipanggil lagi pada ping berikutnya - Perilaku NAT untuk ping di Linux juga didokumentasikan dalam Netfilter Hacking HOWTO, dan intinya terletak pada tuple conntrack serta penulisan ulang ICMP ID
1 komentar
Komentar Hacker News
Saat server mulai berjalan, ia mulai mengirim paket ICMP echo request tetap ke alamat tetap 3.3.3.3, dan paket ini diharapkan tidak akan kembali
3.3.3.3 bukan host yang dapat dijangkau dan juga bukan target untuk di-spoof. Sebaliknya, ketika klien ingin terhubung, ia mengetahui IP server, sehingga ia mengirim paket ICMP Time Exceeded ke server. Di dalam paket ICMP itu terdapat paket tetap “asli” yang dikirim server ke 3.3.3.3, dan paket hardcoded ini berfungsi sebagai pengenal pwnat
Klien seolah-olah berpura-pura menjadi satu hop di internet dan memberi tahu bahwa “ICMP echo request” asli dari server tidak dapat diteruskan. NAT melihat bahwa paket di dalam ICMP Time Exceeded cocok dengan paket yang dikirim server, lalu meneruskannya ke server di belakang NAT; karena pada saat itu seluruh header IP klien ikut disertakan, server dapat mengetahui alamat IP klien
Perilaku inti alat ini setelah itu adalah membuat tunnel UDP antara klien dan server
Namun dari yang saya lihat sekilas, sepertinya ia berasumsi bahwa NAT tidak menulis ulang port sumber UDP, jadi kemungkinan tidak akan bekerja di semua router. STUN, yang digunakan antara lain di WebRTC, menerapkan teknik yang lebih canggih, tetapi jika tetap tidak bisa, harus memakai TURN sebagai relay
Masalah yang sama sangat mungkin juga berlaku pada trik ping 3.3.3.3. Jika NAT menulis ulang pengenal ping seperti dalam artikel, trik ini akan rusak
Saat menerima respons, router menggunakan nilai ID unik itu untuk meneruskan respons ke perangkat yang tepat di jaringan lokal
Ini bisa diperiksa hanya dengan satu komputer dan Wireshark/tcpdump
Artikelnya sendiri bagus, dan bisa menjadi momen pencerahan bagi orang yang sama sekali belum memahami networking. Namun pada dasarnya ini lebih terasa seperti cara membangun lab jaringan yang benar dan menelusuri source, ketimbang memikirkannya sendiri secara langsung
Contoh yang bisa diikuti langsung seperti ini sangat saya hargai, dan saya berniat mencobanya sendiri
Hampir satu-satunya artikel lain tentang topik ini yang benar-benar mudah saya pahami adalah artikel Tailscale ini. Banyak “contoh yang dijabarkan”, sehingga menjadi jelas bagaimana semuanya saling terhubung
https://tailscale.com/blog/how-nat-traversal-works/
Kebetulan akhir pekan ini saya bergulat dengan Netfilter untuk menyalakan proxy transparan di router OpenWRT
Referensi dasar yang layak dipakai saat melihat Netfilter adalah https://wiki.nftables.org/wiki-nftables/index.php/Main_Page dan https://www.netfilter.org/projects/nftables/manpage.html
Namun ICMP echo request memiliki ID, yang secara praktis berperan seperti nomor port sumber
Agar ICMP echo bisa ditangani NAT dengan benar, ID juga harus dipetakan ulang dua arah, seperti remapping port sumber pada UDP
Sebab jika mesin di belakang NAT secara bersamaan menerima ping dari dua host, dan kedua host itu kebetulan memakai nomor request yang sama, hasilnya menjadi ambigu
Kemungkinan lain adalah tidak menulis ulang identifier, melainkan menyimpan daftar mesin remote yang terhubung ke setiap ID. Jika ID bertabrakan, daftar itu berisi lebih dari satu alamat IP remote, dan ketika respons datang dari mesin di belakang NAT, NAT dapat memilih salah satu dari daftar, mengirim respons ke mesin tersebut, lalu menghapus entrinya
Jika memakai IPv6, saya harus mengubah semua node di jaringan dan juga memperbarui DNS internal
Secara teori saya bisa memiliki /48 sendiri yang portabel, tetapi ISP baru harus mengiklankannya, dan meskipun ISP saya saat ini bisa melakukannya, itu bukan hal yang umum
Seminggu lalu ketika kabel telepon putus, saya mengeluarkan 5G MiFi dan memindahkan koneksi WAN ke sana; cukup memasang masquerade sederhana pada interface tersebut. Sinyalnya lemah sehingga tidak ideal, tetapi tetap berfungsi
Masalahnya, meskipun memakai IPv6, saya tetap harus menjalankan dual stack atau memakai abstraksi NAT yang berantakan. Bagi saya hanya menambah pekerjaan tanpa manfaat
Di sisi pekerjaan juga sama. Kendaraan-kendaraan menggunakan subnet internal 172.16/12, saling terhubung dan melakukan routing, serta terhubung ke luar melalui berbagai koneksi VPN. Karena sering diparkir di bawah tanah, sinyalnya hampir tidak ada, jadi arsitekturnya berharap setidaknya salah satu dari beberapa metode bisa bekerja
Jika beralih ke IPv6, harus memindahkan kembali /48. Selain itu, kendaraan-kendaraan ini mendapatkan internet di berbagai stadion olahraga, dan banyak di antaranya bahkan kesulitan mematikan MITM/443 atau membuka blokir UDP. Ini tidak akan berhasil di lingkungan di mana kita tiba pukul 10 pagi hari Sabtu dan harus berfungsi 2 jam kemudian
Saya tidak tahu apa manfaat bisnisnya beralih ke dual stack yang menggandakan beban kerja dan risiko
https://en.wikipedia.org/wiki/Lindy_effect
https://stackoverflow.com/questions/31857419/how-to-send-a-m...
Sayangnya ping ditangani oleh sistem operasi, jadi aplikasi di IP peer tidak bisa membaca pesannya
Mungkin sudah waktunya menyediakan hook ruang pengguna untuk sebagian layanan seperti ini agar P2P sungguhan bisa dilakukan saat kedua pihak berada di balik NAT. Setidaknya semacam stream event hanya-baca. Sekarang semua penghalangnya terasa sepenuhnya artifisial
Namun saya pernah melihat strategi eksfiltrasi data atau cara komunikasi lain yang memakai ping. Saat ini kebanyakan konfigurasi default firewall diam-diam membuang semua ICMP termasuk ping, jadi rasanya hampir mustahil untuk keperluan P2P
idtampaknya pada dasarnya setara dengan(sport, dport), tetapi karena 16-bit, ruangnya jauh lebih kecil daripada 32-bitNamun saya rasa masalah utama NAT hole punching adalah untuk membuat koneksi, kedua ujungnya harus sama-sama aktif. Karena itu selalu diperlukan server koordinasi untuk memberi tahu T bahwa node S ingin berbicara dengan node T
Meski begitu, ini memberi bahan pemikiran. Saya penasaran apakah bisa dilakukan dengan pesan routing ICMP, misalnya unreachable atau TTL expired. Jika melakukan traceroute ke suatu IP, kita menerima paket dari berbagai IP lain secara arbitrer, dan ini umumnya melewati NAT
Bisa dibayangkan host T yang ingin menerima koneksi masuk memilih alamat IP “dummy” acak, memublikasikan
(IP router, IP dummy)sebagai identitasnya, lalu secara berkala mengirim paket ke IP dummy itu. Host S yang ingin berbicara dengan T dapat mengirim ICMP TTL-expired terkait alamat dummy itu ke router T, dan router mungkin melihatnya lalu meneruskannya ke TTentu saja ini bergantung pada apakah alamat IP di dalam field ICMP mengalami ingress filtering seperti alamat di header IP
Edit: sudah ada komentar tingkat atas yang menunjuk ke implementasi ide ini
[1] - https://github.com/yarrick/pingfs
Kalau di GitHub, tautannya bisa diikat ke kombinasi hash commit tertentu, nama file, dan nomor baris, tetapi jika codebase berubah besar-besaran, itu tidak terlalu berguna. Di git webview yang kurang umum seperti git.blender.org, ini tidak berjalan baik
https://elixir.bootlin.com/linux/latest/source