Network Fundamentals — Hands-on in a Linux VM
Push a Private Network Out with NAT
한국어 원문으로 표시합니다.
이 실습은 VM 에서 돕니다
이번엔 VM 자신이 라우터입니다. 네임스페이스 lan 이 사설망의 호스트이고,
VM 의 uplink(enp1s0, 10.0.2.2)가 인터넷 쪽입니다. 사설 주소 10.30.0.2 는
바깥에서 돌아올 길이 없으므로, VM 이 자기 주소로 바꿔 내보내야 합니다 — 그것이
마스커레이드입니다. 이 VM 은 공인 80/443 과 DNS 에만 닿습니다.
목표
마스커레이드(SNAT)로 사설망 호스트를 인터넷에 내보내고, 패킷의 출발지가 실제로
바뀌는 것을 양쪽 인터페이스에서 잡아 보고, DNAT 로 바깥에서 온 접속을 안쪽
서버로 넘깁니다. 집 공유기와 도커의 -p 가 하는 일이 정확히 이것입니다.
왜 중요한가
사설 주소는 인터넷에서 라우팅되지 않습니다. 그런데 집·회사·클라우드 VPC· 쿠버네티스 파드가 전부 사설 주소입니다. 이들이 바깥과 이야기하는 방법은 하나뿐입니다 — 경계에서 주소를 바꾸고, 응답이 오면 되돌리는 것. 그 되돌림을 가능하게 하는 것이 연결 추적(conntrack)이고, 그래서 NAT 장비가 재시작되면 모든 연결이 끊깁니다. "안에서는 되는데 밖에서 안 된다" 는 신고의 답은 거의 언제나 이 장에 있습니다.
단계
- 네임스페이스
lan과 vethlan0(lan,10.30.0.2/24)↔lan-r(루트,10.30.0.1/24)을 만들고,lan의 기본 경로를10.30.0.1로, 루트의net.ipv4.ip_forward를 1 로 하세요. lan에서 uplink 게이트웨이10.0.2.1로 ping 이 안 되는 결과를/root/nat/before.txt에 저장하세요.- 테이블
ip labnat의postrouting훅 체인에10.30.0.0/24가 uplink 로 나갈 때counter masquerade하는 규칙을 넣어 ping 이 되게 하세요. - uplink 와
lan-r에서 동시에 ICMP 를 캡처하며lan에서 ping 을 보내, 바깥에서 본 출발지가 바뀐 것을/root/nat/outside.txt와 안쪽의 원래 출발지를/root/nat/inside.txt에 저장하세요. /etc/netns/lan/resolv.conf에 상류 DNS 서버를 적어lan안에서getent hosts archive.ubuntu.com이 되게 하고 그 결과를/root/nat/dns.txt에 저장하세요.lan에서curl -sI http://archive.ubuntu.com/의 응답 머리를/root/nat/web.txt에 저장하세요.nft list table ip labnat을/root/nat/counter.txt에 저장하세요. 마스커레이드 규칙의counter packets가 0 보다 커야 합니다.lan안에10.30.0.2:80웹 서버를 띄우고, 네임스페이스guest(vethguest010.31.0.2/24 ↔guest-r10.31.0.1/24)를 만들어,prerouting훅 체인의iifname "guest-r" tcp dport 8080 dnat to 10.30.0.2:80규칙으로guest에서http://10.31.0.1:8080/이 되게 하고 그 응답 첫 줄을/root/nat/dnat.txt에 저장하세요.
참고
- uplink 이름은
ip route show default의dev입니다(이 VM 은enp1s0). - 마스커레이드:
nft add table ip labnat; nft 'add chain ip labnat post { type nat hook postrouting priority 100; }'; nft add rule ip labnat post ip saddr 10.30.0.0/24 oifname "enp1s0" counter masquerade. - 네임스페이스는
/etc/resolv.conf도 따로입니다./etc/netns/<이름>/resolv.conf가 있으면ip netns exec가 그것을 덮어 씌워 줍니다. 상류 서버 주소는resolvectl dns enp1s0. - DNAT:
nft 'add chain ip labnat pre { type nat hook prerouting priority -100; }'. - 흔한 실수:
ip_forward를 빼먹는 것. NAT 규칙이 있어도 넘겨 주지 않으면 소용없습니다.
사설망 한 대
네임스페이스 lan 과 veth lan0(lan, 10.30.0.2/24)↔lan-r(루트, 10.30.0.1/24)을 만들고, lan 의 기본 경로를 10.30.0.1 로, 루트의 net.ipv4.ip_forward 를 1 로 하세요.
이번엔 veth 한쪽(lan-r)을 옮기지 않고 루트에 둡니다. ip addr add 10.30.0.1/24 dev lan-r; ip link set lan-r up. ip -n lan route add default via 10.30.0.1. sysctl -w net.ipv4.ip_forward=1.
NAT 없이는
lan 에서 uplink 게이트웨이 10.0.2.1 로 ping 이 안 되는 결과를 /root/nat/before.txt 에 저장하세요.
ip netns exec lan ping -c 1 -W 2 10.0.2.1 > /root/nat/before.txt 2>&1. 요청은 VM 을 거쳐 나가지만 응답이 10.30.0.2 로 돌아올 길이 없어 100% packet loss 입니다. 경로가 없을 때(unreachable)와 다른 증상입니다.
마스커레이드
테이블 ip labnat 의 postrouting 훅 체인에 10.30.0.0/24 가 uplink 로 나갈 때 counter masquerade 하는 규칙을 넣어 ping 이 되게 하세요.
nft add table ip labnat, nft 'add chain ip labnat post { type nat hook postrouting priority 100; }', nft add rule ip labnat post ip saddr 10.30.0.0/24 oifname "enp1s0" counter masquerade. 이제 lan 의 ping 이 됩니다.
주소가 바뀌는 순간
uplink 와 lan-r 에서 동시에 ICMP 를 캡처하며 lan 에서 ping 을 보내, 바깥에서 본 출발지가 바뀐 것을 /root/nat/outside.txt 와 안쪽의 원래 출발지를 /root/nat/inside.txt 에 저장하세요.
캡처 둘을 걸어 둡니다: timeout 5 tcpdump -i enp1s0 -nn -l icmp > /root/nat/outside.txt 2>&1 & 와 timeout 5 tcpdump -i lan-r -nn -l icmp > /root/nat/inside.txt 2>&1 &. 그 뒤 ip netns exec lan ping -c 1 10.0.2.1. 바깥 파일은 10.0.2.2 > 10.0.2.1, 안쪽 파일은 10.30.0.2 > 10.0.2.1 이어야 합니다.
네임스페이스의 resolv.conf
/etc/netns/lan/resolv.conf 에 상류 DNS 서버를 적어 lan 안에서 getent hosts archive.ubuntu.com 이 되게 하고 그 결과를 /root/nat/dns.txt 에 저장하세요.
mkdir -p /etc/netns/lan; echo 'nameserver <상류>' > /etc/netns/lan/resolv.conf. 상류 주소는 resolvectl dns enp1s0. 네임스페이스 안에서는 127.0.0.53 스텁이 없으므로 진짜 서버를 직접 적어야 합니다. 질의는 마스커레이드를 타고 나갑니다.
인터넷에 닿는다
lan 에서 curl -sI http://archive.ubuntu.com/ 의 응답 머리를 /root/nat/web.txt 에 저장하세요.
ip netns exec lan curl -sI -m 10 http://archive.ubuntu.com/ > /root/nat/web.txt. 첫 줄이 HTTP/1.1 200 OK 여야 합니다. 사설 주소의 호스트가 인터넷 서버와 TCP 를 맺은 것입니다.
세어 본다
nft list table ip labnat 을 /root/nat/counter.txt 에 저장하세요. 마스커레이드 규칙의 counter packets 가 0 보다 커야 합니다.
3단계에서 counter 를 넣어 두었으므로 nft list table ip labnat 에 counter packets N bytes M masquerade 가 보입니다. N 은 새 연결의 첫 패킷 수입니다 — 이후 패킷은 conntrack 이 처리해 NAT 체인을 다시 타지 않습니다.
포트 포워딩
lan 안에 10.30.0.2:80 웹 서버를 띄우고, 네임스페이스 guest(veth guest0 10.31.0.2/24 ↔ guest-r 10.31.0.1/24)를 만들어, prerouting 훅 체인의 iifname "guest-r" tcp dport 8080 dnat to 10.30.0.2:80 규칙으로 guest 에서 http://10.31.0.1:8080/ 이 되게 하고 그 응답 첫 줄을 /root/nat/dnat.txt 에 저장하세요.
서버: ip netns exec lan setsid python3 -m http.server 80 --bind 10.30.0.2 --directory /root/nat > /root/nat/http.log 2>&1 < /dev/null &. guest 는 1단계와 같은 방법(기본 경로 10.31.0.1). 체인: nft 'add chain ip labnat pre { type nat hook prerouting priority -100; }'. 확인: ip netns exec guest curl -sI http://10.31.0.1:8080/ | head -1.