2 पॉइंट द्वारा GN⁺ 2023-09-11 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • IPv4 NAT आम तौर पर TCP/UDP ports से response के लक्ष्य को अलग करता है, लेकिन ping के ICMP echo में port नहीं होता, इसलिए Linux किस value को mapping key के तौर पर इस्तेमाल करता है, यही मुख्य बात है
  • प्रयोग में network namespaces से client1, client2, natbox, server बनाए गए, और iptables MASQUERADE के जरिए 192.168.99.0/24 से 10.0.100.0/24 पर NAT होने वाली configuration को दोहराया गया
  • RFC 792 और packet capture की तुलना करने पर दिखता है कि ICMP echo का Identifier और Sequence Number request-response matching में इस्तेमाल होते हैं, और Linux के ICMP SOCK_DGRAM path में socket local port ID के रूप में जाता है
  • जब दो clients एक ही ICMP ID 999 इस्तेमाल करते हैं, तो netfilter collision से बचने के लिए एक तरफ का ICMP ID random value में बदल देता है, और response में उसे वापस original client IP और ID में restore करता है
  • Linux NAT port न होने वाले ICMP में भी conntrack tuple में original और reply direction की state store करता है, और ICMP ID को manipulate की जा सकने वाली key की तरह इस्तेमाल कर response को सही internal host से map करता है

प्रयोग का environment और NAT configuration

  • एक Linux machine पर कई devices की नकल करने के लिए network namespaces का इस्तेमाल किया गया
  • दो clients, NAT router की भूमिका वाला natbox, और server को अलग-अलग namespaces में बनाया गया और private network तथा server-side network को अलग किया गया
    • client1: 192.168.99.1/24
    • client2: 192.168.99.2/24
    • natbox internal interface: 192.168.99.3/24
    • natbox external interface: 10.0.100.1/24
    • server: 10.0.100.2/24
  • Fedora 38 Server VM और Linux kernel 6.2.9 पर ip, iptables, tcpdump आदि root के रूप में चलाए गए
  • दोनों clients br0 bridge से जुड़े, और natbox bridge तथा server-side veth pair से अलग-अलग जुड़ा
  • clients का default route 192.168.99.3 पर set किया गया ताकि server की ओर जाने वाला traffic natbox से होकर गुजरे
  • natbox में net.ipv4.ip_forward=1 से packet forwarding चालू की गई, और iptables की nat table की POSTROUTING chain में MASQUERADE rule जोड़ा गया
    • ip netns exec natbox iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE

packet capture में दिखा ICMP NAT

  • client1 और server namespaces में tcpdump -n icmp से ICMP packets capture किए गए
  • client side पर 192.168.99.1 > 10.0.100.2 echo request और 10.0.100.2 > 192.168.99.1 echo reply दिखे
  • server side पर उसी request का source IP 10.0.100.1 में बदला हुआ था, जिससे पुष्टि हुई कि NAT ने source address को natbox के external IP में rewrite किया
  • अलग-अलग clients की ICMP requests में अलग-अलग id fields थे
    • example में client1 का ID 31428 था
    • client2 का ID 33391 था
  • यह observation दिखाता है कि natbox ICMP response को internal client तक वापस भेजते समय ID field का उपयोग कर सकता है

RFC 792 और ping का ICMP ID

  • ICMP 1981 में प्रकाशित RFC 792 में defined एक पुराना protocol है
  • ICMP echo और echo reply messages में Type, Code, Checksum, Identifier, Sequence Number, Data होते हैं
  • Type echo request और echo reply को अलग करता है
    • echo request का Type 8 है
    • RFC quote वाले हिस्से में echo reply को 1 लिखा गया है
    • Code 0 है
  • RFC 792 बताता है कि Identifier और Sequence Number को echo request और reply की matching में इस्तेमाल किया जा सकता है
  • Identifier TCP/UDP के port की तरह session identification में इस्तेमाल हो सकता है, और Sequence Number हर echo request के साथ बढ़ सकता है
  • RFC यह तय नहीं करता कि ID वास्तव में कैसे चुना जाए, इसलिए implementation यानी ping के source code की जांच जरूरी है

