2 คะแนน โดย GN⁺ 2023-09-11 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • โดยปกติ IPv4 NAT จะแยกปลายทางของคำตอบด้วย พอร์ต ของ TCP/UDP แต่ ICMP echo ของ ping ไม่มีพอร์ต ดังนั้นประเด็นสำคัญคือ Linux ใช้ค่าใดเป็นคีย์ในการแมป
  • การทดลองใช้ network namespace สร้าง client1, client2, natbox, server และจำลองโครงสร้างที่ NAT จาก 192.168.99.0/24 ไปยัง 10.0.100.0/24 ด้วย iptables MASQUERADE
  • เมื่อเทียบ RFC 792 กับ packet capture จะเห็นว่า Identifier และ Sequence Number ของ ICMP echo ถูกใช้จับคู่คำขอและคำตอบ และในเส้นทาง ICMP SOCK_DGRAM ของ Linux ค่า local port ของซ็อกเก็ตจะถูกใส่เป็น ID
  • ถ้าไคลเอนต์สองเครื่องใช้ ICMP ID 999 เหมือนกัน netfilter จะเปลี่ยน ICMP ID ของฝั่งหนึ่งเป็นค่าสุ่ม เพื่อหลีกเลี่ยงการชนกัน และเมื่อมีคำตอบกลับมาก็จะแปลงกลับเป็น IP และ ID เดิมของไคลเอนต์
  • Linux NAT จะเก็บสถานะทิศทางต้นทางและทิศทางตอบกลับไว้ใน conntrack tuple แม้เป็น ICMP ที่ไม่มีพอร์ต และใช้ ICMP ID เป็นคีย์ที่สามารถแก้ไขได้เพื่อแมปคำตอบกลับไปยังโฮสต์ภายในที่ถูกต้อง

สภาพแวดล้อมการทดลองและการตั้งค่า NAT

  • ใช้ network namespace เพื่อจำลองอุปกรณ์หลายตัวบนเครื่อง Linux เครื่องเดียว
  • สร้างสองไคลเอนต์, natbox ที่ทำหน้าที่เป็นเราเตอร์ NAT และเซิร์ฟเวอร์แยกเป็นคนละ namespace พร้อมแยกเครือข่ายฝั่ง private กับฝั่งเซิร์ฟเวอร์
    • client1: 192.168.99.1/24
    • client2: 192.168.99.2/24
    • อินเทอร์เฟซภายในของ natbox: 192.168.99.3/24
    • อินเทอร์เฟซภายนอกของ natbox: 10.0.100.1/24
    • server: 10.0.100.2/24
  • รัน ip, iptables, tcpdump เป็นต้น ด้วยสิทธิ์ root บน Fedora 38 Server VM และ Linux kernel 6.2.9
  • ไคลเอนต์ทั้งสองเชื่อมกับบริดจ์ br0 และ natbox เชื่อมทั้งกับบริดจ์และ veth pair ฝั่งเซิร์ฟเวอร์
  • ตั้งค่า default route ของไคลเอนต์เป็น 192.168.99.3 เพื่อให้ทราฟฟิกไปยังเซิร์ฟเวอร์วิ่งผ่าน natbox
  • บน natbox เปิด packet forwarding ด้วย net.ipv4.ip_forward=1 และเพิ่มกฎ MASQUERADE ในเชน POSTROUTING ของตาราง nat ใน iptables
    • ip netns exec natbox iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE

ICMP NAT ที่เห็นจาก packet capture

  • ดักจับแพ็กเก็ต ICMP ด้วย tcpdump -n icmp ใน namespace ของ client1 และ server
  • ฝั่งไคลเอนต์จะเห็น echo request 192.168.99.1 > 10.0.100.2 และ echo reply 10.0.100.2 > 192.168.99.1
  • ฝั่งเซิร์ฟเวอร์จะเห็น source IP ของคำขอเดียวกันเปลี่ยนเป็น 10.0.100.1 ยืนยันว่า NAT ได้เขียนทับ source address เป็น IP ภายนอกของ natbox
  • คำขอ ICMP จากไคลเอนต์คนละเครื่องมี ฟิลด์ id ไม่เหมือนกัน
    • ในตัวอย่าง client1 ใช้ ID 31428
    • client2 ใช้ ID 33391
  • ข้อสังเกตนี้ชี้ว่า natbox สามารถใช้ฟิลด์ ID เมื่อต้องส่ง ICMP response กลับเข้าไปยังไคลเอนต์ภายใน

