집에서 쓰는 노트북의 주소는 보통 192.168.0.x 같은 사설 주소다. 그런데 이 주소로는
인터넷의 어떤 서버도 답장을 보낼 수 없다. 그런데도 인터넷이 잘 되는 것은 공유기가
중간에서 주소를 바꿔 주기 때문이다. 이 글에서는 그 공유기를 리눅스 한 대 안에 직접
만들어 본다.
| 확인하는 것 | 공유기의 어떤 기능인가 |
|---|---|
| 사설 주소로는 인터넷에서 답장이 못 온다 | NAT 가 필요한 이유 |
| 나가는 패킷의 출발지를 공인 IP 로 바꾼다 | SNAT (masquerade) |
| 바꾼 내용을 표에 적어 두고 답장을 되돌려 준다 | conntrack (연결 추적) |
| 밖에서 공인 IP:포트로 오면 집 안 서버로 넘긴다 | 포트포워딩 (DNAT) |
| NAT 만 있으면 밖에서 집 안으로 그냥 들어온다 | NAT 는 방화벽이 아니다 |
| 기본은 막고 필요한 것만 연다 | 방화벽 (forward 정책) |
ip netns 로 라우터 만들기에서 만든 "PC — 라우터 — PC" 구조를
그대로 쓴다. 네임스페이스와 라우팅이 처음이면 그 글을 먼저 보고 오면 쉽다. 출력은 모두
실습용 Ubuntu 24.04 가상 머신에서 실제로 실행한 결과다.
먼저 알아 둘 말#
| 말 | 쉽게 말하면 |
|---|---|
| 사설 IP | 집·회사 안에서만 쓰는 주소. 192.168.x.x, 10.x.x.x 등. 인터넷에서는 길이 없다 |
| 공인 IP | 인터넷에서 통하는 주소. 통신사가 공유기에 하나 준다 |
| NAT | 패킷의 주소를 바꿔 쓰는 것 (Network Address Translation) |
| SNAT / masquerade | 출발지(Source) 주소를 바꾼다. 나갈 때 사설 → 공인 |
| DNAT / 포트포워딩 | 목적지(Destination) 주소를 바꾼다. 들어올 때 공인 → 사설 |
| conntrack | 공유기가 "누가 누구와 연결 중인지" 적어 두는 표 |
| nftables | 리눅스의 방화벽·NAT 도구. 명령은 nft |
참고
203.0.113.x는 문서와 실습용으로 정해 둔 주소 범위다. 실제 인터넷 어디에도 쓰이지
않으니 공인 IP 역할로 안심하고 쓸 수 있다.
만들 네트워크#
[pc] 192.168.10.10 ── 집 LAN ── [rtr = 공유기] ── 인터넷 ── 203.0.113.100 [inet]
r-lan: 192.168.10.1 r-wan: 203.0.113.1 웹 서버
pc— 집 안의 컴퓨터. 사설 주소를 쓴다.rtr— 공유기. 집 쪽(r-lan)과 인터넷 쪽(r-wan) 랜카드가 하나씩 있다.203.0.113.1이 이 집의 공인 IP 다.inet— 인터넷 어딘가의 웹 서버. 사설 주소로 가는 길을 모른다. 진짜 인터넷과 같다.
준비물#
- 리눅스 한 대 (여기서는 Ubuntu 24.04 가상 머신)
iproute2,nftables,conntrack,tcpdump,curl,python3
sudo apt install -y iproute2 nftables conntrack tcpdump curl python3
걸리는 시간은 40분 정도다. 관리자 셸에서 시작한다. 끝나면 exit 로 나온다.
sudo -i
1. 네트워크를 만든다#
2주 차와 같은 방법으로 공간 세 개와 랜선 두 가닥을 만든다.
ip netns add pc; ip netns add rtr; ip netns add inet
ip link add pc-eth0 type veth peer name r-lan
ip link add inet-eth0 type veth peer name r-wan
ip link set pc-eth0 netns pc; ip link set r-lan netns rtr
ip link set inet-eth0 netns inet; ip link set r-wan netns rtr
ip -n pc addr add 192.168.10.10/24 dev pc-eth0
ip -n rtr addr add 192.168.10.1/24 dev r-lan
ip -n rtr addr add 203.0.113.1/24 dev r-wan
ip -n inet addr add 203.0.113.100/24 dev inet-eth0
for n in pc rtr inet; do ip -n $n link set lo up; done
ip -n pc link set pc-eth0 up; ip -n rtr link set r-lan up
ip -n rtr link set r-wan up; ip -n inet link set inet-eth0 up
pc 의 게이트웨이를 공유기로 잡고, 공유기의 전달 기능을 켠다.
ip -n pc route add default via 192.168.10.1
ip netns exec rtr sysctl -w net.ipv4.ip_forward=1
인터넷 쪽에 웹 서버를 하나 띄운다. 파이썬에 들어 있는 간단한 웹 서버다.
ip netns exec inet sh -c 'cd /tmp && python3 -m http.server 80 --bind 203.0.113.100 > /tmp/inet-http.log 2>&1 &'
각 공간이 아는 길을 확인한다.
for n in pc rtr inet; do echo "--- $n"; ip -n $n route; done
--- pc
default via 192.168.10.1 dev pc-eth0
192.168.10.0/24 dev pc-eth0 proto kernel scope link src 192.168.10.10
--- rtr
192.168.10.0/24 dev r-lan proto kernel scope link src 192.168.10.1
203.0.113.0/24 dev r-wan proto kernel scope link src 203.0.113.1
--- inet
203.0.113.0/24 dev inet-eth0 proto kernel scope link src 203.0.113.100
inet 에는 192.168.10.x 로 가는 길이 없다. 진짜 인터넷의 서버들도 마찬가지다.
2. NAT 없이 나가 본다 — 답장이 못 온다#
웹 서버 쪽에 녹화를 걸고, pc 에서 웹 서버에 접속한다.
ip netns exec inet timeout 6 tcpdump -i inet-eth0 -nn -c 4 'tcp port 80' &
ip netns exec pc curl -sS -m 4 http://203.0.113.100/
curl: (28) Connection timed out after 4002 milliseconds
웹 서버 쪽 녹화에는 요청이 도착해 있다. 뒷부분은 줄였다.
13:06:45.986549 IP 192.168.10.10.42460 > 203.0.113.100.80: Flags [S], seq 1492983472, …
13:06:47.029877 IP 192.168.10.10.42460 > 203.0.113.100.80: Flags [S], seq 1492983472, …
13:06:48.053909 IP 192.168.10.10.42460 > 203.0.113.100.80: Flags [S], seq 1492983472, …
13:06:49.077900 IP 192.168.10.10.42460 > 203.0.113.100.80: Flags [S], seq 1492983472, …
같은 SYN 이 네 번 왔다. 답장이 한 번도 돌아가지 못해서 pc 가 계속 다시 보낸 것이다.
웹 서버에게 192.168.10.10 으로 가는 길을 물어보면 이유가 보인다.
ip netns exec inet ip route get 192.168.10.10
RTNETLINK answers: Network is unreachable
요청은 갔는데 답장 보낼 길이 없다. 사설 주소는 인터넷에서 길이 없는 주소이기
때문이다. 집 공유기가 NAT 를 하는 이유가 바로 이것이다.
참고
사설 주소는 전 세계 모든 집과 회사가 겹쳐서 쓴다. 우리 집에도, 옆집에도
192.168.10.10이 있을 수 있다. 그러니 인터넷은 이 주소로 가는 길을 만들 수가 없다.
3. masquerade 로 출발지를 바꾼다 (SNAT)#
공유기에 "인터넷 쪽으로 나가는 패킷은 출발지를 내 공인 IP 로 바꿔라" 라는 규칙을 넣는다.
ip netns exec rtr nft add table ip nat
ip netns exec rtr nft add chain ip nat postrouting '{ type nat hook postrouting priority srcnat; policy accept; }'
ip netns exec rtr nft add rule ip nat postrouting oifname r-wan counter masquerade
ip netns exec rtr nft list ruleset
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifname "r-wan" counter packets 0 bytes 0 masquerade
}
}
세 줄의 뜻은 이렇다.
nat라는 규칙 묶음(table)을 만든다.- 그 안에 패킷이 공유기를 막 나가기 직전(
postrouting)에 검사하는 목록(chain)을 만든다. - 나가는 랜카드가
r-wan(인터넷 쪽)이면masquerade한다. "나가는 랜카드의 주소로 출발지를 바꿔라" 는 뜻이다.
이번에는 공유기의 양쪽에서 녹화하면서 다시 접속한다.
ip netns exec rtr timeout 6 tcpdump -i r-lan -nn -c 3 'tcp port 80' &
ip netns exec inet timeout 6 tcpdump -i inet-eth0 -nn -c 3 'tcp port 80' &
ip netns exec pc curl -sS -o /dev/null -w 'http_code=%{http_code}n' http://203.0.113.100/
http_code=200
드디어 된다. 같은 연결의 첫 세 패킷(3-way handshake)이 공유기 양쪽에서 이렇게 보인다.
집 쪽 (r-lan)
IP 192.168.10.10.55666 > 203.0.113.100.80: Flags [S], seq 3897098878, …
IP 203.0.113.100.80 > 192.168.10.10.55666: Flags [S.], seq 3009650718, ack 3897098879, …
IP 192.168.10.10.55666 > 203.0.113.100.80: Flags [.], ack 1, …
인터넷 쪽 (inet-eth0)
IP 203.0.113.1.55666 > 203.0.113.100.80: Flags [S], seq 3897098878, …
IP 203.0.113.100.80 > 203.0.113.1.55666: Flags [S.], seq 3009650718, ack 3897098879, …
IP 203.0.113.1.55666 > 203.0.113.100.80: Flags [.], ack 1, …
- 집 쪽에서는 출발지가
192.168.10.10, 인터넷 쪽에서는203.0.113.1이다. 같은 SYN 인 것은seq 3897098878이 같은 것으로 알 수 있다. - 웹 서버는 공유기의 공인 IP 하고만 이야기한다. 답장도
203.0.113.1로 보낸다. - 공유기는 그 답장의 목적지를 다시
192.168.10.10으로 바꿔 pc 에게 넘긴다.
웹 서버의 접속 기록에도 공유기 주소가 남는다.
tail -1 /tmp/inet-http.log
203.0.113.1 - - [18/Oct/2026 13:06:53] "GET / HTTP/1.1" 200 -
웹 서버 입장에서는 pc 가 있는지조차 모른다. 집 안의 컴퓨터가 몇 대든, 밖에서는 모두
공인 IP 하나로 보인다.
4. conntrack — 공유기의 기억#
답장이 오면 공유기는 그게 누구에게 갈 것인지 어떻게 알까. conntrack 표에 적어 뒀다.
ip netns exec rtr conntrack -L
tcp 6 117 TIME_WAIT src=192.168.10.10 dst=203.0.113.100 sport=55666 dport=80 src=203.0.113.100 dst=203.0.113.1 sport=80 dport=55666 [ASSURED] mark=0 use=1
conntrack v1.4.8 (conntrack-tools): 1 flow entries have been shown.
한 줄에 주소가 두 묶음 있다.
| 묶음 | 뜻 |
|---|---|
앞 src=192.168.10.10 dst=203.0.113.100 sport=55666 dport=80 |
pc 가 보낸 원래 모습 |
뒤 src=203.0.113.100 dst=203.0.113.1 sport=80 dport=55666 |
답장이 돌아올 모습 |
답장이 뒤쪽 모습으로 오면, 공유기는 이 줄을 찾아 앞쪽의 pc 에게 되돌려 준다.
TIME_WAIT 는 연결이 막 끝났다는 뜻이고, 117 은 이 줄이 지워지기까지 남은 초다.
NAT 규칙의 카운터도 본다.
ip netns exec rtr nft list ruleset | grep counter
oifname "r-wan" counter packets 1 bytes 60 masquerade
연결 하나에 패킷이 열 개쯤 오갔는데 카운터는 1 이다. NAT 규칙은 연결의 첫
패킷만 본다. 나머지는 conntrack 표를 보고 같은 방식으로 바꾼다.
5. 포트포워딩 — 밖에서 집 안 서버로 (DNAT)#
이번에는 반대 방향이다. pc 에 웹 서버를 하나 띄우고, 인터넷에서 접속하게 만든다.
ip netns exec pc sh -c 'mkdir -p /tmp/pcweb && cd /tmp/pcweb && echo "hello from pc" > index.html && python3 -m http.server 8080 > /tmp/pc-http.log 2>&1 &'
인터넷에서 보이는 것은 공유기의 공인 IP 뿐이다. 거기의 8080번으로 접속해 본다.
ip netns exec inet curl -sS -m 3 http://203.0.113.1:8080/
curl: (7) Failed to connect to 203.0.113.1 port 8080 after 0 ms: Couldn't connect to server
공유기 자신은 8080번에서 기다리는 프로그램이 없다. 그래서 곧바로 거절(RST)한다.
tcpdump 로 TCP 3-way handshake 보기에서 본 Connection refused 와
같은 상황이다.
공유기에 "공인 IP 의 8080번으로 오면 pc 의 8080번으로 넘겨라" 라는 규칙을 넣는다.
ip netns exec rtr nft add chain ip nat prerouting '{ type nat hook prerouting priority dstnat; policy accept; }'
ip netns exec rtr nft add rule ip nat prerouting iifname r-wan ip daddr 203.0.113.1 tcp dport 8080 counter dnat to 192.168.10.10:8080
이번에는 패킷이 공유기에 막 들어온 직후(prerouting)에 목적지를 바꾼다.
목적지를 먼저 바꿔야 공유기가 "이건 pc 에게 보낼 패킷이구나" 하고 길을 정할 수 있다.
ip netns exec inet curl -sS -m 3 http://203.0.113.1:8080/
tail -1 /tmp/pc-http.log
hello from pc
203.0.113.100 - - [18/Oct/2026 13:08:01] "GET / HTTP/1.1" 200 -
pc 의 웹 서버가 답했다. pc 의 기록에는 접속한 쪽의 진짜 주소 203.0.113.100 이
남는다. 들어올 때는 목적지만 바꾸고 출발지는 그대로 두기 때문이다.
주의
규칙에서
ip daddr 203.0.113.1을 빼면, 공유기 주소가 아닌 곳으로 가는 8080번 접속까지
이 규칙에 걸린다. 처음 실습할 때 이걸 빼먹어서, 카운터가 예상보다 훨씬 많이 올라갔다.
포트포워딩은 어느 주소의 어느 포트인지 좁게 지정한다.
6. NAT 는 방화벽이 아니다#
흔히 "공유기 뒤에 있으니 밖에서 못 들어온다" 고 생각한다. 정말 그런지 시험해 본다.
같은 통신사 망 안에 있는 누군가가 "192.168.10.x 로 가는 길은 저 공유기" 라고 직접
길을 잡았다고 하자. 인터넷 전체에서는 안 되지만, 공유기 바로 바깥의 같은 망에서는
이런 일이 가능하다.
ip -n inet route add 192.168.10.0/24 via 203.0.113.1
ip netns exec inet ping -c 2 -W 1 192.168.10.10
ip netns exec inet curl -sS -m 3 http://192.168.10.10:8080/
64 bytes from 192.168.10.10: icmp_seq=1 ttl=63 time=0.041 ms
64 bytes from 192.168.10.10: icmp_seq=2 ttl=63 time=0.056 ms
2 packets transmitted, 2 received, 0% packet loss, time 1057ms
hello from pc
포트포워딩을 거치지 않고 집 안의 pc 에 그대로 들어갔다. NAT 는 주소를 바꿔 줄 뿐,
아무것도 막지 않는다. 공유기는 그저 길을 아는 라우터라서, 길만 알면 그대로 넘겨준다.
실제 공유기가 안전한 것은 NAT 와 함께 방화벽이 켜져 있기 때문이다. 다음 단계에서
그 방화벽을 만든다.
7. 방화벽 — 기본은 막고 필요한 것만 연다#
공유기를 지나가는 패킷(forward)을 검사하는 규칙을 파일로 만든다. 규칙이 여러 줄일 때는
파일로 한 번에 넣는 편이 읽기 쉽다.
cat > /tmp/fw.nft <<'NFT'
table inet filter {
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
iifname "r-lan" oifname "r-wan" accept
ct status dnat accept
counter drop
}
}
NFT
ip netns exec rtr nft -f /tmp/fw.nft
규칙은 위에서부터 차례로 검사하고, 처음 맞는 줄에서 결정된다.
| 줄 | 뜻 |
|---|---|
policy drop |
아래 어느 줄에도 안 맞으면 버린다. 기본은 차단 |
ct state established,related accept |
이미 허용한 연결의 나머지 패킷과 답장은 통과 |
iifname "r-lan" oifname "r-wan" accept |
집 안에서 인터넷으로 새로 나가는 연결은 허용 |
ct status dnat accept |
포트포워딩 규칙으로 들어온 연결은 허용 |
counter drop |
나머지는 개수를 세고 버린다 |
팁
ct로 시작하는 줄은 conntrack 표를 보고 판단한다. "답장인지", "포트포워딩된 연결인지"
를 패킷 하나만 봐서는 알 수 없는데, 공유기가 연결을 기억하고 있어서 가능하다. 이렇게
연결 상태를 보는 방화벽을 상태 기반 방화벽(stateful firewall)이라고 부른다.
방금 시험을 그대로 다시 해 본다.
ip netns exec inet ping -c 2 -W 1 192.168.10.10
ip netns exec inet curl -sS -m 3 http://192.168.10.10:8080/
2 packets transmitted, 0 received, 100% packet loss, time 1005ms
curl: (28) Connection timed out after 3002 milliseconds
직접 들어오는 길은 막혔다. 버리기만 하고 거절 답장도 안 보내서 refused 가 아니라
timed out 이 된다. 이제 열어 둔 길이 여전히 되는지 본다.
ip netns exec inet curl -sS -m 3 http://203.0.113.1:8080/
ip netns exec pc curl -sS -o /dev/null -w 'http_code=%{http_code}n' http://203.0.113.100/
ip netns exec pc ping -c 1 -W 1 203.0.113.100
hello from pc
http_code=200
64 bytes from 203.0.113.100: icmp_seq=1 ttl=63 time=0.044 ms
| 시험 | 방화벽 전 | 방화벽 후 |
|---|---|---|
| 인터넷 → pc 직접 ping | 통과 | 차단 |
| 인터넷 → pc:8080 직접 접속 | 통과 | 차단 |
| 인터넷 → 공인 IP:8080 (포트포워딩) | 통과 | 통과 |
| pc → 인터넷 웹·ping | 통과 | 통과 |
카운터로 무엇이 얼마나 걸렸는지 본다.
ip netns exec rtr nft list ruleset | grep counter
oifname "r-wan" counter packets 3 bytes 204 masquerade
iifname "r-wan" ip daddr 203.0.113.1 tcp dport 8080 counter packets 2 bytes 120 dnat to 192.168.10.10:8080
counter packets 5 bytes 348 drop
- masquerade 3 — pc 가 밖으로 낸 연결 세 개(웹 두 번, ping 한 번)
- dnat 2 — 포트포워딩으로 들어온 연결 두 개
- drop 5 — 직접 ping 2개, 직접 접속 SYN 3개(재전송 포함)
8. 정리하기#
pkill -f http.server
for n in pc rtr inet; do ip netns del $n; done
ip netns list
exit
ip netns list 가 아무것도 안 보여 주면 깨끗해진 것이다.
확인#
처음부터 다시 했다면 아래가 모두 맞아야 한다.
- masquerade 전에는 pc → 웹 서버가
(28) timed out, 후에는http_code=200 - 웹 서버 로그의 접속 주소가
203.0.113.1 conntrack -L에 주소 묶음이 두 개인 줄이 보인다- 포트포워딩 후
curl http://203.0.113.1:8080/이hello from pc - 방화벽 후 인터넷 → pc 직접 접속은 실패, 포트포워딩과 pc → 인터넷은 성공
정리#
- SNAT(masquerade) 는 나가는 패킷의 출발지를 공인 IP 로 바꾼다. 사설 주소로 인터넷을 쓸 수 있는 이유다.
- DNAT(포트포워딩) 는 들어오는 패킷의 목적지를 집 안 서버로 바꾼다. 대상은 좁게 잡는다.
- conntrack 이 연결마다 바꾼 내용을 기억해서 답장을 제자리로 돌려준다. NAT 규칙은 연결의 첫 패킷만 본다.
- NAT 는 방화벽이 아니다. 지켜 주는 것은 "기본은 차단, 답장과 허용한 것만 통과" 라는 방화벽 정책이다.
nftables 에서 각 기능이 걸리는 자리#
| 하는 일 | chain 의 hook | 언제 |
|---|---|---|
| 포트포워딩 (DNAT) | prerouting |
패킷이 공유기에 막 들어왔을 때 |
| 방화벽 (지나가는 패킷) | forward |
공유기를 거쳐 다른 랜선으로 넘어갈 때 |
| masquerade (SNAT) | postrouting |
패킷이 공유기를 막 나갈 때 |
다음 실습에서는 같은 공유기를 OPNsense 라는 방화벽 전용 운영체제로 다시 만든다.
웹 화면에서 누르는 버튼 하나하나가 오늘 손으로 쓴 규칙의 어느 줄에 해당하는지 비교해 본다.

댓글