iputils ping में ID तय होने का तरीका

  • ping command iputils package में शामिल है
  • ping4_send_probe के पास की comment बताती है कि ICMP echo request बनाते समय ID field random number होता है और Sequence Number बढ़ता हुआ integer होता है
  • ping के अंदर struct ping_rts का ident field होता है
    • default value -1 है
    • CLI option -e से इसे 0 से IDENTIFIER_MAX यानी 0xFFFF के बीच की value से override किया जा सकता है
  • अगर rts->ident == -1 है, तो ping SOCK_DGRAM type और IPPROTO_ICMP protocol के साथ socket पर bind करता है
  • Linux के IPPROTO_ICMP socket description के अनुसार ICMP header send() के समय check और organize किया जाता है, और id socket local port number पर set होता है
  • अगर ping source port specify नहीं करता, तो समझा जाता है कि Linux kernel कोई खाली port random तरीके से चुनता है, और वही port ICMP packet के ID के तौर पर इस्तेमाल होता है

जब समान ICMP ID collide करता है

  • दोनों clients से ping -e 999 का इस्तेमाल करके समान ICMP ID 999 के साथ server को ping भेजा गया
  • server capture के result में एक client की request ने ID 999 बनाए रखा, लेकिन दूसरे client की request ID 30218 में बदल गई
  • NAT device बाहरी IP और समान ICMP ID के combination में collision न हो, इसके लिए एक तरफ का ID बदलता है
  • collision handling कहां होती है, यह खोजने के लिए Linux के net/netfilter directory में ICMP id field इस्तेमाल करने वाला code देखा गया

netfilter, conntrack, और NAT की भूमिका

  • iptables rules लागू करने वाला kernel subsystem netfilter है
  • MASQUERADE rule NAT करता है, इसलिए ICMP NAT implementation भी netfilter के अंदर है
  • nf_nat_core.c का nf_nat_setup_info get_unique_tuple को call करता है, जो आगे nf_nat_l4proto_unique_tuple तक जाता है
  • nf_nat_l4proto_unique_tuple में IPPROTO_ICMP case है और यह tuple->src.u.icmp.id को reference करता है
  • nf_nat_proto.c का nf_nat_manip_pkt, nf_nat_ipv4_manip_pkt और l4proto_manip_pkt से होकर ICMP होने पर icmp_manip_pkt को call करता है
  • icmp_manip_pkt असली ICMP ID को packet में hdr->un.echo.id = tuple->src.u.icmp.id से लिखता है

conntrack tuple में ICMP को represent करने का तरीका

  • netfilter में connection का मतलब सिर्फ TCP connection नहीं है; UDP या ICMP जैसे connectionless protocols में भी यह outgoing packets और incoming packets को जोड़ने वाली state को दर्शाता है
  • nf_conn में tuplehash[IP_CT_DIR_MAX] होता है
    • IP_CT_DIR_ORIGINAL: outgoing packet direction
    • IP_CT_DIR_REPLY: incoming response direction
  • हर nf_conntrack_tuple_hash में connection को identify करने वाला nf_conntrack_tuple होता है
  • tuple manipulate किए जा सकने वाले src और immutable dst में बंटा होता है
    • src में IP address और protocol-specific fields होते हैं
    • ICMP का protocol-specific field __be16 id है
    • dst में न बदलने वाला IP address और ICMP type, code होते हैं
  • NAT outgoing packet को कैसे बदला गया, यह connection में store करता है और response packet में उस बदलाव को reverse करता है