RFC 792 และ ICMP ID ของ ping

  • ICMP เป็นโปรโตคอลเก่าที่นิยามไว้ใน RFC 792 ซึ่งเผยแพร่ในปี 1981
  • ข้อความ ICMP echo และ echo reply มี Type, Code, Checksum, Identifier, Sequence Number และ Data
  • Type ใช้แยก echo request กับ echo reply
    • Type ของ echo request คือ 8
    • ในข้อความที่อ้างจาก RFC ระบุ echo reply เป็น 1
    • Code คือ 0
  • RFC 792 อธิบายว่า Identifier และ Sequence Number สามารถใช้จับคู่ echo request กับ reply ได้
  • Identifier สามารถใช้ระบุเซสชันได้คล้ายพอร์ตของ TCP/UDP และ Sequence Number สามารถเพิ่มขึ้นในแต่ละ echo request
  • RFC ไม่ได้กำหนดว่าต้องเลือก ID อย่างไรจริง ๆ จึงต้องไปดูซอร์สโค้ดของ ping ซึ่งเป็นตัว implementation

วิธีที่ ping ใน iputils กำหนด ID

  • คำสั่ง ping อยู่ในแพ็กเกจ iputils
  • คอมเมนต์ใกล้ ping4_send_probe อธิบายว่าเวลาสร้าง ICMP echo request นั้น ฟิลด์ ID เป็นตัวเลขสุ่ม และ Sequence Number เป็นเลขจำนวนเต็มที่เพิ่มขึ้น
  • ภายใน ping มีฟิลด์ ident ของ struct ping_rts
    • ค่าเริ่มต้นคือ -1
    • สามารถเขียนทับได้ด้วยออปชัน CLI -e โดยกำหนดค่าระหว่าง 0 ถึง IDENTIFIER_MAX ซึ่งคือ 0xFFFF
  • ถ้า rts->ident == -1 คำสั่ง ping จะ bind ซ็อกเก็ตด้วยชนิด SOCK_DGRAM และโปรโตคอล IPPROTO_ICMP
  • ตามคำอธิบายของซ็อกเก็ต IPPROTO_ICMP ใน Linux ส่วนหัว ICMP จะถูกตรวจสอบและจัดการตอน send() และ id จะถูกตั้งเป็นหมายเลข local port ของซ็อกเก็ต
  • หาก ping ไม่ได้ระบุ source port เคอร์เนล Linux จะเลือกพอร์ตว่างแบบสุ่ม และพอร์ตนั้นจะถูกใช้เป็น ID ของแพ็กเก็ต ICMP

เมื่อ ICMP ID เดียวกันชนกัน

  • บนไคลเอนต์ทั้งสองใช้ ping -e 999 เพื่อส่ง ping ไปยังเซิร์ฟเวอร์ด้วย ICMP ID 999 เหมือนกัน
  • จากการดักจับฝั่งเซิร์ฟเวอร์ พบว่าคำขอจากไคลเอนต์เครื่องหนึ่งยังคงใช้ ID 999 แต่ของอีกเครื่องถูกเปลี่ยนเป็น ID 30218
  • อุปกรณ์ NAT เปลี่ยน ID ของฝั่งหนึ่งเพื่อไม่ให้ชุดค่าของ external IP และ ICMP ID เดียวกันเกิดการชนกัน
  • เพื่อหาว่าจัดการการชนกันตรงไหน จึงตรวจดูโค้ดในไดเรกทอรี net/netfilter ของ Linux ที่ใช้ฟิลด์ ICMP id

บทบาทของ netfilter, conntrack และ NAT

  • ซับซิสเต็มในเคอร์เนลที่ทำให้กฎ iptables ทำงานคือ netfilter
  • เนื่องจากกฎ MASQUERADE เป็นผู้ทำ NAT ดังนั้น implementation ของ ICMP NAT ก็อยู่ใน netfilter
  • nf_nat_setup_info ใน nf_nat_core.c เรียก get_unique_tuple และต่อไปยัง nf_nat_l4proto_unique_tuple
  • ใน nf_nat_l4proto_unique_tuple มีเคส IPPROTO_ICMP และอ้างถึง tuple->src.u.icmp.id
  • nf_nat_manip_pkt ใน nf_nat_proto.c จะผ่าน nf_nat_ipv4_manip_pkt และ l4proto_manip_pkt แล้วเรียก icmp_manip_pkt เมื่อเป็น ICMP
  • icmp_manip_pkt เขียน ICMP ID ลงแพ็กเก็ตจริงด้วย hdr->un.echo.id = tuple->src.u.icmp.id

