본문 바로가기

iptables

Ubuntu에서 iptables을 이용한 NAT

1. 필자의 글 nftables examples 참고

nftables을 이용한 NAT은 nftables examples 에서 "Basic NAT"을 검색하여 확인합니다. 

iptables을 이용한 NAT은 nftables examples 에서 "Multiple NATs using nftables maps"을 검색하여 확인합니다. iptables을 이용한 DNAT, SNAT 예가 있습니다. 함께 nftables을 이용한 NAT도 볼 수 있습니다. 

 

2. 다른 문서 참고

참고1: https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=alice_k106&logNo=221305928714 

참고2: https://gist.github.com/tomasinouk/eec152019311b09905cd

참고3: https://www.cyberciti.biz/tips/linux-iptables-examples.html

참고4: https://www.digitalocean.com/community/tutorials/iptables-essentials-common-firewall-rules-and-commands

 

3. 이 글 참고

  1. 시험환경 구성
  2. ip_forward 설정
  3. R3에서 ssh, icmp와 connection 응답만 허용
  4. 내부와 외부를 기본으로 차단하고 분리
  5. 외부에서 내부로 향하는 트래픽에 대한 NAT
  6. 내부에서 외부로 향하는 트래픽에 대한 NAT

3.1. 시험환경 구성

시험 환경을 아래와 같이 구성하고 설정합니다. 아래 구성도는 VirtualBox, Ubuntu로 삼각형 네트웍 환경 구성하기를 변형한 것입니다. 

  • 외부에서는 내부시스템에 접근할 수 없습니다. 
  • 외부에서는 내부시스템을 접근할 때 1.1.1.0/24을 목적지로 설정해야 하며, response 마저도 1.1.1.0/24에서 받습니다. 즉 NAT를 이용해야 합니다. 
  • 내부에서 외부로 접근하는 것은 제한 없습니다. 다만 이때 source address는 default로 1.1.1.254로 설정합니다. 
  • 외부에서는 내부에 특정한 destination-address/destination-port만 접근 가능합니다. 
  • 방화벽은 시험을 위해 ssh, icmp는 허용합니다. 

 

r2 route table

  • r2, r3 사이의 링크 (192.168.23.0/24)와 내부 시스템에 대한 pool (1.1.1.0/24)에 대한 경로만 가지고 있습니다. 
salsal@r2:~$ sudo route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
1.1.1.0         192.168.23.3    255.255.255.0   UG    0      0        0 enp0s10
192.168.23.0    0.0.0.0         255.255.255.0   U     0      0        0 enp0s10

r3 route table

  • 내부 (192.168.3.0/24)와 외부 (192.168.23.0/24)에 대한 정보를 알고 있습니다. 
salsal@r3:~$ sudo route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.3.0     0.0.0.0         255.255.255.0   U     0      0        0 enp0s8
192.168.23.0    0.0.0.0         255.255.255.0   U     0      0        0 enp0s10

pc1,2,3 route table

  • 192.16.0.0/16에 대해서는 default gateway로 r3로 알고 있습니다. 
salsal@pc1:~$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.0.0     192.168.3.1     255.255.0.0     UG    0      0        0 enp0s8
192.168.3.0     0.0.0.0         255.255.255.0   U     0      0        0 enp0s8

 

3.2. R3 ip_forward 설정

r3에서 ip forwarding을 enable해 줍니다. 

salsal@r3:~$ sudo sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0
salsal@r3:~$ sudo sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1

## 리부팅 후에도 동작하게 하려면 /etc/sysctl.conf에서 net.ipv4.ip_forward=1을 comment out (#을 삭제)
## salsal@r3:~$ grep ipv4.ip_forward /etc/sysctl.conf
## #net.ipv4.ip_forward=1

 

3.3. R3에서 ssh, icmp와 connection 응답만 허용

R3를 방화벽이라고 가정합니다. R3가 자신에게 들어오는 것을 허용하는 것은 ssh, icmp, 그리고 연결에 대한 response만입니다. ssh는 아예 허용하지 않거나 특정 source address를 허용하는 것을 권장합니다만, 이 글에서는 source address를 지정하지 않고 허용합니다. 

