LabHub
배우기 러닝패스 코스

Network Fundamentals — Hands-on in a Linux VM

Push a Private Network Out with NAT

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

이 실습은 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 장비가 재시작되면 모든 연결이 끊깁니다. "안에서는 되는데 밖에서 안 된다" 는 신고의 답은 거의 언제나 이 장에 있습니다.

단계

  1. 네임스페이스 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 로 하세요.
  2. lan 에서 uplink 게이트웨이 10.0.2.1 로 ping 이 안 되는 결과를 /root/nat/before.txt 에 저장하세요.
  3. 테이블 ip labnatpostrouting 훅 체인에 10.30.0.0/24 가 uplink 로 나갈 때 counter masquerade 하는 규칙을 넣어 ping 이 되게 하세요.
  4. uplink 와 lan-r 에서 동시에 ICMP 를 캡처하며 lan 에서 ping 을 보내, 바깥에서 본 출발지가 바뀐 것을 /root/nat/outside.txt 와 안쪽의 원래 출발지를 /root/nat/inside.txt 에 저장하세요.
  5. /etc/netns/lan/resolv.conf 에 상류 DNS 서버를 적어 lan 안에서 getent hosts archive.ubuntu.com 이 되게 하고 그 결과를 /root/nat/dns.txt 에 저장하세요.
  6. lan 에서 curl -sI http://archive.ubuntu.com/ 의 응답 머리를 /root/nat/web.txt 에 저장하세요.
  7. nft list table ip labnat/root/nat/counter.txt 에 저장하세요. 마스커레이드 규칙의 counter packets 가 0 보다 커야 합니다.
  8. 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 에 저장하세요.

참고

사설망 한 대

네임스페이스 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 labnatpostrouting 훅 체인에 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 labnatcounter 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.