ICMP ID चुनने का code path

  • जब natbox ICMP echo receive करता है, तो nf_nat_setup_info नया connection बनाता है और तय करता है कि source IP और ICMP ID बदलने हैं या नहीं
  • इसके बाद हर ICMP packet के लिए nf_nat_manip_pkt connection में store values के अनुसार source या destination IP और ICMP ID set करता है
  • get_unique_tuple उपलब्ध NAT tuple चुनने वाला core path है
    • find_best_ips_proto source IP address को rewrite करता है
    • nf_nat_used_tuple जांचता है कि tuple पहले से इस्तेमाल में है या नहीं, और अगर इस्तेमाल में नहीं है तो current tuple को वैसे ही return करता है
    • इसी वजह से अगर दो clients के ICMP IDs अलग हैं, तो NAT किए गए packets में भी ID preserve रहता है
    • अगर tuple पहले से इस्तेमाल में है, तो nf_nat_l4proto_unique_tuple call होता है और protocol-specific NAT किया जाता है
  • ICMP में tuple->src.u.icmp.id को NAT target key के रूप में चुना जाता है
  • find_free_id get_random_u16() से random ID generate करता है, उसे valid ICMP ID range में adjust करता है, और फिर जांचता है कि वह इस्तेमाल में है या नहीं
  • default ID range पूरा ID range है, और iptables MASQUERADE rule में --to-ports से 100-200 जैसी range specify की जा सकती है
  • अगर unused tuple नहीं मिलता, तो duplicate ID connection में रह जाता है, और बाद में __nf_conntrack_confirm duplicate detect करके packet drop कर देता है

bpftrace से verify किया गया kernel behavior

  • समझे गए netfilter behavior को verify करने के लिए bpftrace का इस्तेमाल किया गया
  • trace target kernel functions nf_nat_setup_info और nf_nat_manip_pkt हैं
  • kprobe function call के समय को trace करता है, और kretprobe function return के समय को trace करता है
  • kretprobe में function arguments तक सीधे access नहीं हो सकता, इसलिए entry के समय BPF map में arguments store किए गए और return पर दोबारा read किए गए
  • struct sk_buff वह structure है जिससे Linux kernel packet represent करता है
  • bswap network byte order यानी big endian को little endian में बदलने के लिए इस्तेमाल होता है
  • ntop IP address को string में बदलता है
  • नए Linux kernels के BPF Type Format(BTF) की वजह से BPF program sk_buff, nf_conn जैसे kernel data structures को header include किए बिना reference कर सकता है
  • यह bpftrace program Linux kernel 6.2.9 पर test किया गया, और दूसरे kernel versions में यह चलेगा या नहीं, अलग हो सकता है

trace results और conclusion

  • जब दोनों clients समान ICMP ID 999 से ping भेजते हैं, तो nf_nat_setup_info हर client के लिए एक बार call होता है
  • पहले client 192.168.99.1 में original tuple और reply tuple दोनों ICMP ID 999 बनाए रखते हैं
  • दूसरे client 192.168.99.2 में reply tuple का ICMP ID 32809 में rewrite होता है
  • nf_nat_manip_pkt echo request में NF_NAT_MANIP_SRC से source IP को 10.0.100.1 में बदलता है, और response में NF_NAT_MANIP_DST से destination IP को original client IP में वापस करता है
  • response packet का ICMP ID भी NAT की गई value से original client द्वारा भेजी गई value में restore होता है
  • default ICMP conntrack timeout /proc/sys/net/netfilter/nf_conntrack_icmp_timeout में देखा जा सकता है, और observed default value 30 seconds थी
  • अगर client 30 seconds से ज्यादा packet नहीं भेजता, तो अगले ping पर nf_nat_setup_info फिर call होता है
  • Linux का ping NAT behavior Netfilter Hacking HOWTO में भी documented है, और core बात conntrack tuples तथा ICMP ID rewriting में है