salsal@r3:~$ sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
salsal@r3:~$ sudo iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
salsal@r3:~$ sudo iptables -A INPUT -i lo -j ACCEPT
salsal@r3:~$ sudo iptables -A INPUT -p icmp -j ACCEPT
salsal@r3:~$ sudo iptables -P INPUT DROP
salsal@r3:~$ sudo iptables -S
-P INPUT DROP
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp -j ACCEPT

 

iptables에서 chain은 rule을 담아 놓은 집합을 말합니다. iptables에서 기본으로 정의하여 사용하는 chain이 있는데 그것은 INPUT, OUTPUT, FORWARD입니다. 

  • INPUT: local system bound traffic에 적용하는 룰을 담든 곳입니다. 
  • OUTPUT: local system generated traffic에 적용하는 룰을 담는 곳입니다.
  • FORWARD: local system을 경유하는 트래픽에 적용하는 룰을 담는 곳입니다.  

따라서 R3 bound traffic에 대해 chain은 INPUT을 사용한 것입니다. 

 

3.4. R3 방화벽: 내부와 외부를 기본으로 차단하고 분리

R3를 경유하는 트래픽을 기본적으로 차단 (iptables -P FORWARD DROP) 합니다. 이렇게 하여 내부와 외부를 분리합니다.

 

salsal@r3:~$ sudo iptables -P FORWARD DROP
salsal@r3:~$ sudo iptables -S
-P INPUT DROP
-P FORWARD DROP
-P OUTPUT ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp -j ACCEPT

3.5. R3 방화벽: 내부 -> 외부는 모두 허용, 외부 -> 내부는 특정 목적지만 허용

 

내부에서 외부로 향하는 트래픽은 모두 허용합니다. chain FORWARD에 룰을 적용합니다. 

salsal@r3:~$ sudo iptables -A FORWARD -o enp0s10 -j ACCEPT

외부에서 내부로 또는 내부에서 외부로 향하는 트래픽중 response 트래픽은 모두 허용합니다. chain FORWARD에 룰을 적용합니다. 

salsal@r3:~$ sudo iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
salsal@r3:~$ sudo iptables -S
-P INPUT DROP
-P FORWARD DROP
-P OUTPUT ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A FORWARD -o enp0s10 -j ACCEPT
-A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT

외부에서 내부로 향하는 트래픽 중에서 PC1, 2, 3로 향하는 특정 port (여기에서는 80)만 허용합니다. chain FORWARD에 룰을 적용합니다. 

