2 poin oleh GN⁺ 2023-09-11 | 1 komentar | Bagikan ke WhatsApp
  • NAT IPv4 biasanya membedakan tujuan balasan berdasarkan port TCP/UDP, tetapi ICMP echo milik ping tidak 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, dan server menggunakan network namespace, serta iptables MASQUERADE
  • 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_DGRAM Linux, local port socket dimasukkan sebagai ID
  • Jika dua klien memakai ICMP ID 999 yang 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, natbox yang berperan sebagai router NAT, dan server dibuat sebagai namespace terpisah, dengan jaringan privat dan jaringan sisi server dipisahkan
    • client1: 192.168.99.1/24
    • client2: 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, dan natbox terhubung masing-masing ke bridge dan veth pair ke sisi server
  • Rute default klien diset ke 192.168.99.3 agar trafik menuju server melewati natbox
  • Di natbox, packet forwarding diaktifkan dengan net.ipv4.ip_forward=1, lalu aturan MASQUERADE ditambahkan ke chain POSTROUTING pada tabel nat milik iptables
    • ip netns exec natbox iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE

NAT ICMP dilihat lewat packet capture

  • Paket ICMP ditangkap dengan tcpdump -n icmp di namespace client1 dan server
  • Di sisi klien terlihat echo request 192.168.99.1 > 10.0.100.2 dan echo reply 10.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 milik natbox
  • Request ICMP dari klien yang berbeda memiliki field id yang berbeda
    • Pada contoh, client1 memakai ID 31428
    • client2 memakai ID 33391
  • Pengamatan ini menunjukkan bahwa natbox dapat 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
  • 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 ping termasuk dalam paket iputils
  • Komentar di sekitar ping4_send_probe menjelaskan bahwa saat membuat ICMP echo request, field ID adalah angka acak dan Sequence Number adalah bilangan bulat yang terus bertambah
  • Di dalam ping ada field ident pada struct ping_rts
    • Nilai default-nya adalah -1
    • Bisa ditimpa lewat opsi CLI -e dengan nilai dari 0 sampai IDENTIFIER_MAX, yaitu 0xFFFF
  • Jika rts->ident == -1, ping melakukan bind socket dengan tipe SOCK_DGRAM dan protokol IPPROTO_ICMP
  • Menurut penjelasan socket IPPROTO_ICMP di Linux, header ICMP akan diperiksa dan dirapikan saat send() dipanggil, dan id diisi dengan nomor local port socket
  • Jika ping tidak 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 999 digunakan 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 ID 30218
  • 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/netfilter Linux diperiksa untuk menemukan bagian yang memakai field id pada ICMP

Peran netfilter, conntrack, dan NAT

  • Subsystem kernel yang mengimplementasikan aturan iptables adalah netfilter
  • Karena aturan MASQUERADE menjalankan NAT, implementasi NAT untuk ICMP juga ada di dalam netfilter
  • nf_nat_setup_info di nf_nat_core.c memanggil get_unique_tuple, yang kemudian berlanjut ke nf_nat_l4proto_unique_tuple
  • Di nf_nat_l4proto_unique_tuple ada kasus IPPROTO_ICMP yang merujuk ke tuple->src.u.icmp.id
  • nf_nat_manip_pkt di nf_nat_proto.c memanggil nf_nat_ipv4_manip_pkt dan l4proto_manip_pkt, lalu untuk ICMP akan memanggil icmp_manip_pkt
  • icmp_manip_pkt menulis ICMP ID yang sebenarnya ke paket melalui hdr->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_conn memiliki tuplehash[IP_CT_DIR_MAX]
    • IP_CT_DIR_ORIGINAL: arah paket keluar
    • IP_CT_DIR_REPLY: arah respons masuk
  • Setiap nf_conntrack_tuple_hash memiliki nf_conntrack_tuple yang mengidentifikasi connection
  • Tuple dibagi menjadi src yang bisa dimanipulasi dan dst yang tidak berubah
    • src berisi alamat IP dan field khusus protokol
    • Untuk ICMP, field khusus protokolnya adalah __be16 id
    • dst berisi alamat IP yang tidak diubah serta ICMP type dan code
  • NAT menyimpan di dalam connection bagaimana paket keluar diubah, lalu membalikkan perubahan itu pada paket respons