วิธีที่ conntrack tuple ใช้แทน ICMP

  • ใน netfilter คำว่า connection ไม่ได้หมายถึงแค่การเชื่อมต่อ TCP แต่หมายถึงสถานะที่ใช้เชื่อมโยงแพ็กเก็ตขาออกและแพ็กเก็ตขาเข้าของโปรโตคอลที่ไม่มีการเชื่อมต่ออย่าง UDP หรือ ICMP ด้วย
  • nf_conn มี tuplehash[IP_CT_DIR_MAX]
    • IP_CT_DIR_ORIGINAL: ทิศทางของแพ็กเก็ตขาออก
    • IP_CT_DIR_REPLY: ทิศทางของคำตอบขาเข้า
  • แต่ละ nf_conntrack_tuple_hash มี nf_conntrack_tuple ที่ใช้ระบุ connection
  • tuple แบ่งเป็น src ที่แก้ไขได้ และ dst ที่คงเดิม
    • src มีทั้ง IP address และฟิลด์เฉพาะโปรโตคอล
    • สำหรับ ICMP ฟิลด์เฉพาะโปรโตคอลคือ __be16 id
    • dst มี IP address ที่ไม่ถูกเปลี่ยน รวมถึง ICMP type และ code
  • NAT จะเก็บไว้ใน connection ว่าตอนแพ็กเก็ตขาออกถูกเปลี่ยนอย่างไร แล้วค่อยย้อนการเปลี่ยนนั้นกลับตอนมีแพ็กเก็ตตอบกลับ

เส้นทางโค้ดที่เลือก ICMP ID

  • เมื่อ natbox รับ ICMP echo, nf_nat_setup_info จะสร้าง connection ใหม่และตัดสินใจว่าต้องเปลี่ยน source IP กับ ICMP ID หรือไม่
  • หลังจากนั้นสำหรับทุกแพ็กเก็ต ICMP, nf_nat_manip_pkt จะตั้งค่า source หรือ destination IP รวมถึง ICMP ID ตามค่าที่เก็บไว้ใน connection
  • get_unique_tuple คือเส้นทางหลักในการเลือก NAT tuple ที่ใช้งานได้
    • find_best_ips_proto จะเขียนทับ source IP address
    • nf_nat_used_tuple ตรวจว่า tuple นี้ถูกใช้อยู่แล้วหรือไม่ และถ้ายังไม่ถูกใช้ก็จะคืน tuple ปัจจุบันกลับมาเลย
    • ด้วยเหตุนี้ ถ้า ICMP ID ของไคลเอนต์สองเครื่องต่างกัน ID ก็จะถูกคงไว้แม้หลัง NAT
    • ถ้า tuple นั้นถูกใช้อยู่แล้ว จะเรียก nf_nat_l4proto_unique_tuple เพื่อทำ NAT เฉพาะระดับโปรโตคอล
  • สำหรับ ICMP, tuple->src.u.icmp.id ถูกเลือกเป็น คีย์ สำหรับทำ NAT
  • find_free_id จะสร้าง ID แบบสุ่มด้วย get_random_u16() ปรับให้อยู่ในช่วง ICMP ID ที่ใช้ได้ แล้วตรวจว่าถูกใช้อยู่หรือไม่
  • ช่วง ID เริ่มต้นคือช่วง ID ทั้งหมด และในกฎ iptables MASQUERADE สามารถกำหนดช่วงเช่น 100-200 ได้ด้วย --to-ports
  • ถ้าหา tuple ที่ยังไม่ถูกใช้ไม่ได้ connection จะยังมี ID ซ้ำอยู่ และ __nf_conntrack_confirm จะตรวจพบความซ้ำแล้ว drop แพ็กเก็ตในภายหลัง

