- IPv4 NAT आम तौर पर TCP/UDP ports से response के लक्ष्य को अलग करता है, लेकिन
pingके ICMP echo में port नहीं होता, इसलिए Linux किस value को mapping key के तौर पर इस्तेमाल करता है, यही मुख्य बात है - प्रयोग में network namespaces से
client1,client2,natbox,serverबनाए गए, औरiptablesMASQUERADE के जरिए 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_DGRAMpath में 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/24client2:192.168.99.2/24natboxinternal interface:192.168.99.3/24natboxexternal interface:10.0.100.1/24server:10.0.100.2/24
- Fedora 38 Server VM और Linux kernel 6.2.9 पर
ip,iptables,tcpdumpआदि root के रूप में चलाए गए - दोनों clients
br0bridge से जुड़े, औरnatboxbridge तथा server-side veth pair से अलग-अलग जुड़ा - clients का default route
192.168.99.3पर set किया गया ताकि server की ओर जाने वाला trafficnatboxसे होकर गुजरे natboxमेंnet.ipv4.ip_forward=1से packet forwarding चालू की गई, औरiptablesकीnattable कीPOSTROUTINGchain में MASQUERADE rule जोड़ा गयाip netns exec natbox iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
packet capture में दिखा ICMP NAT
client1औरservernamespaces मेंtcpdump -n icmpसे ICMP packets capture किए गए- client side पर
192.168.99.1 > 10.0.100.2echo request और10.0.100.2 > 192.168.99.1echo reply दिखे - server side पर उसी request का source IP
10.0.100.1में बदला हुआ था, जिससे पुष्टि हुई कि NAT ने source address कोnatboxके external IP में rewrite किया - अलग-अलग clients की ICMP requests में अलग-अलग id fields थे
- example में
client1का ID31428था client2का ID33391था
- example में
- यह observation दिखाता है कि
natboxICMP 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है
- echo request का Type
- 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 तय होने का तरीका
pingcommand iputils package में शामिल हैping4_send_probeके पास की comment बताती है कि ICMP echo request बनाते समय ID field random number होता है और Sequence Number बढ़ता हुआ integer होता हैpingके अंदरstruct ping_rtsकाidentfield होता है- default value
-1है - CLI option
-eसे इसे0सेIDENTIFIER_MAXयानी0xFFFFके बीच की value से override किया जा सकता है
- default value
- अगर
rts->ident == -1है, तोpingSOCK_DGRAMtype औरIPPROTO_ICMPprotocol के साथ socket पर bind करता है - Linux के
IPPROTO_ICMPsocket description के अनुसार ICMP headersend()के समय check और organize किया जाता है, और id socket local port number पर set होता है - अगर
pingsource 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 ID30218में बदल गई - NAT device बाहरी IP और समान ICMP ID के combination में collision न हो, इसके लिए एक तरफ का ID बदलता है
- collision handling कहां होती है, यह खोजने के लिए Linux के
net/netfilterdirectory में ICMPidfield इस्तेमाल करने वाला code देखा गया
netfilter, conntrack, और NAT की भूमिका
iptablesrules लागू करने वाला kernel subsystem netfilter है- MASQUERADE rule NAT करता है, इसलिए ICMP NAT implementation भी netfilter के अंदर है
nf_nat_core.cकाnf_nat_setup_infoget_unique_tupleको call करता है, जो आगेnf_nat_l4proto_unique_tupleतक जाता हैnf_nat_l4proto_unique_tupleमेंIPPROTO_ICMPcase है और यह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 directionIP_CT_DIR_REPLY: incoming response direction
- हर
nf_conntrack_tuple_hashमें connection को identify करने वालाnf_conntrack_tupleहोता है - tuple manipulate किए जा सकने वाले
srcऔर immutabledstमें बंटा होता हैsrcमें IP address और protocol-specific fields होते हैं- ICMP का protocol-specific field
__be16 idहै dstमें न बदलने वाला IP address और ICMPtype,codeहोते हैं
- NAT outgoing packet को कैसे बदला गया, यह connection में store करता है और response packet में उस बदलाव को reverse करता है
ICMP ID चुनने का code path
- जब
natboxICMP echo receive करता है, तोnf_nat_setup_infoनया connection बनाता है और तय करता है कि source IP और ICMP ID बदलने हैं या नहीं - इसके बाद हर ICMP packet के लिए
nf_nat_manip_pktconnection में store values के अनुसार source या destination IP और ICMP ID set करता है get_unique_tupleउपलब्ध NAT tuple चुनने वाला core path हैfind_best_ips_protosource IP address को rewrite करता हैnf_nat_used_tupleजांचता है कि tuple पहले से इस्तेमाल में है या नहीं, और अगर इस्तेमाल में नहीं है तो current tuple को वैसे ही return करता है- इसी वजह से अगर दो clients के ICMP IDs अलग हैं, तो NAT किए गए packets में भी ID preserve रहता है
- अगर tuple पहले से इस्तेमाल में है, तो
nf_nat_l4proto_unique_tuplecall होता है और protocol-specific NAT किया जाता है
- ICMP में
tuple->src.u.icmp.idको NAT target key के रूप में चुना जाता है find_free_idget_random_u16()से random ID generate करता है, उसे valid ICMP ID range में adjust करता है, और फिर जांचता है कि वह इस्तेमाल में है या नहीं- default ID range पूरा ID range है, और
iptablesMASQUERADE rule में--to-portsसे100-200जैसी range specify की जा सकती है - अगर unused tuple नहीं मिलता, तो duplicate ID connection में रह जाता है, और बाद में
__nf_conntrack_confirmduplicate detect करके packet drop कर देता है
bpftrace से verify किया गया kernel behavior
- समझे गए netfilter behavior को verify करने के लिए
bpftraceका इस्तेमाल किया गया - trace target kernel functions
nf_nat_setup_infoऔरnf_nat_manip_pktहैं kprobefunction call के समय को trace करता है, औरkretprobefunction return के समय को trace करता हैkretprobeमें function arguments तक सीधे access नहीं हो सकता, इसलिए entry के समय BPF map में arguments store किए गए और return पर दोबारा read किए गएstruct sk_buffवह structure है जिससे Linux kernel packet represent करता हैbswapnetwork byte order यानी big endian को little endian में बदलने के लिए इस्तेमाल होता हैntopIP address को string में बदलता है- नए Linux kernels के BPF Type Format(BTF) की वजह से BPF program
sk_buff,nf_connजैसे kernel data structures को header include किए बिना reference कर सकता है - यह
bpftraceprogram 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 ID999बनाए रखते हैं - दूसरे client
192.168.99.2में reply tuple का ICMP ID32809में rewrite होता है nf_nat_manip_pktecho 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 टिप्पणियां
Hacker News की टिप्पणियाँ
सर्वर शुरू होते ही 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 पता चल जाता है
इस 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 टूट जाएगी
response मिलने पर router उस unique ID value का इस्तेमाल करके response को local network की सही device तक पहुंचाता है
सिर्फ एक computer और Wireshark/tcpdump से इसे verify किया जा सकता है
लेख अपने-आप में अच्छा है, और जिन्हें networking की बिल्कुल समझ नहीं थी उनके लिए यह आंखें खोलने वाला हो सकता है. हालांकि मूल रूप से यह खुद सोचने से ज्यादा एक ठीक-ठाक network lab बनाने और source में गहराई से जाने की विधि जैसा लगता है
इस तरह के 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 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 हटा दे
अगर 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 क्या है, समझ नहीं आता
https://en.wikipedia.org/wiki/Lindy_effect
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 जैसा कुछ तो हो। अब इसे रोकने वाली सारी बाधाएँ कृत्रिम लगती हैं
हालांकि 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 पहले ही आ गया है
[1] - https://github.com/yarrick/pingfs
GitHub हो तो किसी specific commit hash, filename और line number के combination से बाँध सकते हैं, लेकिन codebase बहुत बदल जाए तो वह बहुत useful नहीं रहता। git.blender.org जैसे कम इस्तेमाल होने वाले git web views में यह ठीक से नहीं हुआ
https://elixir.bootlin.com/linux/latest/source