Thuta Learning
Computer Networking
IntermediateDevOps & Toolsbeginner

NAT နဲ့ Private Address Space

ဒီခန်းပြီးရင် ဘာတတ်သွားမလဲ

  • NAT နဲ့ Private Address Space concept ကို နားလည်ရှင်းပြနိုင်ရန်
  • Diagram ကို ဖတ်ပြီး packet/data ဘယ်လိုသွားလာသလဲ ခြေရာခံနိုင်ရန်
  • နမူနာ code ကို ကိုယ်တိုင် run ပြီး output စစ်နိုင်ရန်

နားလည်ထားရမယ့် အချက်

IPv4 address ဟာ 32 bit ရှိလို့ စုစုပေါင်း လေးဘီလီယံ ကျော်ကျော်သာ ရှိပြီး၊ 1990 ခုနှစ်တွေမှာကတည်းက မလုံလောက်တော့မှန်း ရှင်းရှင်းလင်းလင်း သိခဲ့ကြတယ်။ ဖြေရှင်းချက် နှစ်ခု ပေါ်လာတယ် — ရေရှည်အတွက် IPv6၊ ချက်ချင်း သုံးလို့ရဖို့အတွက် NAT ဖြစ်တယ်။ NAT ရဲ့ အခြေခံ အယူအဆက ရိုးရှင်းတယ် — network တစ်ခုအတွင်းက device တွေဟာ ပြင်ပကနေ တိုက်ရိုက် ရောက်ဖို့ မလိုတာ အများစု ဖြစ်လို့၊ တစ်ကမ္ဘာလုံးမှာ တစ်ခုတည်းသော address မလိုဘဲ ကိုယ့် network ထဲမှာသာ တစ်မူထူးခြားတဲ့ address ပေးလိုက်ရင် ရပြီ။ RFC 1918 က ဒီအတွက် range သုံးခုကို သီးသန့် ဖယ်ထားပေးတယ် — 10.0.0.0/8, 172.16.0.0/12 နဲ့ 192.168.0.0/16 ဖြစ်ပြီး၊ ဒီ address တွေကို public internet router တွေက ဘယ်တော့မှ forward မလုပ်ဘူး။ ဒါကြောင့် ရုံးပေါင်း သန်းချီက 192.168.1.10 ကို တစ်ပြိုင်နက် သုံးနေလို့ ရတာ ဖြစ်တယ်။

တကယ်တမ်း အသုံးများတာက address ကိုပဲ ပြန်ရေးတဲ့ NAT မဟုတ်ဘဲ port ပါ ပြန်ရေးတဲ့ PAT (NAPT) ဖြစ်တယ်။ inside host တစ်ခုက packet ပို့လိုက်ရင် router က source address ကို သူ့ public IP အဖြစ် ပြောင်းရုံမက source port ကိုပါ သူကိုယ်တိုင် ရွေးထားတဲ့ တစ်မူထူးခြားတဲ့ port အဖြစ် ပြောင်းပြီး၊ 'outside port ဒီဟာ ဆိုတာ inside host ဒီဟာရဲ့ port ဒီဟာ' ဆိုတဲ့ အချက်အလက်ကို translation table ထဲ မှတ်ထားလိုက်တယ်။ အဖြေ ပြန်ရောက်လာတဲ့အခါ router က destination port ကို ကြည့်ပြီး ဘယ် host ဆီ ပို့ရမလဲ ဆိုတာ သိတယ် — ဒါကြောင့် port ဟာ မဖြစ်မနေ လိုအပ်တာ ဖြစ်တယ်။ host အားလုံးရဲ့ address ကို တစ်ခုတည်း လုပ်ပစ်လိုက်ပြီးရင် သူတို့ကို ခွဲခြားပေးနိုင်တဲ့ တစ်ခုတည်းသော အရာက port ပဲ ကျန်တော့တယ်။