salsal@r3:~$ sudo iptables -A FORWARD -d 192.168.3.11/32 -p tcp -m tcp --dport 80 -j ACCEPT
salsal@r3:~$ sudo iptables -A FORWARD -d 192.168.3.12/32 -p tcp -m tcp --dport 80 -j ACCEPT
salsal@r3:~$ sudo iptables -A FORWARD -d 192.168.3.13/32 -p tcp -m tcp --dport 80 -j ACCEPT
salsal@r3:~$ sudo iptables -t filter -L -v --line-number
Chain INPUT (policy DROP 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1     1688 92168 ACCEPT     tcp  --  any    any     anywhere             anywhere             tcp dpt:ssh
2      365 1735K ACCEPT     all  --  any    any     anywhere             anywhere             state RELATED,ESTABLISHED
3       28  2357 ACCEPT     all  --  lo     any     anywhere             anywhere
4        0     0 ACCEPT     icmp --  any    any     anywhere             anywhere

Chain FORWARD (policy DROP 4 packets, 336 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        1    84 ACCEPT     all  --  any    enp0s10  anywhere             anywhere
2        1    84 ACCEPT     all  --  any    any     anywhere             anywhere             state RELATED,ESTABLISHED
3        0     0 ACCEPT     tcp  --  any    any     anywhere             192.168.3.11         tcp dpt:http
4        0     0 ACCEPT     tcp  --  any    any     anywhere             192.168.3.12         tcp dpt:http
5        0     0 ACCEPT     tcp  --  any    any     anywhere             192.168.3.13         tcp dpt:http

Chain OUTPUT (policy ACCEPT 425 packets, 37982 bytes)
num   pkts bytes target     prot opt in     out     source               destination

 

3.5. 현재 상태

R2에서는 내부 네트웍에 직접 접근할 수 없습니다. 이유는 R2 입장에서는 192.168.3.0/24 네트웍이 unreachable 상태입니다. 

R2에서 강제로 192.168.3.0/24 향하는 패켓을 R3로 던져도 chain FORWARD에서는 ACCPET에 매칭되는 룰이 없어 차단됩니다. 

PC1, 2, 3에서 외부로 보내는 패켓은 모두 통과합니다. 

 

그런데 여기에서 문제점이 있습니다. PC1에서 r2로 ping을 보낸 것을 tcpdump 한 것입니다. 

salsal@r2:~$ sudo tcpdump -i enp0s10 icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s10, link-type EN10MB (Ethernet), capture size 262144 bytes
16:34:11.197560 IP 192.168.3.11 > r2: ICMP echo request, id 5085, seq 26, length 64
16:34:11.197584 IP r2 > 192.168.3.11: ICMP echo reply, id 5085, seq 26, length 64
16:34:12.197974 IP 192.168.3.11 > r2: ICMP echo request, id 5085, seq 27, length 64
16:34:12.197997 IP r2 > 192.168.3.11: ICMP echo reply, id 5085, seq 27, length 64

문제점은 PC1의 IP address 192.168.3.11 가 그대로 노출됩니다. 내부 네트웍은 IP address pool 1.1.1.0/24로 모두 위장(가장, masquerade) 되어야 합니다. 

 

따라서 이제 NAT을 이용할 차례입니다. 

 

3.6. DNAT, SNAT 설정

tables and chains in iptables

 

위 그림을 보면 iptables을 이용한 NAT는 chain PREROUTING, chain OUTPUT, chain POSTROUTING에 적용할 수 있다는 것을 알 수 있습니다. 그런데 우리는 R3를 경유하는 트래픽에 대해서만 NAT를 적용하므로, 사용하는 chain은 PREROUTING과 POSTROUTING 입니다. 

 

3.6.1. 외부에서 내부로 향하는 트래픽에 대한 NAT

 

R2에서 1.1.1.1/80으로 서비스 요청을 하면 패켓의 흐름은 아래 그림과 같습니다. 

 

즉 외부에서 내부로 향하는 패켓에 대해 Destination address를 변경해 줘야 하며, r3에서 chain PREROUTING에 적용합니다. 

salsal@r3:~$ sudo iptables -t nat -L -v -n
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
 
salsal@r3:~$ sudo iptables -t nat -A PREROUTING -d 1.1.1.1 -p tcp --dport 80 -j DNAT --to-destination 192.168.3.11:80
salsal@r3:~$ sudo iptables -t nat -A PREROUTING -d 1.1.1.2 -p tcp --dport 80 -j DNAT --to-destination 192.168.3.12:80
salsal@r3:~$ sudo iptables -t nat -A PREROUTING -d 1.1.1.3 -p tcp --dport 80 -j DNAT --to-destination 192.168.3.13:80

salsal@r3:~$ sudo iptables -t nat -L -v -n --line-number
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 DNAT       tcp  --  *      *       0.0.0.0/0            1.1.1.1              tcp dpt:80 to:192.168.3.11:80
2        0     0 DNAT       tcp  --  *      *       0.0.0.0/0            1.1.1.2              tcp dpt:80 to:192.168.3.12:80
3        0     0 DNAT       tcp  --  *      *       0.0.0.0/0            1.1.1.3              tcp dpt:80 to:192.168.3.13:80

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination

 

이렇게 한 상태에서 r2에서 http request를 하고, r2와 pc1에서 tcpdump를 해 source address, destination address를 살펴보면 NAT가 제대로 동작한 것을 알 수 있습니다. 

 

r2 tcpdump

  • r2에서 1.1.1.1/80으로 http request하고 1.1.1.1과 주고 받는 packet
salsal@r2:~$ sudo tcpdump -i enp0s10 port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s10, link-type EN10MB (Ethernet), capture size 262144 bytes
18:30:37.199447 IP r2.38622 > one.one.one.one.http: Flags [S], seq 3241348608, win 64240, options [mss 1460,sackOK,TS val 1683949503 ecr 0,nop,wscale 7], length 0
18:30:37.200601 IP one.one.one.one.http > r2.38622: Flags [S.], seq 2769024009, ack 3241348609, win 65160, options [mss 1460,sackOK,TS val 596135752 ecr 1683949503,nop,wscale 7], length 0
18:30:37.200613 IP r2.38622 > one.one.one.one.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 1683949504 ecr 596135752], length 0
18:30:37.201352 IP r2.38622 > one.one.one.one.http: Flags [P.], seq 1:135, ack 1, win 502, options [nop,nop,TS val 1683949505 ecr 596135752], length 134: HTTP: GET / HTTP/1.1
18:30:37.202116 IP one.one.one.one.http > r2.38622: Flags [.], ack 135, win 509, options [nop,nop,TS val 596135754 ecr 1683949505], length 0
18:30:37.202327 IP one.one.one.one.http > r2.38622: Flags [P.], seq 1:7241, ack 135, win 509, options [nop,nop,TS val 596135754 ecr 1683949505], length 7240: HTTP: HTTP/1.1 200 OK
18:30:37.202330 IP one.one.one.one.http > r2.38622: Flags [P.], seq 7241:11230, ack 135, win 509, options [nop,nop,TS val 596135754 ecr 1683949505], length 3989: HTTP
18:30:37.202336 IP r2.38622 > one.one.one.one.http: Flags [.], ack 7241, win 474, options [nop,nop,TS val 1683949506 ecr 596135754], length 0
18:30:37.202488 IP r2.38622 > one.one.one.one.http: Flags [.], ack 11230, win 451, options [nop,nop,TS val 1683949506 ecr 596135754], length 0
18:30:37.206216 IP r2.38622 > one.one.one.one.http: Flags [F.], seq 135, ack 11230, win 501, options [nop,nop,TS val 1683949510 ecr 596135754], length 0
18:30:37.207163 IP one.one.one.one.http > r2.38622: Flags [F.], seq 11230, ack 136, win 509, options [nop,nop,TS val 596135759 ecr 1683949510], length 0
18:30:37.207170 IP r2.38622 > one.one.one.one.http: Flags [.], ack 11231, win 501, options [nop,nop,TS val 1683949511 ecr 596135759], length 0

 

pc1 tcpdump

  • pc1이 r2와 주고 받는 패켓
  • r2의 source address (192.168.23.2)는 그대로 pc1까지 전달됨
salsal@pc1:~$ sudo tcpdump -i enp0s8 port 80
[sudo] password for salsal:
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s8, link-type EN10MB (Ethernet), capture size 262144 bytes
18:23:27.147014 IP 192.168.23.2.38620 > pc1.http: Flags [S], seq 3546489102, win 64240, options [mss 1460,sackOK,TS val 1683819955 ecr 0,nop,wscale 7], length 0
18:23:27.147062 IP pc1.http > 192.168.23.2.38620: Flags [S.], seq 3981414444, ack 3546489103, win 65160, options [mss 1460,sackOK,TS val 596006139 ecr 1683819955,nop,wscale 7], length 0
18:23:27.148332 IP 192.168.23.2.38620 > pc1.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 1683819956 ecr 596006139], length 0
18:23:27.148776 IP 192.168.23.2.38620 > pc1.http: Flags [P.], seq 1:135, ack 1, win 502, options [nop,nop,TS val 1683819957 ecr 596006139], length 134: HTTP: GET / HTTP/1.1
18:23:27.148811 IP pc1.http > 192.168.23.2.38620: Flags [.], ack 135, win 509, options [nop,nop,TS val 596006141 ecr 1683819957], length 0
18:23:27.149331 IP pc1.http > 192.168.23.2.38620: Flags [P.], seq 1:7241, ack 135, win 509, options [nop,nop,TS val 596006141 ecr 1683819957], length 7240: HTTP: HTTP/1.1 200 OK
18:23:27.149350 IP pc1.http > 192.168.23.2.38620: Flags [P.], seq 7241:11230, ack 135, win 509, options [nop,nop,TS val 596006141 ecr 1683819957], length 3989: HTTP

 