Jalur kode yang memilih ICMP ID

  • Saat natbox menerima ICMP echo, nf_nat_setup_info membuat connection baru dan memutuskan apakah alamat IP sumber serta ICMP ID perlu diubah
  • Setelah itu, untuk setiap paket ICMP, nf_nat_manip_pkt akan menetapkan alamat IP sumber atau tujuan serta ICMP ID berdasarkan nilai yang tersimpan di connection
  • get_unique_tuple adalah jalur utama untuk memilih tuple NAT yang tersedia
    • find_best_ips_proto menulis ulang alamat IP sumber
    • nf_nat_used_tuple memeriksa 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_tuple dipanggil untuk menjalankan NAT spesifik protokol
  • Untuk ICMP, tuple->src.u.icmp.id dipilih sebagai kunci target NAT
  • find_free_id membuat ID acak dengan get_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 iptables MASQUERADE bisa ditentukan rentang seperti 100-200 lewat --to-ports
  • Jika tuple yang belum dipakai tidak dapat ditemukan, ID duplikat akan tetap berada di connection, dan kemudian __nf_conntrack_confirm mendeteksi 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_info dan nf_nat_manip_pkt
  • kprobe melacak saat fungsi dipanggil, dan kretprobe melacak saat fungsi mengembalikan hasil
  • Karena kretprobe tidak bisa langsung mengakses argumen fungsi, argumen disimpan lebih dulu di BPF map saat masuk lalu dibaca lagi saat return
  • struct sk_buff adalah struktur yang dipakai kernel Linux untuk merepresentasikan paket
  • bswap dipakai untuk mengubah network byte order yang berupa big endian menjadi little endian
  • ntop mengubah alamat IP menjadi string
  • Berkat BPF Type Format (BTF) pada kernel Linux modern, program BPF dapat merujuk ke struktur kernel seperti sk_buff dan nf_conn tanpa menyertakan header
  • Program bpftrace ini 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 999 yang sama, nf_nat_setup_info dipanggil satu kali untuk masing-masing klien
  • Untuk klien pertama 192.168.99.1, baik original tuple maupun reply tuple sama-sama mempertahankan ICMP ID 999
  • Untuk klien kedua 192.168.99.2, ICMP ID pada reply tuple ditulis ulang menjadi 32809
  • nf_nat_manip_pkt mengganti alamat IP sumber menjadi 10.0.100.1 pada echo request dengan NF_NAT_MANIP_SRC, dan pada respons mengembalikan alamat IP tujuan ke IP klien asal dengan NF_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_info akan 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

 