ဒီနေရာကနေပဲ NAT ရဲ့ အကြီးမားဆုံး အားနည်းချက် ပေါ်လာတယ်။ table ထဲက entry တွေဟာ inside ကနေ ထွက်တဲ့ packet တစ်ခုက ဖန်တီးပေးမှသာ ရှိလာတာ ဖြစ်တယ်။ ပြင်ပက တစ်စုံတစ်ယောက်က ကိုယ့် public IP ဆီ connection အသစ် စဖွင့်လာရင် router မှာ ကြည့်စရာ entry မရှိလို့ ဘယ် host ဆီ ပို့ရမှန်း မသိဘဲ ချသွားတယ်။ ဒါကြောင့် အိမ်က network ထဲမှာ server run ထားရင် ပြင်ပကနေ မရောက်တာ ဖြစ်တယ်။ port forwarding ဆိုတာ ဒီပြဿနာအတွက် ကြိုတင်ပြီး လက်နဲ့ ထည့်ထားတဲ့ table entry တစ်ခုမျှသာ ဖြစ်တယ် — '8080 ကို ရောက်လာသမျှ 192.168.1.50:80 ဆီ ပို့' ဆိုတာမျိုး ဖြစ်တယ်။

text
NAT TRANSLATION: BEFORE, AFTER, AND THE TABLE
---------------------------------------------
BEFORE (inside the LAN)                AFTER (on the Internet)
src 192.168.1.10:51230  --[NAT]-->     src 198.51.100.7:50000
src 192.168.1.11:51230  --[NAT]-->     src 198.51.100.7:50001
src 192.168.1.10:51231  --[NAT]-->     src 198.51.100.7:50002