ยืนยันพฤติกรรมของเคอร์เนลด้วย bpftrace

  • เพื่อยืนยันความเข้าใจเกี่ยวกับการทำงานของ netfilter ได้ใช้ bpftrace
  • ฟังก์ชันในเคอร์เนลที่ติดตามคือ nf_nat_setup_info และ nf_nat_manip_pkt
  • kprobe ใช้ติดตามตอนฟังก์ชันถูกเรียก และ kretprobe ใช้ติดตามตอนฟังก์ชันคืนค่า
  • ใน kretprobe ไม่สามารถเข้าถึงอาร์กิวเมนต์ของฟังก์ชันได้โดยตรง จึงต้องเก็บอาร์กิวเมนต์ไว้ใน BPF map ตอนเข้า แล้วอ่านกลับมาอีกครั้งตอนคืนค่า
  • struct sk_buff คือโครงสร้างที่เคอร์เนล Linux ใช้แทนแพ็กเก็ต
  • bswap ใช้สลับ network byte order แบบ big endian ให้เป็น little endian
  • ntop ใช้แปลง IP address เป็นสตริง
  • ด้วย BPF Type Format (BTF) ของเคอร์เนล Linux รุ่นใหม่ ทำให้โปรแกรม BPF อ้างถึงโครงสร้างข้อมูลของเคอร์เนลอย่าง sk_buff, nf_conn ได้โดยไม่ต้อง include header
  • โปรแกรม bpftrace นี้ทดสอบบน Linux kernel 6.2.9 และอาจทำงานต่างออกไปบนเคอร์เนลเวอร์ชันอื่น

ผลการติดตามและข้อสรุป

  • เมื่อไคลเอนต์สองเครื่องส่ง ping ด้วย ICMP ID 999 เหมือนกัน nf_nat_setup_info จะถูกเรียกหนึ่งครั้งต่อไคลเอนต์
  • ไคลเอนต์ตัวแรก 192.168.99.1 คง ICMP ID 999 ไว้ทั้งใน original tuple และ reply tuple
  • ไคลเอนต์ตัวที่สอง 192.168.99.2 มี ICMP ID ใน reply tuple ถูกเขียนทับเป็น 32809
  • nf_nat_manip_pkt จะเปลี่ยน source IP เป็น 10.0.100.1 ด้วย NF_NAT_MANIP_SRC ใน echo request และจะเปลี่ยน destination IP กลับเป็น IP เดิมของไคลเอนต์ด้วย NF_NAT_MANIP_DST ในแพ็กเก็ตตอบกลับ
  • ICMP ID ในแพ็กเก็ตตอบกลับก็ถูกกู้คืนจากค่าที่ NAT เปลี่ยนไว้ให้กลับเป็นค่าที่ไคลเอนต์เดิมส่งมา
  • ค่า timeout เริ่มต้นของ ICMP conntrack ดูได้จาก /proc/sys/net/netfilter/nf_conntrack_icmp_timeout โดยค่าดีฟอลต์ที่สังเกตได้คือ 30 วินาที
  • ถ้าไคลเอนต์ไม่ส่งแพ็กเก็ตนานเกิน 30 วินาที nf_nat_setup_info จะถูกเรียกใหม่ใน ping ครั้งถัดไป
  • พฤติกรรม NAT ของ Linux สำหรับ ping ถูกบันทึกไว้ใน Netfilter Hacking HOWTO เช่นกัน และแก่นสำคัญคือ conntrack tuple กับการเขียนทับ ICMP ID