GN⁺ 2023-09-11
Komentar Hacker News
  • Anda mungkin tertarik pada https://samy.pl/pwnat/
    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
    • Singkatnya, trik ping ke 3.3.3.3 adalah cara agar server di belakang NAT mengetahui alamat IP klien di belakang NAT, tanpa memerlukan server non-NAT seperti https://ifconfig.co
      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
  • Jika perangkat di jaringan lokal mengirim ping ke perangkat di internet, router yang melakukan NAT mengganti alamat sumber ping menjadi IP publiknya sendiri dan menulis ulang field ID pada paket ICMP menjadi nilai yang unik
    Saat menerima respons, router menggunakan nilai ID unik itu untuk meneruskan respons ke perangkat yang tepat di jaringan lokal
    • Untuk melihatnya dengan lebih tepat, pikirkan bagaimana sistem operasi membedakan berbagai percakapan ICMP yang menuju tujuan yang sama
      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
    • Jika pemikiran ini didorong sedikit lebih jauh, pada dasarnya ini mengubah protokol stateless menjadi protokol stateful
    • Ping pun bagaimanapun memerlukan informasi state seperti itu untuk mencocokkan request dan response
    • Saya penasaran mengapa tidak memakai IP privat sumber alih-alih “nilai unik”
    • Saya penasaran apakah ID itu ada di header ICMP, atau termasuk di sisi IP
  • Senang melihat artikel tipe “bagaimana cara kerjanya” turun mengikuti lapisan abstraksi sampai menggali ke source code. Penjelasannya juga bagus dan informatif
    • Saya juga datang untuk mengatakan ini. Routing dan networking masih membingungkan bagi saya, dan tulisan-tulisan terkait biasanya terasa terlalu abstrak
      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/
  • Artikel yang bagus
    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
  • Karena ICMP tidak memiliki port, NAT tidak perlu menangani masalah mengembalikan ICMP echo response ke port yang benar
    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
  • NAT benar-benar abstraksi yang berantakan. IPv4 seharusnya menghilang
    • Di internet rumah saya ada perangkat-perangkat di beberapa subnet rentang 192.168. Beberapa waktu lalu saya pindah ISP sehingga AS tempat rumah saya berada berubah dan saya mendapat alamat IPv4 baru, tetapi saya hanya perlu memperbarui penerusan trafik yang masuk ke IP baru di router WAN
      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

  • Tidak yakin IPv6 akan menyelesaikan masalah ini. Secara teknis iya, tetapi penyedia besar hanya memberi pengguna rumahan /64 dan mengenakan biaya besar untuk /48 “bisnis”, sehingga sudah mengarah ke NAT IPv6 atau pemecahan /64 tambahan. Seharusnya memang tidak begitu
  • Kalau begitu, orang akan makin tidak menyukai CG-NAT
  • Perlu mengingat efek Lindy. Ini adalah pengamatan bahwa umur masa depan dari sesuatu yang tidak habis dipakai, seperti teknologi atau ide, sebanding dengan usianya saat ini; karena IPv4 sudah lama, kemungkinan besar ia akan tetap ada cukup lama ke depan
    https://en.wikipedia.org/wiki/Lindy_effect
  • IPv6 juga seharusnya menghilang. Ia sudah punya cukup waktu untuk menjadi dominan, tetapi terus saja tersendat
  • Saya penasaran apakah ping bisa disalahgunakan untuk menangani NAT traversal tanpa server pusat, sambil mengirim pesan singkat dalam jaringan P2P berbasis UDP. Bagian pesannya sepertinya sudah pernah dipecahkan seseorang
    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
    • Koreksi teknis kecil: ping itu ICMP, bukan UDP
      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
    • Pemikiran yang menarik. id tampaknya pada dasarnya setara dengan (sport, dport), tetapi karena 16-bit, ruangnya jauh lebih kecil daripada 32-bit
      Namun 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 T
      Tentu 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
    • Sudah ada: https://samy.pl/pwnat/
    • Tidak persis seperti yang dicari, tetapi kata-kata menyalahgunakan ping mengingatkan saya pada pingfs. Ini memberi definisi yang benar-benar baru untuk cloud computing
      [1] - https://github.com/yarrick/pingfs
    • Jika adopsi IPv6 makin meningkat, semua orang akan memiliki IP yang dapat dirutekan secara publik dan bisa sepenuhnya menghindari NAT, jadi masalah seperti ini akan berkurang
  • Saat menulis posting blog seperti ini, menyebalkan sekali karena sangat sulit menautkan ke baris kode tertentu dan membuat tautan itu tetap hidup serta berguna seiring waktu
    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
  • Ringkasnya, di dalam paket ICMP ada field id, dan Netfilter mengenali paket atau frame ICMP sebagai “kasus khusus”