1 टिप्पणियां

 
GN⁺ 2023-09-11
Hacker News की टिप्पणियाँ
  • https://samy.pl/pwnat/ में आपकी दिलचस्पी हो सकती है
    सर्वर शुरू होते ही fixed address 3.3.3.3 पर fixed ICMP echo request packet भेजना शुरू करता है, और उम्मीद करता है कि यह packet वापस नहीं आएगा
    3.3.3.3 न तो कोई पहुंच योग्य host है और न ही spoof करने का target. इसके बजाय, जब client connect करने की कोशिश करता है, तो उसे server IP पता होता है, इसलिए वह server को ICMP Time Exceeded packet भेजता है. उस ICMP packet के अंदर वही “मूल” fixed packet होता है जिसे server 3.3.3.3 पर भेज रहा था, और यह hardcoded packet pwnat के identifier की तरह काम करता है
    client इंटरनेट पर किसी hop होने का नाटक करते हुए server को बताता है कि उसका मूल “ICMP echo request” deliver नहीं हो पाया. NAT देखता है कि ICMP Time Exceeded के अंदर का packet server द्वारा भेजे गए packet से match करता है, और उसे NAT के पीछे मौजूद server तक forward कर देता है; इस दौरान client का पूरा IP header भी शामिल होता है, इसलिए server को client IP address पता चल जाता है
    • संक्षेप में, 3.3.3.3 पर ping करने वाली trick NAT के पीछे मौजूद server को NAT के पीछे मौजूद client का IP address जानने देती है, और https://ifconfig.co जैसे non-NAT server की जरूरत नहीं पड़ती
      इस tool का मुख्य व्यवहार इसके बाद client और server के बीच UDP tunnel बनाना है
      हालांकि ऊपर-ऊपर देखने पर लगता है कि यह मानता है कि NAT UDP source port को rewrite नहीं करता, इसलिए शायद यह सभी routers पर काम नहीं करेगा. WebRTC आदि में इस्तेमाल होने वाला STUN ज्यादा परिष्कृत तकनीकें implement करता है, लेकिन जब वह भी काम न करे तो relay, यानी TURN, इस्तेमाल करना पड़ता है
      यही समस्या 3.3.3.3 ping trick पर भी लागू होने की काफी संभावना है. लेख में जैसे NAT ping identifier को rewrite करे, तो यह trick टूट जाएगी
  • जब local network की कोई device इंटरनेट की किसी device को ping भेजती है, तो NAT करने वाला router ping के source address को अपने public IP से बदल देता है और ICMP packet के ID field को किसी unique value से rewrite करता है
    response मिलने पर router उस unique ID value का इस्तेमाल करके response को local network की सही device तक पहुंचाता है
    • इसे ज्यादा ठीक से देखने के लिए सोचें कि operating system एक ही destination की ओर जाने वाली अलग-अलग ICMP conversations को कैसे अलग पहचानता है
      सिर्फ एक computer और Wireshark/tcpdump से इसे verify किया जा सकता है
      लेख अपने-आप में अच्छा है, और जिन्हें networking की बिल्कुल समझ नहीं थी उनके लिए यह आंखें खोलने वाला हो सकता है. हालांकि मूल रूप से यह खुद सोचने से ज्यादा एक ठीक-ठाक network lab बनाने और source में गहराई से जाने की विधि जैसा लगता है
    • इस विचार को थोड़ा और आगे बढ़ाएं, तो यह stateless protocol को stateful protocol में बदलने जैसा है
    • ping को भी request और response match करने के लिए आखिरकार ऐसी state information चाहिए ही होती है
    • सोच रहा हूँ कि “unique value” की जगह source private IP क्यों नहीं इस्तेमाल करते
    • सोच रहा हूँ कि वह ID ICMP header में है या IP side से संबंधित है
  • “यह कैसे काम करता है” किस्म के लेख को abstraction layers से नीचे उतरकर source code तक जाते देखना अच्छा लगा. explanation भी अच्छी है और जानकारी भी भरपूर है
    • मैं भी यही कहने आया था. routing और networking अभी भी उलझाते हैं, और इससे जुड़े लेख आम तौर पर बहुत abstract लगते हैं
      इस तरह के hands-on examples के लिए मैं वाकई आभारी हूँ, और इन्हें खुद follow करके देखने का सोच रहा हूँ
      इस topic पर मुझे अच्छी तरह समझ आया ऐसा लगभग सिर्फ एक और लेख Tailscale का यह लेख था. इसमें “worked examples” ज्यादा हैं, जिससे साफ होता है कि पूरा सिस्टम कैसे आपस में fit होता है
      https://tailscale.com/blog/how-nat-traversal-works/
  • अच्छा लेख है
    संयोग से इसी weekend मैं OpenWRT router पर transparent proxy चालू करने के लिए Netfilter से जूझ रहा था
    Netfilter देखते समय default reference के तौर पर इस्तेमाल करने लायक resources हैं https://wiki.nftables.org/wiki-nftables/index.php/Main_Page और https://www.netfilter.org/projects/nftables/manpage.html
  • ICMP में ports नहीं होते, इसलिए NAT को ICMP echo response को सही port पर वापस भेजने की समस्या से निपटने की जरूरत नहीं होती
    लेकिन ICMP echo request में ID होती है, और वह असल में source port number जैसा ही role निभाती है
    ICMP echo को सही तरह NAT करने के लिए, जैसे UDP के source port को remap किया जाता है, वैसे ही ID को भी दोनों दिशाओं में remap करना होगा
    क्योंकि NAT के पीछे की machine अगर एक साथ दो hosts से ping receive करे, और दोनों hosts संयोग से एक ही request number इस्तेमाल करें, तो ambiguity पैदा हो जाएगी
    दूसरी संभावना यह है कि identifier को rewrite न किया जाए और हर ID से जुड़ी remote machines की list maintain की जाए. अगर ID collide हो, तो list में एक से ज्यादा remote IP addresses होंगे, और NAT के पीछे की machine से response आने पर NAT list में से एक को चुनकर response उस machine को भेज दे और फिर entry हटा दे
  • NAT सचमुच एक गंदी abstraction है. IPv4 खत्म हो जाना चाहिए
    • मेरे home internet में 192.168 range के कई subnets में devices हैं. कुछ समय पहले ISP बदलते समय मेरे घर वाला AS बदल गया और नया IPv4 address मिला, लेकिन WAN router पर नए IP पर आने वाला traffic forwarding update करना ही काफी था
      अगर IPv6 होता, तो network के सभी nodes बदलने पड़ते और internal DNS भी update करना पड़ता
      theory में मेरे पास portable /48 हो सकता है, लेकिन नए ISP को इसे advertise करना होगा, और मौजूदा ISP अगर कर भी दे तो यह आम बात नहीं है
      एक हफ्ते पहले phone line कट गई थी, तो मैंने 5G MiFi निकाला और WAN connection उस पर shift कर दिया; उस interface पर बस सरल सा masquerade लगाना काफी था. signal weak था इसलिए अच्छा नहीं था, लेकिन काम कर गया
      समस्या यह है कि IPv6 करने पर भी अब भी dual stack चलाना पड़ेगा या वही गंदी NAT abstraction इस्तेमाल करनी पड़ेगी. मेरे लिए कोई फायदा नहीं, बस काम बढ़ता है
      काम वाली side पर भी यही है. vehicles internal 172.16/12 subnets इस्तेमाल करते हैं, आपस में connect और route करते हैं, और अलग-अलग VPN connections के जरिए बाहर से जुड़ते हैं. वे अक्सर basement में parked होते हैं, जहां signal लगभग नहीं होता, इसलिए setup ऐसा है कि कई तरीकों में से कोई एक तो काम कर जाए