1 ความคิดเห็น

 
GN⁺ 2023-09-11
ความคิดเห็นจาก Hacker News
  • คุณอาจสนใจ https://samy.pl/pwnat/
    เมื่อเซิร์ฟเวอร์เริ่มทำงาน มันจะเริ่มส่งแพ็กเก็ตคำขอ ICMP echo แบบคงที่ไปยังที่อยู่คงที่ 3.3.3.3 และคาดว่าแพ็กเก็ตนี้จะไม่ย้อนกลับมา
    3.3.3.3 ไม่ใช่โฮสต์ที่เข้าถึงได้ และไม่ใช่เป้าหมายที่จะสปูฟ แต่เมื่อไคลเอนต์ต้องการเชื่อมต่อ มันรู้ IP ของเซิร์ฟเวอร์อยู่แล้ว จึงส่งแพ็กเก็ต ICMP Time Exceeded ไปให้เซิร์ฟเวอร์ ภายในแพ็กเก็ต ICMP นั้นมีแพ็กเก็ตคงที่ “ต้นฉบับ” ที่เซิร์ฟเวอร์เคยส่งไปยัง 3.3.3.3 อยู่ และแพ็กเก็ตที่ฮาร์ดโค้ดไว้นี้ทำหน้าที่เป็นตัวระบุของ pwnat
    กล่าวคือ ไคลเอนต์ทำทีเหมือนเป็นฮอปหนึ่งบนอินเทอร์เน็ต แล้วแจ้งว่า “ICMP echo request” ต้นฉบับของเซิร์ฟเวอร์ส่งต่อไปไม่ได้ NAT เห็นว่าแพ็กเก็ตภายใน ICMP Time Exceeded ตรงกับแพ็กเก็ตที่เซิร์ฟเวอร์ส่งออกไป จึงส่งต่อไปยังเซิร์ฟเวอร์หลัง NAT และเนื่องจากมี IP header ทั้งชุดของไคลเอนต์รวมอยู่ด้วย เซิร์ฟเวอร์จึงรู้ ที่อยู่ IP ของไคลเอนต์ ได้
    • สรุปคือ เทคนิค ping ไปยัง 3.3.3.3 เป็นวิธีที่ทำให้เซิร์ฟเวอร์หลัง NAT รู้ที่อยู่ IP ของไคลเอนต์หลัง NAT ได้ และไม่ต้องมีเซิร์ฟเวอร์ที่ไม่อยู่หลัง NAT อย่าง https://ifconfig.co
      การทำงานหลักของเครื่องมือนี้หลังจากนั้นคือการสร้าง UDP tunnel ระหว่างไคลเอนต์กับเซิร์ฟเวอร์
      แต่จากที่ดูคร่าว ๆ ดูเหมือนจะสมมติว่า NAT ไม่เขียนพอร์ตต้นทางของ UDP ใหม่ จึงน่าจะใช้ไม่ได้กับเราเตอร์ทุกตัว STUN ที่ใช้ใน WebRTC และอย่างอื่น ๆ ใช้เทคนิคที่ซับซ้อนกว่า แต่ในกรณีที่ยังใช้ไม่ได้ ก็ต้องใช้ TURN ซึ่งเป็นรีเลย์
      ปัญหาเดียวกันนี้มีแนวโน้มสูงว่าจะเกิดกับเทคนิค ping 3.3.3.3 ด้วย หาก NAT เขียนตัวระบุของ ping ใหม่เหมือนในบทความ เทคนิคนี้ก็จะพัง
  • เมื่ออุปกรณ์ในเครือข่ายภายในส่ง ping ไปยังอุปกรณ์บนอินเทอร์เน็ต เราเตอร์ที่ทำ NAT จะเปลี่ยนที่อยู่ต้นทางของ ping เป็น IP สาธารณะของตัวเอง และเขียน ฟิลด์ ID ของแพ็กเก็ต ICMP ใหม่เป็นค่าที่ไม่ซ้ำกัน
    เมื่อได้รับคำตอบ เราเตอร์จะใช้ค่า ID ที่ไม่ซ้ำกันนั้นเพื่อส่งต่อคำตอบไปยังอุปกรณ์ที่ถูกต้องในเครือข่ายภายใน
    • ถ้าจะดูให้ถูกต้องกว่านี้ ให้ลองคิดว่าระบบปฏิบัติการแยก บทสนทนา ICMP ต่าง ๆ ที่มุ่งไปยังปลายทางเดียวกันอย่างไร
      ตรวจสอบได้ด้วยคอมพิวเตอร์เครื่องเดียวกับ Wireshark/tcpdump
      ตัวบทความเองดี และสำหรับคนที่ไม่เคยเข้าใจเรื่อง networking มาก่อนเลย อาจทำให้ตาสว่างได้ แต่โดยแก่นแล้วดูเหมือนเป็นวิธีสร้างแล็บเครือข่ายที่ถูกต้องแล้วขุดลงไปในซอร์สมากกว่าการคิดเอาเองโดยตรง
    • ถ้าดันแนวคิดนี้ต่ออีกนิด ก็เท่ากับเปลี่ยน โปรโตคอลไร้สถานะ ให้เป็นโปรโตคอลมีสถานะ
    • ping เองก็ต้องมี ข้อมูลสถานะ แบบนั้นอยู่ดีเพื่อจับคู่คำขอกับคำตอบ
    • สงสัยว่าทำไมไม่ใช้ IP ส่วนตัวต้นทาง แทน “ค่าที่ไม่ซ้ำกัน”
    • สงสัยว่า ID นั้นอยู่ใน ICMP header หรือเป็นของฝั่ง IP
  • ดีใจที่ได้เห็นบทความแนว “มันทำงานอย่างไร” ไล่ลงมาตามชั้น abstraction จนถึง ซอร์สโค้ด คำอธิบายก็ดีและมีข้อมูลเยอะ
    • ผมก็เข้ามาเพื่อจะพูดแบบนี้เหมือนกัน routing กับ networking ยังทำให้สับสนอยู่ และบทความที่เกี่ยวข้องส่วนใหญ่รู้สึกว่า เป็นนามธรรม เกินไป
      ตัวอย่างแบบที่ลองทำตามด้วยมือได้แบบนี้น่าขอบคุณจริง ๆ และตั้งใจว่าจะลองทำตามเอง
      บทความอื่นแทบจะเรื่องเดียวที่ผมเข้าใจหัวข้อนี้ได้ดีคือบทความนี้ของ Tailscale เพราะมี “ตัวอย่างที่อธิบายละเอียด” เยอะ ทำให้เห็นชัดว่าทั้งหมดประกอบเข้าด้วยกันอย่างไร
      https://tailscale.com/blog/how-nat-traversal-works/
  • บทความดี
    บังเอิญว่าช่วงสุดสัปดาห์นี้ผมเพิ่งปล้ำกับ Netfilter เพื่อเปิด transparent proxy บนเราเตอร์ OpenWRT
    แหล่งอ้างอิงพื้นฐานเวลาอ่าน Netfilter คือ https://wiki.nftables.org/wiki-nftables/index.php/Main_Page และ https://www.netfilter.org/projects/nftables/manpage.html
  • ICMP ไม่มีพอร์ต จึงไม่ต้องจัดการปัญหาว่า NAT จะส่ง ICMP echo reply กลับไปยังพอร์ตที่ถูกต้องอย่างไร
    แต่ ICMP echo request มี ID และโดยพฤตินัยแล้วทำหน้าที่เหมือนหมายเลขพอร์ตต้นทาง
    หากต้องการ NAT สำหรับ ICMP echo ให้ถูกต้อง ก็ต้อง remap ID ทั้งสองทิศทางเหมือนกับการ remap พอร์ตต้นทางของ UDP
    เพราะถ้าเครื่องหลัง NAT ได้รับ ping จากโฮสต์สองตัวพร้อมกัน และโฮสต์สองตัวนั้นบังเอิญใช้หมายเลขคำขอเดียวกัน ก็จะกำกวม
    ความเป็นไปได้อีกอย่างคือไม่เขียน identifier ใหม่ แต่เก็บรายการเครื่องระยะไกลที่เชื่อมกับแต่ละ ID ไว้ ถ้า ID ชนกัน รายการก็จะมีที่อยู่ IP ระยะไกลมากกว่าหนึ่งรายการ และเมื่อมีคำตอบจากเครื่องหลัง NAT มา NAT ก็เลือกหนึ่งรายการจากลิสต์ ส่งคำตอบไปยังเครื่องนั้น แล้วลบรายการออกก็ได้
  • NAT เป็น abstraction ที่สกปรกจริง ๆ IPv4 ควรหายไปได้แล้ว
    • อินเทอร์เน็ตที่บ้านผมมีอุปกรณ์อยู่ในหลายซับเน็ตของช่วง 192.168 ไม่นานมานี้ผมย้าย ISP ทำให้ AS ที่บ้านผมสังกัดเปลี่ยนไปและได้ที่อยู่ IPv4 ใหม่ แต่สิ่งที่ต้องทำมีแค่อัปเดตการส่งต่อทราฟฟิกที่เข้ามายัง IP ใหม่บนเราเตอร์ WAN
      ถ้าเป็น IPv6 คงต้องเปลี่ยนทุกโหนดในเครือข่ายและอัปเดต DNS ภายในด้วย
      ในทางทฤษฎี ผมสามารถมี /48 ของตัวเองที่พกพาไปได้ แต่ ISP ใหม่ต้องโฆษณาให้ และแม้ ISP ปัจจุบันจะทำให้ได้ แต่นั่นก็ไม่ใช่เรื่องทั่วไป
      เมื่อสัปดาห์ก่อนสายโทรศัพท์ขาด ผมก็หยิบ 5G MiFi ออกมาแล้วย้ายการเชื่อมต่อ WAN ไปทางนั้น จากนั้นแค่ทำ masquerade ง่าย ๆ บนอินเทอร์เฟซนั้นก็พอ สัญญาณอ่อนเลยไม่ดีนัก แต่ก็ใช้งานได้
      ปัญหาคือแม้จะใช้ IPv6 ก็ยังต้องรัน dual stack หรือใช้ abstraction ของ NAT ที่สกปรกอยู่ดี สำหรับผมมีแต่งานเพิ่มโดยไม่ได้ประโยชน์
      ฝั่งงานก็เหมือนกัน รถที่ใช้ซับเน็ตภายใน 172.16/12 เชื่อมต่อและ route หากัน และเชื่อมออกภายนอกผ่าน VPN หลากหลายแบบ โครงสร้างคือจอดใต้ดินบ่อยจนแทบไม่มีสัญญาณ จึงหวังว่าอย่างน้อยหนึ่งในหลายวิธีจะใช้งานได้