3.6.2. 내부에서 외부로 향하는 트래픽에 대한 NAT

이제 내부에서 외부로 연결을 요청할 때 밖으로 나가는 트래픽에 대해 source address를 192.168.3.0/24에서 1.1.1.254로 변경해야 합니다. r3의 외부 interface enp0s10의 address로 가장(maquerade)할 수 있으나, 여기에서는 1.1.1.254로 지정합니다. 

salsal@r3:~$ sudo iptables -t nat -A POSTROUTING -s 192.168.3.0/24 -o enp0s10 -j SNAT --to 1.1.1.254
salsal@r3:~$ sudo iptables -t nat -L -v -n --line-number
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        4   240 DNAT       tcp  --  *      *       0.0.0.0/0            1.1.1.1              tcp dpt:80 to:192.168.3.11:80
2        0     0 DNAT       tcp  --  *      *       0.0.0.0/0            1.1.1.2              tcp dpt:80 to:192.168.3.12:80
3        0     0 DNAT       tcp  --  *      *       0.0.0.0/0            1.1.1.3              tcp dpt:80 to:192.168.3.13:80

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 SNAT       all  --  *      enp0s10  192.168.3.0/24       0.0.0.0/0            to:1.1.1.254

이 상태에서 pc1에서 r2로 http request를 한 상태에서, r2에서 tcpdump를 해 보면 1.1.1.254에서 http request를 보낸 것을 볼 수 있습니다. 