IPv6 पर जाने पर फिर से /48s को माइग्रेट करना पड़ेगा। ऊपर से ये वाहन कई sports stadiums से इंटरनेट लेते हैं, जिनमें से कई के लिए MITM/443 बंद करना या UDP blocking हटाना भी मुश्किल होता है। शनिवार सुबह 10 बजे पहुँचकर 2 घंटे बाद चालू होना हो, ऐसे माहौल में यह काम नहीं करेगा
dual stack पर जाकर काम और जोखिम दोनों को दोगुना करने का business benefit क्या है, समझ नहीं आता

  • पक्का नहीं कि IPv6 इस समस्या को हल करेगा। तकनीकी तौर पर हाँ, लेकिन बड़े providers घर के users को सिर्फ /64 दे रहे हैं और “business” /48 के लिए भारी शुल्क लगा रहे हैं, जिससे पहले ही IPv6 NAT या /64 की अतिरिक्त subdivision की नौबत आ रही है। असल में ऐसा नहीं होना चाहिए
  • तब तो CG-NAT से और भी ज्यादा चिढ़ होगी
  • Lindy effect को ध्यान में रखना चाहिए। यह observation है कि technology या idea जैसी non-perishable चीजों की भविष्य की उम्र उनकी मौजूदा उम्र के अनुपात में होती है; IPv4 जितना पुराना है, उतना ही संभव है कि आगे भी काफी समय तक बना रहे
    https://en.wikipedia.org/wiki/Lindy_effect
  • IPv6 को भी खत्म हो जाना चाहिए। dominant बनने के लिए इसे पर्याप्त समय मिला, लेकिन यह लगातार अटका ही रहा
  • सोच रहा हूँ कि क्या central server के बिना NAT traversal संभालते हुए UDP-based P2P networking में छोटे messages भेजने के लिए ping का दुरुपयोग किया जा सकता है। message वाला हिस्सा शायद किसी ने पहले ही समझ लिया है
    https://stackoverflow.com/questions/31857419/how-to-send-a-m...
    अफसोस, ping को operating system handle करता है, इसलिए peer IP की apps messages पढ़ नहीं सकतीं
    शायद अब समय आ गया है कि ऐसी कुछ services के लिए user-space hooks दिए जाएँ, ताकि दोनों तरफ NAT के पीछे होने पर भी असली P2P संभव हो सके। कम से कम read-only event stream जैसा कुछ तो हो। अब इसे रोकने वाली सारी बाधाएँ कृत्रिम लगती हैं
    • एक छोटा technical correction: ping UDP नहीं, ICMP है
      हालांकि ping का इस्तेमाल करने वाली data exfiltration strategies या दूसरे communication methods देखे हैं। आजकल ज्यादातर firewalls की default settings ping समेत सभी ICMP को चुपचाप drop कर देती हैं, इसलिए P2P के लिए यह लगभग नामुमकिन लगता है
    • दिलचस्प विचार है। id असल में (sport, dport) के बराबर जैसा दिखता है, लेकिन 16-bit है, इसलिए 32-bit की तुलना में space काफी छोटा है
      लेकिन NAT hole punching की मुख्य समस्या यह नहीं है कि connection बनाने के लिए दोनों endpoints पर activity चाहिए? इसलिए हमेशा एक coordination server चाहिए, जो T को बताए कि node S, node T से बात करना चाहता है
      फिर भी सोचने लायक बात है। ICMP routing messages, जैसे unreachable या TTL expired, से कोई तरीका हो सकता है क्या। किसी IP पर traceroute करने पर arbitrary दूसरे IPs से packets वापस मिलते हैं, और यह आम तौर पर NAT पार कर जाता है
      कल्पना कर सकते हैं कि host T, जो incoming connections लेना चाहता है, कोई random “dummy” IP address चुने, (router IP, dummy IP) को अपना identifier बनाकर publish करे, और periodic रूप से उस dummy IP पर packets भेजे। T से बात करना चाहने वाला host S उस dummy address के बारे में ICMP TTL-expired T के router को भेज सकता है, और router उसे देखकर T तक forward कर सकता है
      बेशक यह इस पर निर्भर करेगा कि ICMP fields के अंदर का IP address, IP header में मौजूद address की तरह ingress filtering से गुजरता है या नहीं
      edit: इस idea के implementation की ओर इशारा करने वाला top-level comment पहले ही आ गया है
    • यह पहले से है: https://samy.pl/pwnat/
    • जो ढूँढ रहा था उससे बिल्कुल वही नहीं है, लेकिन ping का दुरुपयोग सुनकर pingfs याद आया। यह cloud computing को बिल्कुल नई परिभाषा देता है
      [1] - https://github.com/yarrick/pingfs
    • IPv6 adoption और बढ़ेगा तो सबके पास publicly routable IP होगा और NAT को पूरी तरह avoid किया जा सकेगा, इसलिए ऐसी समस्याएँ कम हो जाएँगी
  • ऐसे blog posts लिखते समय किसी खास code line से link करना, और उस link को समय के साथ alive और useful बनाए रखना, बेहद मुश्किल और चिढ़ाने वाला है
    GitHub हो तो किसी specific commit hash, filename और line number के combination से बाँध सकते हैं, लेकिन codebase बहुत बदल जाए तो वह बहुत useful नहीं रहता। git.blender.org जैसे कम इस्तेमाल होने वाले git web views में यह ठीक से नहीं हुआ
    • Linux kernel code के लिए elixir इस्तेमाल करें तो कम से कम किसी specific version से link कर सकते हैं। अगर थोड़ी persistence चाहिए तो LTS version इस्तेमाल कर सकते हैं
      https://elixir.bootlin.com/linux/latest/source
  • संक्षेप में, ICMP packet के अंदर एक id field होता है और Netfilter, ICMP packet या frame को “special case” के रूप में पहचानता है