ถ้าย้ายไป IPv6 ก็ต้องย้าย /48 เหล่านั้นอีกรอบ แถมรถพวกนี้ยังรับอินเทอร์เน็ตจากสนามกีฬาหลายแห่ง ซึ่งหลายที่แค่ปิด MITM/443 หรือปลดการบล็อก UDP ก็ยังทำได้ยาก ในสภาพแวดล้อมที่ไปถึงวันเสาร์ 10 โมงเช้า แล้วต้องใช้งานได้ในอีก 2 ชั่วโมง วิธีนี้ใช้ไม่ได้
ไม่เห็นว่าประโยชน์ทางธุรกิจของการย้ายไปใช้ dual stack เพื่อเพิ่มงานและความเสี่ยงเป็นสองเท่าคืออะไร

  • ไม่แน่ใจว่า IPv6 จะแก้ปัญหานี้ได้หรือไม่ ในทางเทคนิคก็ใช่ แต่ผู้ให้บริการรายใหญ่ให้ผู้ใช้ตามบ้านแค่ /64 และคิดค่าใช้จ่ายสูงสำหรับ /48 แบบ “ธุรกิจ” จนเริ่มนำไปสู่ IPv6 NAT หรือการแบ่งย่อย /64 เพิ่มแล้ว ซึ่งเดิมทีไม่ควรทำแบบนั้น
  • ถ้าอย่างนั้นก็จะยิ่งเกลียด CG-NAT มากขึ้น
  • ควรคำนึงถึง Lindy effect ไว้ด้วย มันเป็นข้อสังเกตว่าอายุขัยในอนาคตของสิ่งที่ไม่เสื่อมสภาพ เช่น เทคโนโลยีหรือไอเดีย จะแปรผันตามอายุปัจจุบันของมัน และ IPv4 ก็เก่าพอที่มีแนวโน้มจะอยู่ต่อไปได้อีกค่อนข้างนาน
    https://en.wikipedia.org/wiki/Lindy_effect
  • IPv6 ก็ควรหายไปเหมือนกัน มีเวลามากพอที่จะขึ้นมาเหนือกว่าแล้ว แต่ก็ยังไปได้ไม่ถึงไหน
  • สงสัยว่าจะสามารถ ใช้ ping ในทางที่ผิด เพื่อส่งข้อความสั้น ๆ บนเครือข่าย P2P ที่ใช้ UDP ขณะจัดการการเจาะ NAT โดยไม่ต้องมีเซิร์ฟเวอร์กลางได้ไหม ส่วนของข้อความดูเหมือนมีคนคิดออกแล้ว
    https://stackoverflow.com/questions/31857419/how-to-send-a-m...
    น่าเสียดายที่ ping ถูกจัดการโดยระบบปฏิบัติการ ดังนั้นแอปบน IP ของเพียร์จึงอ่านข้อความไม่ได้
    อาจถึงเวลาแล้วหรือเปล่าที่จะมี hook ใน user space ให้บริการบางอย่างพวกนี้ เพื่อให้ P2P จริง ๆ ที่ทั้งสองฝั่งอยู่หลัง NAT เป็นไปได้ อย่างน้อยก็เป็นสตรีมเหตุการณ์แบบอ่านอย่างเดียวก็ยังดี ตอนนี้กำแพงที่ขวางอยู่ทั้งหมดดูเหมือนเป็นสิ่งที่มนุษย์สร้างขึ้นล้วน ๆ
    • แก้รายละเอียดทางเทคนิคนิดหนึ่ง: ping ไม่ใช่ UDP แต่เป็น ICMP
      อย่างไรก็ตาม เคยเห็นกลยุทธ์การรั่วไหลของข้อมูลที่ใช้ ping หรือรูปแบบการสื่อสารอื่น ๆ อยู่บ้าง ทุกวันนี้ firewall ส่วนใหญ่มักตั้งค่าเริ่มต้นให้ทิ้ง ICMP ทั้งหมด รวมถึง ping แบบเงียบ ๆ ดังนั้นสำหรับการใช้แบบ P2P คงแทบเป็นไปไม่ได้
    • เป็นความคิดที่น่าสนใจ id ดูเหมือนเทียบได้กับ (sport, dport) โดยพฤตินัย แต่เป็น 16 บิต จึงมีพื้นที่น้อยกว่า 32 บิตมาก
      แต่คิดว่าปัญหาหลักของ NAT hole punching คือ ถ้าจะสร้างการเชื่อมต่อ ทั้งสองปลายทางต้องมีกิจกรรมด้วยไม่ใช่หรือ ดังนั้นจึงต้องมี เซิร์ฟเวอร์ประสานงาน เสมอ เพื่อบอก T ว่าโหนด S ต้องการคุยกับโหนด T
      ถึงอย่างนั้นก็ทำให้มีเรื่องให้คิดต่อ สงสัยว่าจะมีวิธีทำด้วยข้อความ routing ของ ICMP เช่น unreachable หรือ TTL expired ได้ไหม ถ้า traceroute ไปยัง IP ใด IP หนึ่ง ก็จะได้รับแพ็กเก็ตตอบกลับจาก IP อื่น ๆ ตามอำเภอใจ และโดยทั่วไปสิ่งนี้ผ่าน NAT ได้
      ลองจินตนาการว่าโฮสต์ T ที่อยากรับการเชื่อมต่อขาเข้า เลือกที่อยู่ IP “dummy” แบบสุ่ม แล้วเผยแพร่ (IP ของเราเตอร์, IP dummy) เป็นตัวระบุของตัวเอง จากนั้นส่งแพ็กเก็ตไปยัง IP dummy นั้นเป็นระยะ ๆ โฮสต์ S ที่ต้องการคุยกับ T ก็สามารถส่ง ICMP TTL-expired เกี่ยวกับที่อยู่ dummy นั้นไปยังเราเตอร์ของ T และเราเตอร์อาจเห็นแล้วส่งต่อไปยัง T ได้
      แน่นอนว่าขึ้นอยู่กับว่าที่อยู่ IP ภายในฟิลด์ ICMP ถูก ingress filtering เหมือนที่อยู่ใน IP header หรือไม่
      แก้ไข: มีคอมเมนต์ระดับบนสุดที่ชี้ไปยังการใช้งานจริงของไอเดียนี้แล้ว
    • มีอยู่แล้ว: https://samy.pl/pwnat/
    • ไม่ตรงกับสิ่งที่กำลังหาเป๊ะ ๆ แต่พอพูดถึงการใช้ ping ในทางที่ผิดก็นึกถึง pingfs ขึ้นมา มันให้นิยามใหม่เอี่ยมแก่ cloud computing เลย
      [1] - https://github.com/yarrick/pingfs
    • ถ้าการใช้งาน IPv6 เพิ่มขึ้นอีก ทุกคนจะมี IP ที่ route ได้แบบสาธารณะและหลีกเลี่ยง NAT ได้ทั้งหมด ปัญหาแบบนี้ก็จะลดลง
  • เวลาจะเขียนบล็อกโพสต์แบบนี้ มันน่าหงุดหงิดมากที่การลิงก์ไปยังบรรทัดโค้ดเฉพาะ แล้วทำให้ลิงก์นั้นยังคงอยู่และมีประโยชน์เมื่อเวลาผ่านไปนั้นยากเหลือเกิน
    ถ้าเป็น GitHub ก็ผูกกับชุดของ commit hash, ชื่อไฟล์ และเลขบรรทัดที่เฉพาะเจาะจงได้ แต่ถ้า codebase เปลี่ยนไปมาก มันก็ไม่ค่อยมีประโยชน์นัก ส่วน git webview ที่ใช้น้อยกว่าอย่าง git.blender.org ก็ทำได้ไม่ดี
    • ถ้าเป็นโค้ดเคอร์เนล Linux ใช้ elixir ก็อย่างน้อยลิงก์ไปยังเวอร์ชันเฉพาะได้ ถ้าต้องการความคงทนระดับหนึ่งก็ใช้เวอร์ชัน LTS
      https://elixir.bootlin.com/linux/latest/source
  • สรุปคือ ในแพ็กเก็ต ICMP มี ฟิลด์ id และ Netfilter มองแพ็กเก็ตหรือเฟรม ICMP เป็น “กรณีพิเศษ”