salsal@r2:~$ sudo tcpdump -i enp0s10 port 80

19:23:00.755838 IP 1.1.1.254.58934 > r2.http: Flags [S], seq 2878851821, win 64240, options [mss 1460,sackOK,TS val 599280778 ecr 0,nop,wscale 7], length 0
19:23:00.755864 IP r2.http > 1.1.1.254.58934: Flags [S.], seq 916484378, ack 2878851822, win 65160, options [mss 1460,sackOK,TS val 303885625 ecr 599280778,nop,wscale 7], length 0
19:23:00.757190 IP 1.1.1.254.58934 > r2.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 599280779 ecr 303885625], length 0
19:23:00.757638 IP 1.1.1.254.58934 > r2.http: Flags [P.], seq 1:140, ack 1, win 502, options [nop,nop,TS val 599280780 ecr 303885625], length 139: HTTP: GET / HTTP/1.1
19:23:00.757698 IP r2.http > 1.1.1.254.58934: Flags [.], ack 140, win 508, options [nop,nop,TS val 303885627 ecr 599280780], length 0
19:23:00.757964 IP r2.http > 1.1.1.254.58934: Flags [P.], seq 1:7241, ack 140, win 508, options [nop,nop,TS val 303885627 ecr 599280780], length 7240: HTTP: HTTP/1.1 200 OK
19:23:00.757980 IP r2.http > 1.1.1.254.58934: Flags [P.], seq 7241:11230, ack 140, win 508, options [nop,nop,TS val 303885627 ecr 599280780], length 3989: HTTP
19:23:00.758714 IP 1.1.1.254.58934 > r2.http: Flags [.], ack 7241, win 474, options [nop,nop,TS val 599280781 ecr 303885627], length 0
19:23:00.758903 IP 1.1.1.254.58934 > r2.http: Flags [.], ack 11230, win 451, options [nop,nop,TS val 599280781 ecr 303885627], length 0
19:23:00.800014 IP 1.1.1.254.58934 > r2.http: Flags [F.], seq 140, ack 11230, win 501, options [nop,nop,TS val 599280784 ecr 303885627], length 0
19:23:00.800089 IP r2.http > 1.1.1.254.58934: Flags [F.], seq 11230, ack 141, win 508, options [nop,nop,TS val 303885670 ecr 599280784], length 0
19:23:00.800849 IP 1.1.1.254.58934 > r2.http: Flags [.], ack 11231, win 501, options [nop,nop,TS val 599280823 ecr 303885670], length 0

 

'iptables' 카테고리의 다른 글

ipset  (0) 2021.08.11
Ubuntu iptables 저장, 리부팅 뒤 자동 복구  (0) 2021.08.01
Ubuntu ufw & iptables & nftables  (0) 2021.07.31