NAT TRANSLATION TABLE (state held only in the router's memory)
+--------------+-------------+--------------+----------+
| inside addr  | inside port | outside port | protocol |
+--------------+-------------+--------------+----------+
| 192.168.1.10 |       51230 |        50000 | TCP      |
| 192.168.1.11 |       51230 |        50001 | TCP      |
| 192.168.1.10 |       51231 |        50002 | TCP      |
+--------------+-------------+--------------+----------+

reply arriving for  198.51.100.7:50001
   -> matches row 2 -> rewritten to 192.168.1.11:51230  DELIVERED

fresh inbound SYN to 198.51.100.7:8080
   -> no row exists -> nothing to rewrite to     DROPPED
   -> port forwarding = a row you add by hand, ahead of time

လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်

ရုံးထဲမှာ Raspberry Pi ပေါ်က dashboard တစ်ခုကို ပြင်ပက လှမ်းကြည့်ချင်တယ် ဆိုပါစို့။ Pi က 192.168.1.50:80 မှာ run နေပြီး ရုံးထဲက ဘယ် laptop ကမဆို ဖွင့်လို့ ရတယ်၊ ဒါပေမယ့် အိမ်ကနေ ရုံးရဲ့ public IP ဖြစ်တဲ့ 198.51.100.7 ကို ခေါ်ကြည့်ရင် ဘာမှ မတုံ့ပြန်ဘူး။ အကြောင်းက Pi မှာ firewall ရှိလို့ မဟုတ်ဘဲ router ရဲ့ NAT table ထဲမှာ ဒီ connection အတွက် entry မရှိလို့ ဖြစ်တယ်။ inside ကနေ မထွက်ခဲ့လို့ ဘယ်တော့မှ ဖန်တီးမခံရဘူး။

ဖြေရှင်းနည်း သုံးမျိုး ရှိတယ်။ အရိုးရှင်းဆုံးက router မှာ port forwarding rule တစ်ခု ထည့်ဖို့ — outside port 8080 ကို 192.168.1.50:80 ဆီ ချိတ်လိုက်တာ ဖြစ်ပြီး၊ ဒါက NAT table ထဲက အမြဲတမ်း entry တစ်ခု ကို လက်နဲ့ ထည့်လိုက်တာ ဖြစ်တယ်။ ဒုတိယက Pi ကိုယ်တိုင်ကနေ ပြင်ပက server တစ်ခုဆီ outbound connection ဖွင့်ပြီး အဲဒီ connection ကို ပြန်သုံးတဲ့ reverse tunnel ဖြစ်တယ် — outbound ဖြစ်လို့ entry အလိုအလျောက် ဖန်တီးခံရပြီး router မှာ ဘာမှ ပြင်စရာ မလိုတော့ဘူး။ တတိယက dynamic DNS နဲ့ တွဲသုံးဖို့ ဖြစ်ပြီး၊ ဒါက ISP က public IP ကို ပုံမှန် ပြောင်းပေးနေတဲ့ ပြဿနာကို ဖြေရှင်းပေးတယ် — NAT ပြဿနာကို မဖြေရှင်းပေးဘူး ဆိုတာ သတိထားပါ။ ISP က carrier-grade NAT သုံးထားရင်တော့ ကိုယ့် router ကိုယ်တိုင်က public IP မရှိတာ ဖြစ်လို့ port forwarding က လုံးဝ အလုပ်မလုပ်ဘဲ reverse tunnel တစ်ခုတည်းသာ ကျန်တော့တယ်။

အတူတူ စမ်းရေးကြည့်မယ်

python
import ipaddress

PUBLIC_IP = ipaddress.ip_address("198.51.100.7")   # the router's WAN address
RFC1918 = [ipaddress.ip_network(n) for n in
           ("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16")]


def is_private(addr):
    ip = ipaddress.ip_address(addr)
    return any(ip in net for net in RFC1918)


class NAT:
    """Port Address Translation: many inside hosts, one outside address.
    The key of the table is the OUTSIDE port, because that is the only
    thing in the reply packet that can tell the hosts apart."""

    def __init__(self, first_port=50000):
        self.table = {}          # outside port -> (inside ip, inside port)
        self.next_port = first_port

    def outbound(self, in_ip, in_port, dst_ip, dst_port):
        for out_port, entry in self.table.items():
            if entry == (in_ip, in_port):
                break
        else:
            out_port = self.next_port
            self.next_port += 1
            self.table[out_port] = (in_ip, in_port)
        return (str(PUBLIC_IP), out_port)

    def inbound(self, out_port):
        return self.table.get(out_port)


nat = NAT()
FLOWS = [
    ("192.168.1.10", 51230, "203.0.113.5", 443),
    ("192.168.1.11", 51230, "203.0.113.5", 443),   # same inside port!
    ("192.168.1.10", 51231, "203.0.113.9", 80),
]

print("OUTBOUND: source address and port are rewritten")
print("inside socket           becomes                 destination")
print("----------------------  ----------------------  ---------------")
for ip, port, dip, dport in FLOWS:
    out_ip, out_port = nat.outbound(ip, port, dip, dport)
    print("%-22s  %-22s  %s:%d"
          % ("%s:%d" % (ip, port), "%s:%d" % (out_ip, out_port), dip, dport))

print()
print("NAT TRANSLATION TABLE")
print("outside port  ->  inside socket")
for out_port in sorted(nat.table):
    ip, port = nat.table[out_port]
    print("%12d  ->  %s:%d" % (out_port, ip, port))

print()
print("INBOUND: replies are matched by outside port")
for probe in (50001, 50099):
    entry = nat.inbound(probe)
    if entry:
        print("reply to %s:%d -> forwarded to %s:%d"
              % (PUBLIC_IP, probe, entry[0], entry[1]))
    else:
        print("reply to %s:%d -> NO TABLE ENTRY, dropped"
              % (PUBLIC_IP, probe))

print()
for addr in ("192.168.1.10", "10.20.30.5", "172.20.0.1",
             "172.32.0.1", "198.51.100.7"):
    print("%-14s private(RFC1918)=%s" % (addr, is_private(addr)))
You should see
OUTBOUND: source address and port are rewritten
inside socket           becomes                 destination
----------------------  ----------------------  ---------------
192.168.1.10:51230      198.51.100.7:50000      203.0.113.5:443
192.168.1.11:51230      198.51.100.7:50001      203.0.113.5:443
192.168.1.10:51231      198.51.100.7:50002      203.0.113.9:80

NAT TRANSLATION TABLE
outside port  ->  inside socket
       50000  ->  192.168.1.10:51230
       50001  ->  192.168.1.11:51230
       50002  ->  192.168.1.10:51231

INBOUND: replies are matched by outside port
reply to 198.51.100.7:50001 -> forwarded to 192.168.1.11:51230
reply to 198.51.100.7:50099 -> NO TABLE ENTRY, dropped

192.168.1.10   private(RFC1918)=True
10.20.30.5     private(RFC1918)=True
172.20.0.1     private(RFC1918)=True
172.32.0.1     private(RFC1918)=False
198.51.100.7   private(RFC1918)=False

၅ မိနစ် စမ်းကြည့်

FLOWS ထဲကို 192.168.1.10:51230 ကနေ ထပ်တူ ထပ်မံ ထည့်ကြည့်ပါ — table ထဲမှာ row အသစ် တိုးလား၊ ဘာကြောင့်လဲ။ ပြီးရင် port forwarding ကို ကိုယ်စားပြုဖို့ table ထဲကို အစကတည်းက entry တစ်ခု ကြိုထည့်ပြီး inbound probe က ဘာလို့ အောင်မြင်သွားလဲ ကြည့်ပါ။

သတိလေးတစ်ချက်

NAT ကို security feature လို့ မှတ်တာ။ inbound ကို ပိတ်ဆို့ပေးတာက ဘေးထွက်ရလဒ်သာ ဖြစ်ပြီး UPnP (သို့) compromise ခံရတဲ့ inside host တစ်ခုက အဲဒီ 'ကာကွယ်မှု' ကို ချက်ချင်း ဖောက်ထွက်နိုင်တယ်။ firewall က သီးခြား လိုအပ်တယ်။

172.16.0.0/12 ဆိုတာ 172.16.x.x တစ်ခုတည်းပဲ လို့ ထင်တာ။ အမှန်က 172.16.0.0 ကနေ 172.31.255.255 အထိ ဖြစ်ပြီး 172.32.0.0 က public ဖြစ်နေတယ် — ဒါက subnet ဒီဇိုင်းမှာ မှားလေ့ရှိတဲ့ အချက် ဖြစ်တယ်။

RFC 1918 - Address Allocation for Private InternetsComputer Networking

ဒီနေရာမှာ လူအများမှားတတ်တယ်

  • NAT ကို security feature လို့ မှတ်တာ။ inbound ကို ပိတ်ဆို့ပေးတာက ဘေးထွက်ရလဒ်သာ ဖြစ်ပြီး UPnP (သို့) compromise ခံရတဲ့ inside host တစ်ခုက အဲဒီ 'ကာကွယ်မှု' ကို ချက်ချင်း ဖောက်ထွက်နိုင်တယ်။ firewall က သီးခြား လိုအပ်တယ်။
  • 172.16.0.0/12 ဆိုတာ 172.16.x.x တစ်ခုတည်းပဲ လို့ ထင်တာ။ အမှန်က 172.16.0.0 ကနေ 172.31.255.255 အထိ ဖြစ်ပြီး 172.32.0.0 က public ဖြစ်နေတယ် — ဒါက subnet ဒီဇိုင်းမှာ မှားလေ့ရှိတဲ့ အချက် ဖြစ်တယ်။
  • နမူနာ code ကို production network ပေါ် တိုက်ရိုက်မစမ်းဘဲ local/test environment တွင် အရင်အတည်ပြုပါ။

လေ့ကျင့်ခန်း

FLOWS ထဲကို 192.168.1.10:51230 ကနေ ထပ်တူ ထပ်မံ ထည့်ကြည့်ပါ — table ထဲမှာ row အသစ် တိုးလား၊ ဘာကြောင့်လဲ။ ပြီးရင် port forwarding ကို ကိုယ်စားပြုဖို့ table ထဲကို အစကတည်းက entry တစ်ခု ကြိုထည့်ပြီး inbound probe က ဘာလို့ အောင်မြင်သွားလဲ ကြည့်ပါ။

You'll know it worked when: OUTBOUND: source address and port are rewritten inside socket becomes destination ---------------------- ---------------------- --------------- 192.168.1.10:51230 198.51.100.7:50000 203.0.113.5:443 192.168.1.11:51230 198.51.100.7:50001 203.0.113.5:443 192.168.1.10:51231 198.51.100.7:50002 203.0.113.9:80 NAT TRANSLATION TABLE outside port -> inside socket 50000 -> 192.168.1.10:51230 50001 -> 192.168.1.11:51230 50002 -> 192.168.1.10:51231 INBOUND: replies are matched by outside port reply to 198.51.100.7:50001 -> forwarded to 192.168.1.11:51230 reply to 198.51.100.7:50099 -> NO TABLE ENTRY, dropped 192.168.1.10 private(RFC1918)=True 10.20.30.5 private(RFC1918)=True 172.20.0.1 private(RFC1918)=True 172.32.0.1 private(RFC1918)=False 198.51.100.7 private(RFC1918)=False

NAT နဲ့ Private Address Space | Thuta Learning