서론
- 오픈스택 대시보드에서 라우터를 생성하고 연결했을 때, 리눅스 커널 레벨(Network Namespace, Interface, IPTables)에서 실제로 어떤 객체가 생성되고, 패킷이 어떻게 변환되는지를 확인해보려고 한다.
과정
- 리눅스 네트워크 네임스페이스
- 리눅스 커널은 Network Namespace라는 기능을 통해 하나의 리눅스 시스템 안에서 격리된 여러 개의 네트워크 스택을 가질 수 있게 한다.
- 격리 대상 : 네트워크 인터페이스(NIC), 라우팅 테이블, ARP 테이블, IPTables 규칙 등
- 오픈스택에서의 역할 : Tenant A가 10.0.0.1을 사용하고, Tenant B도 10.0.0.1을 사용하더라도 서로 충돌하지 않는 이유는 이들이 서로 다른 네임스페이스 안에 격리되어 있기 때문이다.
- 구현 : 오픈스택의 가상 라우터는 리눅스 상에서 하나의 네임스페이스로 구현된다.
- 오픈스택이 관리하는 라우터의 ID를 확인하고, 그에 매핑되는 리눅스 네임스페이스를 찾는다.
$ openstack router list
+--------------------------------------+----------------+--------+-------+----------------------------------+-------------+-------+
| ID | Name | Status | State | Project | Distributed | HA |
+--------------------------------------+----------------+--------+-------+----------------------------------+-------------+-------+
| 4a960f6b-7b36-4a4e-8876-ee8bcb8eecf4 | router1 | ACTIVE | UP | 74a6815c3ca94e32a06ef3e4bd86c9db | False | False |
| e300d7f6-d438-4037-a077-83d7a2045da9 | router_ansible | ACTIVE | UP | 74a6815c3ca94e32a06ef3e4bd86c9db | False | False |
+--------------------------------------+----------------+--------+-------+----------------------------------+-------------+-------+
$ ip netns list
qrouter-e300d7f6-d438-4037-a077-83d7a2045da9 (id: 7)
qdhcp-edac54e7-c44c-4211-902f-5a964a3863ad (id: 6)
qrouter-4a960f6b-7b36-4a4e-8876-ee8bcb8eecf4 (id: 5)
qdhcp-95096954-5810-40a1-9b3e-6870b1a09789 (id: 3)
qdhcp-68dcfc55-ed46-4462-85f9-6eee681702c8 (id: 1)
qdhcp-d83222f2-1e8a-4791-bce7-757120efbc67 (id: 2)
$ sudo ip netns exec qrouter-4a960f6b-7b36-4a4e-8876-ee8bcb8eecf4 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
259: qg-b7c22923-72: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
link/ether fa:16:3e:3a:6a:77 brd ff:ff:ff:ff:ff:ff
inet 192.168.35.209/24 brd 192.168.35.255 scope global qg-b7c22923-72
valid_lft forever preferred_lft forever
inet 192.168.35.240/32 brd 192.168.35.240 scope global qg-b7c22923-72
valid_lft forever preferred_lft forever
inet 192.168.35.224/32 brd 192.168.35.224 scope global qg-b7c22923-72
valid_lft forever preferred_lft forever
inet6 fe80::f816:3eff:fe3a:6a77/64 scope link
valid_lft forever preferred_lft forever
260: qr-a43b680b-0b: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default qlen 1000
link/ether fa:16:3e:12:c1:01 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.1/24 brd 10.0.0.255 scope global qr-a43b680b-0b
valid_lft forever preferred_lft forever
inet6 fe80::f816:3eff:fe12:c101/64 scope link
valid_lft forever preferred_lft forever
qr-xxxx (Quantum Router port)
- 연결 대상 : 내부 네트워크 (private_net, VXLAN)
- IP 주소 : 10.0.0.1 (서브넷의 게이트웨이 IP)
- 역할 : VM들이 보내는 패킷을 받는 Default Gateway 인터페이스
qg-xxxx (Quantum Gateway port)
- 연결 대상 : 외부 네트워크 (ext_net, br-ex)
- IP 주소 : 192.168.35.xxx (라우터 자체의 외부 IP)
- 역할 : 인터넷(물리 네트워크)으로 나가는 출구(Uplink). 이 인터페이스에서 NAT가 수행된다.
$ sudo ip netns exec qrouter-4a960f6b-7b36-4a4e-8876-ee8bcb8eecf4 ip route
default via 192.168.35.1 dev qg-b7c22923-72 proto static
10.0.0.0/24 dev qr-a43b680b-0b proto kernel scope link src 10.0.0.1
192.168.35.0/24 dev qg-b7c22923-72 proto kernel scope link src 192.168.35.209
- 10.0.0.0/24 대역(내부망)으로 가는 패킷은
qr- 인터페이스로 보낸다.
- 그 외 모든 패킷(default)은
qg- 인터페이스를 통해 물리 공유기 (192.168.35.1)로 던진다.
$ sudo ip netns exec qrouter-4a960f6b-7b36-4a4e-8876-ee8bcb8eecf4 iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N neutron-l3-agent-OUTPUT
-N neutron-l3-agent-POSTROUTING
-N neutron-l3-agent-PREROUTING
-N neutron-l3-agent-float-snat
-N neutron-l3-agent-snat
-N neutron-postrouting-bottom
-A PREROUTING -j neutron-l3-agent-PREROUTING
-A OUTPUT -j neutron-l3-agent-OUTPUT
-A POSTROUTING -j neutron-l3-agent-POSTROUTING
-A POSTROUTING -j neutron-postrouting-bottom
-A neutron-l3-agent-OUTPUT -d 192.168.35.224/32 -j DNAT --to-destination 10.0.0.245
-A neutron-l3-agent-OUTPUT -d 192.168.35.240/32 -j DNAT --to-destination 10.0.0.102
-A neutron-l3-agent-POSTROUTING ! -o qg-b7c22923-72 -m conntrack ! --ctstate DNAT -j ACCEPT
-A neutron-l3-agent-PREROUTING -d 169.254.169.254/32 -i qr-+ -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 9697
-A neutron-l3-agent-PREROUTING -d 192.168.35.224/32 -j DNAT --to-destination 10.0.0.245
-A neutron-l3-agent-PREROUTING -d 192.168.35.240/32 -j DNAT --to-destination 10.0.0.102
-A neutron-l3-agent-float-snat -s 10.0.0.245/32 -j SNAT --to-source 192.168.35.224 --random-fully
-A neutron-l3-agent-float-snat -s 10.0.0.102/32 -j SNAT --to-source 192.168.35.240 --random-fully
-A neutron-l3-agent-snat -j neutron-l3-agent-float-snat
-A neutron-l3-agent-snat -o qg-b7c22923-72 -j SNAT --to-source 192.168.35.209 --random-fully
-A neutron-l3-agent-snat -m mark ! --mark 0x2/0xffff -m conntrack --ctstate DNAT -j SNAT --to-source 192.168.35.209 --random-fully
-A neutron-postrouting-bottom -m comment --comment "Perform source NAT on outgoing traffic." -j neutron-l3-agent-snat
SNAT : VM -> 인터넷
- VM이 인터넷으로 나갈 때, 출발지 IP를 사설 IP에서 공인(Floating) IP로 바꾸는 과정
- 규칙 : neutron-l3-agent-FLOAT-SNAT 체인 내부
- 패턴 :
-s 10.0.0.x/32 -j SNAT --to-source 192.168.35.yyy
- 해석 : 출발지 IP가 VM IP(10.0.0.x)인 패킷이 나가려 하면, 출발지 주소를 Floating IP(192.168.35.yyy)로 덮어씌워라 (Source NAT)
DNAT : 인터넷 -> VM
- 외부에서 Floating IP로 접속할 때, 목적지 IP를 VM의 사설 IP로 바꾸는 과정
- 규칙 : neutron-l3-agent-PREROUTING 체인 내부
- 패턴 :
-d 192.168.35.yyy/32 -j DNAT --to-destination 10.0.0.x
- 해석 : 목적지 IP가 Floating IP(192.168.35.yyy)인 패킷이 들어오면, 목적지 주소를 VM의 사설 IP(10.0.0.x)로 바꿔라 (Destination NAT)
- 패킷 흐름 정리 ( VM에서 8.8.8.8로 Ping을 칠 때의 전체 경로 )
- VM (10.0.0.3)
- 목적지 8.8.8.8은 내 서브넷이 아님을 인지 -> Default Gateway 10.0.0.1로 패킷 전송
- Src: 10.0.0.3, Dst: 8.8.8.8
- OVS Integration Bridge (br-int)
- 패킷을 받아 라우터 네임스페이스와 연결된 포트로 전달
- Namespace (qrouter-xxxx)
- Input : qr-xxxx 인터페이스로 패킷 수신
- Routing : 라우팅 테이블 참조 -> default via 192.168.35.1 dev qg-xxxx 확인
- Netfilter (POSTROUTING) : IPTables NAT 규칙 매칭
- Src IP 10.0.0.3을 Floating IP 192.168.35.201(예시)로 변환 (SNAT).
- Output: qg-xxxx 인터페이스로 패킷 송신
- 이 시점의 패킷: Src 192.168.35.201, Dst 8.8.8.8
- OVS External Bridge (br-ex) & 물리 NIC
- 네임스페이스에서 나온 패킷은 br-ex를 거쳐 노트북의 실제 USB 랜카드를 타고 나감
- 물리 공유기 (192.168.35.1)
- 패킷을 받아 인터넷으로 라우팅 (여기서 또 한 번 NAT가 발생하여 공인 IP로 변환됨)
질문
- 대시보드에서 router1의 인터페이스를 확인하면 외부 게이트웨이 IP인 192.168.35.209가 있다. 그런데 route1의 네임스페이스 안에서 ip a를 쳐보면 qg-xxx에 3개의 IP가 있다.
.240, .224는 VM에 할당된 IP이다.
- 각 VM이 인터넷으로 나갈 때는 이 IP를 달고 나가게 된다.
- 그렇다면 .209 IP는 언제 사용 되는 것일까?
qg-b7c22923-72: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
link/ether fa:16:3e:3a:6a:77 brd ff:ff:ff:ff:ff:ff
inet 192.168.35.209/24 brd 192.168.35.255 scope global qg-b7c22923-72
valid_lft forever preferred_lft forever
inet 192.168.35.240/32 brd 192.168.35.240 scope global qg-b7c22923-72
valid_lft forever preferred_lft forever
inet 192.168.35.224/32 brd 192.168.35.224 scope global qg-b7c22923-72
valid_lft forever preferred_lft forever
- Floating IP가 할당된 VM (240, 244) : 라우터 인터페이스의 209 와는 상관없이, 자신의 Floating IP를 달고 나간다.
- Floating IP가 없는 VM : 라우터의 대표 IP인 209를 달고 나간다.
- 192.168.35.209 (Router Gateway IP)의 역할
- 209번 IP는 라우터 자체의 외부 인터페이스 IP이자, Floating IP가 없는 VM들을 위한 공용 출구이다.
Default SNAT
- 만약 Floating IP를 할당받지 않은 VM(예: 10.0.0.50)이 인터넷으로 나가려고 하면, 라우터는 이 패킷의 출발지 IP를 192.168.35.209로 변환(SNAT)해서 내보낸다.
- 192.168.35.240, 224 (Floating IP)의 역할
- ip a로 조회했을 때 qg-xxx 인터페이스에 이 IP들이 같이 붙어 있는 이유는, 라우터가 이 IP들에 대한 처리를 전담하기 때문이다.
- 1:1 NAT (우선순위 높음):
- VM에 Floating IP가 할당되면, 라우터 내부의 iptables 규칙에 의해 Floating IP 규칙이 Default SNAT(209)보다 우선적으로 적용된다.
- 따라서 10.0.0.102가 보낸 패킷은 209가 아니라, 자신에게 1:1로 매핑된 240으로 변환되어 나간다.
- 외부에서 240으로 들어오는 패킷도 라우터가 받아서 10.0.0.102로 넘겨준다. (DNAT)