2024년 6월 14일 금요일

Layer 2 WireGuard VPN의 해부

이번 시간에는 WireGuard kernel code를 수정하여 Layer 2 환경에서도 동작 가능한 L2 WireGuard를 만들고, NanoPi 상에서 동작하는 과정을 소개해 보고자 한다. 😎



________________________

목차
1. Layer2 VPN의 이해  
2. L2 WireGuard의 동작 원리
3. L2 WireGuard 동작 시험#1
4. L2 WireGuard 동작 시험#2
5. GRETAP + L3 WireGuard로 L2 VPN 만들기
6. 아직 못다한 이야기
7. References

_______________________________________________________________

WireGuard를 Layer 2에서 동작가능하게 만드는 것은 참으로 흥미로운 작업이 아닐 수 없다. 패킷을 내보내는 시점을 IP layer에서가 아니라 Ethernet layer로 변경하고, 수신시에도 IP packet 뿐만아니라, ARP packet 등 비 IP packet이 수신 될 수 있도록 개선할 수 있다면, 능히 Layer 2 WireGuard를 만들 수 있지 않을까 ? 😹


1. Layer2 VPN의 이해

필자는 이미 아래 blog post를 통해서 Layer 2 VPN의 대명사 격인 SoftEther VPN의 동작 방식을 상세히 소개한 바 있다.
SoftEther VPN은 매우 훌륭한 기능을 보유하고 있으며, 상업용(안정적이고 풍부한 기능 보유)으로 사용하기에도 충분한 VPN이지만, 설정이 너무 복잡하다는 한가지 단점을 가지고 있다(물론, 필자의 개인 의견임 😋).

아래 그림에서 볼 수 있듯이, Layer 2 VPN은 Layer 3 VPN으로는 할 수 없는 몇가지 일을 처리할 수 있다. 물론 이 중에는 IPX, AppleTalk, PPPoE 등 지금은 많이 사용하지 않는 프로토콜 들도 있지만, ARP, 802.3x Pause frame, 802.1q VLAN, 802.1ad QinQ, STP, RIP, LACP, DHCP, mDNS(Printer, AppleTV, Google Chromecast 등) 처럼 Layer 2 VPN을 통해서만 처리 가능한 것들도 꽤 많이 존재한다.


[그림 1.1] L3 VPN vs L2 VPN
📌 DHCP는 IP broadcast packet을 사용하고, mDNS는 IP mulicast packet을 사용하지만, layer 3 vpn으로는 처리하지 못한다.

아래 그림은 mDNS 프로토콜을 사용하는 프린터를 원격으로 액세스(집에서 사무실 printer로 직접 printing 하기)하는 예를 보여준다. Layer 3 VPN으로는 도져히 불가능한 일이지만, Layer 2 VPN의 경우라면 그렇지 않다(가능한 일이다).

[그림 1.2] L2 VPN을 사용하여 원격 Printing 하기
📌 매우 중요한 문서를 실시간으로 프린터를 통해 출력하고자 하는 경우가 있다면, 위의 경우도 의미가 있지 않을까 ? 물론 해당 문서를 암호화해서 메일로 보내고, 수신자가 그 파일을 열어서 프린팅해도 되긴 하지만 ...

오래 전에, SoftEther VPN을 가지고 제품을 만든 적이 있었는데, 그 때 그려둔 그림을 여기에 소개해 본다. 앞으로 2-4장에서 설명하게 될 L2 WireGuard를 가지고도 아래와 유사한 구성이 가능한지 따져볼 생각이다. 🔍
 

[그림 1.3] SoftEther L2 VPN의 동작 방식

📌 사실, SoftEther VPN은 user space에서 동작하는 vpn이지만, 기존의 tun interface를 사용하는 천편일률적인 방식과는 아주 다르며, 아주 멋진 vpn이다. 💯


자, 그럼 다음 장에서는 WireGuard code를 변형하여, 사용하기 쉬우면서도 Layer 2에서 동작하는 L2 WireGuard를 만들어 보도록 하자.


2. L2 WireGuard의 동작 원리

(개인적으로) WireGuard를 사용해 온지, 거의 7-8년이나 된 것 같다. 그 동안 WireGuard를 기반으로 상용 제품도 만들어 보고, protocol을 변경하는 작업도 해 보았다. 

<WireGuard 관련 이전 posting 모음>


WireGuard는 익히 잘 알고 있는 것 처럼, Layer 3 즉, IP(v4, v6) 계층에서 기본적으로 동작하도록 설계되었다. 사실 Layer 3에서 동작하는 것 만으로도 왠만한 프로토콜은 거의 다 소화할 수 있다(서비스하기에 전혀 문제가 안된다). 하지만, OpenVPN(L2 mode)이나, SoftEther VPN 처럼 Layer 2에서 동작하도록 WireGuard를 만들 수만 있다면, 이 또한 꽤나 흥분되는 일이 될 것이다. 😍

간결하고 빠른 WireGuard, 여기에 Layer 2기반으로 확장 !

이번 장에서 소개하는 내용은 WireGuard kernel code를 수정하여 Layer 2에서 동작하도록 만드는 L2 WireGuard에 관한 것으로, 아래 site의 내용을 기초로하였다. 본격적인 설명에 앞서 원작자인 Fadis(NAOMASA MATSUBAYASHI) 님에게 감사의 마음을 전한다.


📌 문서가 일본어로 되어 있어, 이해하는데 애를 좀 먹었다. 그래도 열심히 들여다 보면 뭔 말인지 알 수가 있다. 😋

a) L2 WireGuard의 기본 설계 방향

WireGuard가 layer 2에서 동작하기 위해서는, 크게 아래 3가지 사항이 먼저 고려되어야 한다.

  • (1) 전체 ethernet frame을 통째로 터널링하기 위해서, 패킷을 내보내는 시점을 IP layer가 아니라 Ethernet layer로 어떻게 변경할 것인가 ?
    • wireguard interface(예: wg0)를 만들 때 ethernet device 형태로 만들어 주면 되지 않을까 ?
    • 비유를 하자면, tun device로 만들 것을 tap device로 만드는 것으로 이해하면 될 듯 하다.
  • (2) Ethernet 패킷 통신에서는 IP packet 말고도, ARP 등 비 IP packet이 존재한다. 따라서 패킷 수신 시에도 IP packet 뿐만아니라, ARP packet 등 비 IP packet이 수신 될 수 있도록 하기 위해서는 어찌해야 할 것인가 ?
    • 수신한 패킷에 대해 복호 & decapsulation 처리 후, 비 IP packet을 통과하도록 수정하면 되지 않을까 ?
    • 비 IP Protocol에는 어떤 것들이 있을까 ? ARP, IPX, Appletalk, NETBEUI, VLAN, LACP ...
  • (3) WireGuard는 기본적으로 src/dst IP를 기반으로 table lookup(allowed_ips)을 한다. 이는 IP 패킷의 경우는 문제가 안되나, 비 IP 패킷은 당연히 문제가 된다. 또한, IP 패킷일지라도 multicast, broadcast packet의 경우는 처리 시 문제가 된다. 이 부분은 Layer 3 wireguard 설계 사상과 직접 연관된 것으로, Layer 2 방식으로 전환할 경우, 수정이 불가피하다 하겠다.

b) L2 wireguard interface 생성과 패킷 송신부 변경
먼저, 전체 ethernet frame을 통째로 터널링하기 위해서는 l2 interface를 생성하는 작업이 필요하다. 즉, ip link 명령을 사용하여 l2 wireguard 전용 interface를 생성하는 코드 작업이 필요하다는 뜻이다. 이를 위해서는 아래와 같이 새로운 rtnl_link_ops 구조체를 선언해 주고, 초기화 함수를 통해서 등록해 주면 된다.


______________________________________________________
[코드 2.1] l2용 rtnl_link ops 선언
📌 참고로, 이 절에서 소개하는 내용은 대부분 device.c 파일 안에 있다.


______________________________________________________
[코드 2.2] wg_device_init() 함수 - l2link_ops 등록 

이렇게 하고 나면, ip link add dev wg0 type l2wireguard 와 같은 명령을 사용하여 wireguard l2 interface를 생성할 수 있게 된다. 
참고로, l2link_ops의 .setup field에 해당하는 l2wg_setup( ) 함수에서 ether_setup(dev) 함수를 호출해 주어야만, 해당 interface가 ethernet device로 인식되게 된다.

______________________________________________________
[코드 2.3] l2wg_setup function 정의
📌 ethernet device driver 구현과 관련해서는 drivers/net/tun.c 파일(tun/tap driver)을 참조하는 것도 도움이 된다.

다음으로 l2 전용 netdevice를 선언하고, 앞서 기술한 l2wg_setup( ) 함수를 통해 아래와 같이 초기화(netdev_ops 등록)를 진행한다.

dev->netdev_ops = &l2netdev_ops;

______________________________________________________
[코드 2.4] l2용 netdevice ops 선언
📌 l2 wireguard는 l3 wireguard netdevice와는 다르게, 2개의 필드 즉 .ndo_set_mac_address와 .ndo_validate_addr가 추가되었음을 주목해야 한다.

netdevice를 등록하게 되면, wg_xmit( ) 함수를 통해 패킷(ethernet frame)을 전송할 수 있는 상태가 된다. 문제는 기존의 wg_xmit( ) 함수는 IP 패킷만을 전송하도록 설게되어 있기 때문에, 비 IP 패킷(예: ARP packet)이 정상적으로 peer에게 전달되도록 하기 위해 이 함수를 적절히 가공해 줄 필요가 있다. 

제일 먼저 수정이 필요한 부분은 아래 함수로, ARP 패킷 등이 통과할 수 있도록 아래와 같이 수정하도록 한다.

______________________________________________________
[코드 2.5] queueing.h 파일 내의 wg_check_packet_protocol() 함수 수정

WireGuard는 allowed_ips 설정을 통해 IPv4, IPv6 패킷의 특정 대역만을 허용하는 것을 기본 원칙으로 하고 있다. 따라서 기존의 allowed_ips lookup 코드를 그대로 사용하는 것으로는 ARP 등 비 IP 패킷이 tunneling의 대상에서 제외되는 것을 막을 수가 없다. 따라서 이 함수 역시 적절히 수정(모든 packet이 허용되는 형태 즉, allowed-ips 0.0.0.0/0)되어야만 한다.

______________________________________________________
[코드 2.6] allowedips.c 파일 내의 wg_allowedips_lookup_src/dst 함수 (수정 전)

______________________________________________________
[코드 2.7] l2wg용 wg_allowedips_lookup_src/dst 함수 (수정 후)

따라서 이제 부터 L2 WireGuard를 사용하기 위해서는 반드시 "wg set ... allowed-ips 0.0.0.0/0"과 같이 설정해 주어야만 한다. 이는 l2 wireguard로 전환하기 위해서는 사실상 더 이상은 allowed-ips의 개념을 사용할 수 없게 된다는 뜻이기도 하다.
📌 어찌보면, 기본적인 wireguard code의 틀을 유지하면서, l2 wireguard를 구현하였기 때문에 약간은 기본 concept이 흔들리는 면도 없지 않아 있는 것 같다.

c) 패킷 수신부 변경

다음으로 알아볼 사항은 패킷 수신부에 관한 것이다.  패킷 수신부는 대부분 receive.c 파일에 있는데, 이 파일의 내용 중 wg_packet_rx_poll( ) 함수를 중심으로 살펴볼 필요가 있다. 참고로, WireGuard 패킷 수신 처리 흐름과 관련해서는 이전 blog post 4장의 내용을 참조해 주기 바란다.

https://slowbootkernelhacks.blogspot.com/2020/09/wireguard-vpn.html

암호화된 ip tunnel 패킷 수신(wg_receive) -> tunnel 제거 및 복호화(decrypt_packet) -> 상위로 전달(wg_packet_rx_poll)

wg_packet_rx_poll( ) 함수는 이미 decryption(decapsulation)이 진행된 packet을 queue에서 꺼내어 적절히 처리한 후, napi_schedule 함수를 통해 tcp/ip stack으로 올려 보내는 역할을 한다. 역시 이 과정에서도 기존 코드가 IP packet 만을 대상으로 구현되어 있기 때문에, ARP 등 비 IP packet은 처리 대상에서 제외되고 만다. 따라서, 이를 적절히 수정해 줄 필요가 있는데, 수정이 필요한 함수는 wg_packet_consume_data_done()이다.


______________________________________________________
[코드 2.8] 패킷 수신부 함수

wg_packet_consume_data_done( ) 함수에서 수정해야할 부분은 크게 2가지이다.

  • IPv4, IPv6 packet에 대해 filtering하는 부분. 즉, IPv4, IPv6 packet만 걸러내는 부분
  • source ip 주소를 이용해 allowedips lookup을 시도하는 부분

패킷 수신 시 allowedips lookup(출발지 주소를 대상으로 lookup)을 시도하는 부분과 관련해서는 송신 처리(목적지 주소를 대상으로 lookup)에서 이미 설명(동일한 개념)했으므로, 여기에서는 비 IP packet이 filtering 되지 않도록 하는 코드 구현에 집중하도록 한다. 이를 위해 L2 WireGuard에서는 아래와 같이 packet의 length를 구하는 함수를 추가로 구현하고, 이 함수를 이용하여 비 IP 패킷이 filtering되지 않도록 코드 수정을 진행하였다.

______________________________________________________
[코드 2.9] l2wg_get_packet_length( ) 함수
📌 본 blog post에서는 ARP만을 주로 언급하였으나, L2 VPN의 처리 대상으로, IPX, Appletalk, 802.1q, QinQ, 802.1ah, pause frame, PPPoE 등 많은 부분이 고려되어야 한다.

아래 코드는, wg_packet_consume_data_done() 함수 내에서 l2wg_get_packet_length( ) 함수를 적용한 부분 중 일부를 발췌한 것이다.

                                    ...

                                    ...

______________________________________________________
[코드 2.10] wg_packet_consume_data_done() 함수 내용 중  l2wg_get_packet_length( ) 함수 적용 부분

최대한 L2 WireGuard의 설계 원칙/배경을 나름의 방식으로 표현하고자 하였지만, 그래도 
어딘가 좀 어색한 구석이 보인다. 😂 부족한 부분은 Fadis님이 작성한 pdf 문서를 함께 읽어 봐 주시기 바란다.

--------------------------------------------
이상으로 L2 WireGuard의 기본 설계 idea와 l2 wireguard interface 생성, 패킷 송신부 및 패킷 수신부 수정 내용과 관련하여 자세히 알아 보았다. 사실 말이 쉽지, 실제로 l3 -> l2 형태로 전환하는 작업은 오랜 경험과 시행착오를 통해서만 얻을 수 있는 일이다. 😔

다음 장에서는 이렇게 구현한 L2 WireGuard가 정말 문제 없이 동작하는 지를 2대의 NanoPi를 통해 확인하고자 한다.


3. L2 WireGuard의 동작 시험#1

이 장에서는 NanoPi R2S Plus 1대와 NanoPi R5C 1대를 활용하여, L2 WireGuard가 정상 동작하는지를 확인해 보고자 한다.


[그림 3.1] L2 WireGuard 시험 환경 (왼쪽: NanoPi R5C, 오른쪽 NanoPi R2S Plus)

📌 가운데 있는 제품은 개인적으로 좋아하는 Gl.iNet사의 MT1300이다.


<1차 시험 환경>
NanoPi R5C WAN(eth0) <==== Gl.iNet MT1300 (LAN1, LAN2) ====> NanoPi R2S Plus WAN(eth0)

NanoPi와 관련해서는 이전 blog post(3장)를 통해 한 차례 소개한 바 있다. 따라서 자세한 개발 환경과 관련해서는 아래 내용을 참고하기 바란다.
📌 NanoPi R2S Plus와 R5C의 개발 환경은 크게 다르지 않다.

a) L2 WireGuard build 하기

지금까지 수정한 내용을 토대로 새로 kernel build를 진행하여, wireguard.ko 파일을 생성하도록 하자.

$ cd friendlywrt23-rk3328

$ cd kernel/drivers/net/wireguard

$ grep -rl L2_WIREGUARD
 => Fadis 님의 코드를 토대로, 아래의 파일을 수정하였으며, 코드를 구분하기 쉽도록 L2_WIREGUARD feature 처리를 하였다.
Makefile
allowedips.c
allowedips.h
device.c
device.h
netlink.c
queueing.h
receive.c
📌 주의) NanoPi에서 사용 중인 kernel 6.1.63에서는 IPX 관련 코드가 보이질 않아, l2 wireguard code 중, IPX 관련 코드는 편의상 제거하였다. 아무래도 너무 오래된 protocol이라 이제는 더이상 사용하지 않는다고 판단한 모양이다.


$ cd friendlywrt23-rk3328

./build.sh kernel
NanoPi의 경우는 이와 같이 kernel build를 진행한다.

...

  CC [M]  /mnt/hdd/workspace/mini_devices/friendlywrt23-rk3328/scripts/sd-fuse/out/rtl8812au/hal/phydm/rtl8821a/phy
dm_rtl8821a.o
 CC [M]  /mnt/hdd/workspace/mini_devices/friendlywrt23-rk3328/scripts/sd-fuse/out/rtl8812au/hal/phydm/halrf/rtl882
1a/halrf_iqk_8821a_ce.o
 CC [M]  /mnt/hdd/workspace/mini_devices/friendlywrt23-rk3328/scripts/sd-fuse/out/rtl8812au/platform/platform_ops.
o
 LD [M]  /mnt/hdd/workspace/mini_devices/friendlywrt23-rk3328/scripts/sd-fuse/out/rtl8812au/rtl8812au.o
 MODPOST /mnt/hdd/workspace/mini_devices/friendlywrt23-rk3328/scripts/sd-fuse/out/rtl8812au/Module.symvers
 CC [M]  /mnt/hdd/workspace/mini_devices/friendlywrt23-rk3328/scripts/sd-fuse/out/rtl8812au/rtl8812au.mod.o
 LD [M]  /mnt/hdd/workspace/mini_devices/friendlywrt23-rk3328/scripts/sd-fuse/out/rtl8812au/rtl8812au.ko
make[1]: 디렉터리 '/mnt/hdd/workspace/mini_devices/friendlywrt23-rk3328/kernel' 나감
'rtl8812au.ko' -> '/mnt/hdd/workspace/mini_devices/friendlywrt23-rk3328/scripts/sd-fuse/out/output_rk3328_kmodules/
lib/modules/6.1.63/rtl8812au.ko'
depmod /mnt/hdd/workspace/mini_devices/friendlywrt23-rk3328/scripts/sd-fuse/out/output_rk3328_kmodules 6.1.63 ...
building kernel ok.
====Building kernel ok!====


$ cd kernel/drivers/net/wireguard

$ ls -l wireguard.ko  
-rw-rw-r-- 1 chyi chyi 863608  6월 12 14:55 wireguard.ko

$ scp ./wireguard.ko root@192.168.2.1:~/workspace/l2_wireguard
 => targe board로 복사한다.
root@192.168.2.1's password:  
wireguard.ko                                                                     100%  843KB  14.4MB/s   00:00


b) NanoPi에서 돌려 보기(1차 시험)

먼저 NanoPi R2S Plus에 로긴하여 wireguard 설정 작업을 진행하도록 하자.

$ ssh root@192.168.2.1

root@192.168.2.1's password:  
___    _             _ _    __      __   _
| __| _(_)___ _ _  __| | |_  \ \    / / _| |_
| _| '_| / -_) ' \/ _` | | || \ \/\/ / '_|  _|
|_||_| |_\___|_||_\__,_|_|\_, |\_/\_/|_|  \__|
                         |__/
-----------------------------------------------------
FriendlyWrt 23.05.3, r23809-234f1a2efa
-----------------------------------------------------

📌 NanoPi R2S Plus는 console port(/dev/ttyUSB0, 1500000, 8N1)가 외부에 나와 있으므로, 이를 이용해서 로긴해도 된다.


$ cd ~/workspace/l2_wireguard

root@nanobox-r2s-plus:~/workspace/l2_wireguard# uname -a
Linux nanobox-r2s-plus 6.1.63 #1 SMP Sun Apr 14 15:07:49 KST 2024 aarch64 GNU/Linux
 
root@nanobox-r2s-plus:~/workspace/l2_wireguard# cp wireguard.ko  /lib/modules/6.1.63/wireguard.ko  
  => 현재 동작 중인 wireguard kernel module을 l2 wireguard로 교체한다.

root@nanobox-r2s-plus:~/workspace/l2_wireguard# sync
root@nanobox-r2s-plus:~/workspace/l2_wireguard# reboot

재 부팅 후, 다시 login하여 아래와 같이 l2 wireguard  설정을 하도록 한다.


# wg genkey | tee ./privatekey | wg pubkey > ./publickey
  => curve25519 keypair를 생성한다.

# ip link add dev wg1 type l2wireguard
=> l2wireguard를 지정해 주어야 ethernet frame을 통째로 tunneling할 수 있다.

# ip address add dev wg1 192.168.157.1/24
  => wg1 interface에 ip를 지정한다. l3 wireguard의 경우는 wgX 용 ip는 vpn ip를 의미하지만, l2 vpn에서는 이를 지정할 필요가 없다. 여기서 ip를 지정한 이유는 ping test를 하기 위해서이다.

# ip link set dev wg1 address 56:97:4A:00:00:01
  => l2 wireguard의 경우는 ethernet device 형태이기 때문에 MAC 주소를 반드시 지정해 주어야 한다.

# ip link set wg1 up
  => l2 wireguard interface를 up시킨다.

# wg set wg1 listen-port 51820 private-key ./keys/privatekey peer                           
FppVj8pZvJupnRI9admT8LPiXcwZ4eILHNhfUyU+XWk= allowed-ips 0.0.0.0/0 endpoint 192.168.8.125:51820

  => l2 wireguard peer 설정을 한다.

📌 주의 1) 당연한 거지만, wireguard interface는 wg0 부터 사용 가능하며, 여기서는 (원저자를 오마주하는 뜻에서) wg1을 사용하였다.
📌 주의 2) 앞서 언급한 대로, allowed-ips를 0.0.0.0/0으로 지정해야만 정상 동작한다.
📌 주의 3) 다시 말하지만, l2 wireguard는 반드시 MAC 주소를 설정해 주어야 한다.

다음으로, 동일한 설정 과정을 NanoPi R5C에서도 진행하도록 하자. 여기서는 l2 wireguard 설정 부분만 정리해 보기로 한다.



자, 여기까지 기본 설정이 모두 끝났으므로, 동작을 확인해 보도록 하자. 예상대로, peer vpn ip로의 ping이 정상 동작한다. 😃

[그림 3.2] L2 WireGuard 정상 동작 확인 1
📌 l2 wireguard는 MTU 값이 1420 - 14(ethernet header size) = 1406으로 설정되어 있는 것을 알 수 있다. 이 부분은 device.c code에서도 확인 가능하다.

이 상태에서 tcpdump 결과를 확인해 보아도 l2 wireguard가 정상 동작함을 알 수 있다.

[그림 3.3] L2 WireGuard 정상 동작 확인 2 - wireguard packet capture
📌 l3 wireguard로 ping test를 해 보면, 동일한 조건에서 length가 128이 나온다. 144-128 = 16인데, ethernet header size(14) + 2 만큼 늘어났다.

wireguard interface를 상대로 tcpdump를 하면 raw packet이 보인다(이것도 정상).


[그림 3.4] L2 WireGuard 정상 동작 확인 3 - raw packet capture

이상으로 2대의 NanoPi에 l2 wireguard kernel module을 올리고, NanoPi 간에 1:1 통신시 l2 wireguard가 적용되는 것을 확인해 보았다.


4. L2 WireGuard의 동작 시험#2

자, 그런데, 앞서의 시험으로는 뭔가 2% 부족하다. l3 wireguard와 비교해 뭐가 달라졌는지를 구분하기도 어렵다. 지금 부터는 1장에서 언급한 SoftEther VPN을 시험했던 환경과 동일한 환경을 구축하고, 이 상태에서 l2 wireguard가 정상 동작하는지를 확인해 보고자 한다.



[그림 4.1] L2 WireGuard 터널 패킷

L2 VPN을 사용하게 되면, 위의 그림처럼 ethernet frame이 통째로 터널링되어 peer로 전달되기 때문에, 패킷을 보내는 측의 machine(예: 아래 그림 좌측의 Notebook)이 수신측의 machine(예: 아래 그림 우측의 서버)과 동일한 network에 있는 효과를 누릴 수 있게 된다. 동일한 network에서 통신하려면 ip 대역이 같아야 하는 것은 두말하면 잔소리일 것이다. 따라서 아래 시험 내용을 보면, 터널 연결 후, 송신 측의 ip를 NanoPi R5C 내부망 대역(192.168.3.0/24)으로 변경(수동 혹은 DHCP)하는 부분이 있는데, 이와 같은 이유 때문이다.
📌 이부분이 개념적으로 L3 VPN의 경우와 크게 차이가 나는 부분이다.


<2차 시험 환경>

실제로 인터넷 환경에서 시험해야 하나, 아래와 같은 테스트 환경에서 먼저 동작 시험을 진행해 보도록 하자.

[그림 4.2] L2 WireGuard를 이용한 2차 시험 구성도

위의 구성이 가능하다는 것은, L2 VPN을 사용할 경우, 아래 그림과 같이 원격 DHCP도 가능하다는 뜻이 된다.

[그림 4.3] L2 WireGuard를 이용한 원격 DHCP(DNS 설정 포함)


먼저 NanoPi R2S Plus 설정을 진행하도록 한다.

[그림 4.4] NanoPi R2S Plus network 구성

<NanoPi R2S Plus>
# wg genkey | tee ./privatekey | wg pubkey > ./publickey
  => curve25519 keypair를 생성한다.

# ip link add dev wg1 type l2wireguard
=> l2wireguard를 지정해 주어야 ethernet frame을 통째로 tunneling할 수 있다.

# wg set wg1 listen-port 51820 private-key ./keys/privatekey peer                           
FppVj8pZvJupnRI9admT8LPiXcwZ4eILHNhfUyU+XWk= allowed-ips 0.0.0.0/0 endpoint 192.168.8.125:51820
 => device 및 peer 설정을 한다. allowed-ips는 반드시 0.0.0.0/0으로 지정한다.

# ip link set dev wg1 address 02:ca:fe:f0:0e:04
  => wg1 interface에 대해 MAC address를  설정한다. 회사마다 고유의 MAC 주소 대역이 있으므로, 이를 활용하도록 하자.

# ip link set wg1 up
 => wg1 interface를 link up 시킨다.

# ifconfig br-lan down
# brctl delbr br-lan
  => 기존에 설정되어 있는 br-lan bridge interface를 내린다.

# ifconfig eth1 0.0.0.0
# ifconfig wg1 0.0.0.0
  => bridge에 포함시킬 interface의 ip 주소를 없앤다. openwrt의 경우 eth1은 LAN interface임.

# brctl addbr br-lan
# brctl addif br-lan eth1
# brctl addif br-lan wg1
  => br-lan interface를 새로 생성하고, eth1과 wg1 interface를 bridge에 포함시킨다.

# ip link set dev br-lan address 56:97:4A:F7:7B:2E
  => br-lan interface의 address를 설정한다. 보통은 eth1의 MAC 주소가 그대로 사용된다.

# ip address add dev br-lan 192.168.157.1/24
  => 이 설정은 실제로 불필요하여, 제거한다.

# ip link set br-lan up
  => bridge interface를 up 시킨다.

_________________________________________________________________________
#!/bin/sh

#wg genkey | tee ./privatekey | wg pubkey > ./publickey

ip link add dev wg1 type l2wireguard
wg set wg1 listen-port 51820 private-key ./keys/privatekey peer FppVj8pZvJupnRI9admT8LPiXcwZ4eILHNhfUyU+XWk=    
allowed-ips 0.0.0.0/0 endpoint 192.168.8.125:51820

ip link set dev wg1 address 02:ca:fe:f0:0e:04
ip link set wg1 up

ifconfig br-lan down
brctl delbr br-lan

ifconfig eth1 0.0.0.0
ifconfig wg1 0.0.0.0

brctl addbr br-lan
brctl addif br-lan eth1
brctl addif br-lan wg1
brctl setfd br-lan 1
brctl stp br-lan 1
ip link set dev br-lan address 56:97:4A:F7:7B:2E
ip link set br-lan up
_________________________________________________________________________


[그림 4.5] NanoPi R2S Plus Console 상에서 설정한 모습

다음으로, NanoPi R5C 쪽 설정을 진행하도록 한다.

[그림 4.6] NanoPi R5C network 구성

<NanoPi R5C>
# wg genkey | tee ./privatekey | wg pubkey > ./publickey
  => curve25519 keypair를 생성한다.

# ip link add dev wg1 type l2wireguard
=> l2wireguard를 지정해 주어야 ethernet frame을 통째로 tunneling할 수 있다.

# wg set wg1 listen-port 51820 private-key ./keys/privatekey peer jkOW74X1CRS8fobzmYYPGDkyVhR5rdEa9uh8QA2O/0k= allowed-ips 0.0.0.0/0 endpoint 192.168.8.182:51820
 => device 및 peer 설정을 한다. allowed-ips는 반드시 0.0.0.0/0으로 지정한다.

# ip link set dev wg1 address 02:ca:fe:f0:0e:05
  => wg1 interface에 대해 MAC address를  설정한다. 회사마다 고유의 MAC 주소 대역이 있으므로, 이를 활용하도록 하자.

# ip link set wg1 up
 => wg1 interface를 link up 시킨다.

# ifconfig br-lan down
# brctl delbr br-lan
  => 기존에 설정되어 있는 br-lan bridge interface를 내린다.

# ifconfig eth1 0.0.0.0
# ifconfig wg1 0.0.0.0
  => bridge에 포함시킬 interface의 ip 주소를 없앤다. openwrt의 경우 eth1은 LAN interface임.

# brctl addbr br-lan
# brctl addif br-lan eth1
# brctl addif br-lan wg1
  => br-lan interface를 새로 생성하고, eth1과 wg1 interface를 bridge에 포함시킨다.

# ip link set dev br-lan address 1E:45:77:76:DE:26
  => br-lan interface의 address를 설정한다. 보통은 eth1의 MAC 주소가 그대로 사용된다.

# ip address add dev br-lan 192.168.3.1/24
  => 이렇게 설정해 주어야, 원격 dhcp가 정상 동작하게 된다(기존 설정을 유지).

# ip link set br-lan up
  => bridge interface를 up 시킨다.

__________________________________________________________
#!/bin/sh

#wg genkey | tee ./privatekey | wg pubkey > ./publickey

ip link add dev wg1 type l2wireguard


wg set wg1 listen-port 51820 private-key ./keys/privatekey peer jkOW74X1CRS8fobzmYYPGDkyVhR5rdEa9uh8QA2O/0k= allowed-ips 0.0.0.0/0 endpoint 192.168.8.182:51820

ip link set dev wg1 address 02:ca:fe:f0:0e:05
ip link set wg1 up

ifconfig br-lan down
brctl delbr br-lan

ifconfig eth1 0.0.0.0
ifconfig wg1 0.0.0.0

brctl addbr br-lan
brctl addif br-lan eth1
brctl addif br-lan wg1
brctl setfd br-lan 1
brctl stp br-lan 1
ip link set dev br-lan address 1E:45:77:76:DE:26

ip address add dev br-lan 192.168.3.1/24
ip link set br-lan up
__________________________________________________________

<Linux Notebook>
L2 wireguard 터널이 생성된 상태에서, (좌측의) Linux notebook의 ip 설정을 아래와 같이 수동 설정 변경하자.
(기존) 192.168.2.200/24 => (수정) 192.168.3.200/24

혹은 그림 4.3의 경우처럼, DHCP 설정도 가능하다.

[그림 4.7] Linux notebook 상에서 DHCP 설정하기
📌 DHCP 설정을 하게 되면, dhcp 패킷(broadcast packet 포함)이 통째로 암호화된 후, NanoPi R5C의 LAN 까지 전달되고, 복호화 및 decapsulation 과정을 거쳐 dnsmasq server로 전달되어 최종 DHCP 응답을 받게 된다.

[그림 4.8] DHCP Protocol의 동작 원리 [그림 출처 - 참고문헌 10]


이후, server 192.168.3.139로 ping을 시도한다. OK, ping이 된다(l2 wireguard tunneling이 된다는 뜻).
[그림 4.9] Linux notebook => Server 간의 ping 모습

반대로 server -> Notebook으로도 시도해 보자. 역시 ping이 된다. 이는 마치 2대의 machine이 같은 network 192.168.3.0/24에 있는 것 처럼 느껴진다.

[그림 4.10] Linux notebook <= Server 간의 ping 모습

tcpdump 결과를 봐도 l2 wireguard가 정상 동작하고 있는 것을 알 수 있다.

[그림 4.11] Linux notebook => Server 간의 tcpdump 모습(R2S Plus 상에서 capture)

아래와 같이 tcpdump 내용을 pcap 파일로  떨구워, wireshark으로 확인해 보면, wireguard packet인지를 좀 더 쉽게 확인할 수 있다.

# tcpdump -i eth0 -w lgwg.pcap

[그림 4.12] Linux notebook => Server 간의 wireshark 모습

끝으로, wireguard packet transfer 상태도 확인 결과, 모두 정상이다.

# wg show wg1

[그림 4.13] wg show wg1 실행 모습(R2S Plus)

br-lan은 bridge interface이고, l2 wireguard(wg1)가 여기에 attach되어 있으므로, ping이 된다는 얘기는 ethernet frame이 제대로 tunneling된 다는 것을 암묵적으로 말해준다.

아, 그런데 여기까지 시험으로 모든 것이 완벽하게 동작하는 것으로 생각했는데, 한가지 커다라 문제가 남았다. 즉, 인터넷으로 향하는 ping이나, dns 등은 정상 동작하는데, web, ssh, ftp 등이 잘 안된다. 왜 그럴까 ?
문제 원인을 파악하던 중, linux notebook에서 web 연결을 시도해 둔 상태에서, NanoPi R5C 쪽에서 tcpdump를 해 보니, 아래와 같이 ARP request에 대한 응답이 오지 않는 문제가 보인다.

<NanoPi R5C>

# tcpdump -i eth0


21:45:56.547525 ARP, Request who-has 112.175.235.196 tell 192.168.8.182, length 46

__________________________________________________________________________

내용을 보아하니, 112.175.235.196(웹 서버)의 MAC주소가 어떻게 되는지를 192.168.8.182 즉 NanoPi R2S Plus(wan ip)에게 알려 달라는 것이다. L2 VPN  상황이 아니라면 Gl.iNet MT1300이 곧 바로 ARP reply를 해 주었을 것이다.

(No L2 VPN 상황) Linux Notebook -> NanoPi R2S Plus(wan: 192.168.8.182) -> Gl-iNet MT1300

하지만, L2 VPN 상황에서는 아래 그림과 같이 ARP Reply가 l2 wireguard를 타고 NanoPi R2S Plus에 가는게 아니라, 직접 응답이 가버리게 된다(B - ARP Reply). 따라서, 이러한 경우에는 중간에 있는 NanoPi R5C에서 arp proxy 역할(대신 자신의 MAC 주소로 arp reply를 해 줌)을 해 주게 되면 문제가 해결될 수 있다(C - ARP Reply).

[그림 4.14] Proxy ARP 설정이 필요한 상황
📌 ARP packet은 같은 broadcast domain을 벋어나지 못한다.

echo 1 > /proc/sys/net/ipv4/conf/br-lan/proxy_arp
  => 이걸 해 주어야, R2S Plus에서 요청한 arp request에 대한 arp reply를 받을 수 있다.

OK, 이 상태에서 web 연결을 다시 시도해 보니, 정상 동작한다. Cool~ 🍺


[그림 4.15] L2 WireGuard를 이용한 인터넷 연결
📌 그림에도 표현된 것 처럼, 왼쪽에 있던 Notebook이 오른쪽으로 위치를 변경하여, 마치 Server와 같은 network에 있는 것 처럼 동작한다.

_______________________________________________________________

참고로, L2 WireGuard를 이용할 경우, 아래와 같은 기본 network 구성이 가능하다. 마치 아주 긴 LAN cable을 집에서 사무실까지 연결한 것과 같은 효과를 누릴 수 있다.

[그림 4.16] L2 WireGuard의 네트워크 구성 예1 - Gateway 구성


L2 WireGuard를 사용한다면 (아래 그림 우측)사무실 망 구성을 변경하지 않은 상태에서 동작(standalone server)하는 VPN 구성을 만들 수도 있다.

[그림 4.17] L2 WireGuard VPN의 네트워크 구성 예2 - Standalone 서버 구성

📌 이전 post에서 wiretap을 소개한 바 있는데, l2 wireguard를 사용할 경우, wiretap이 하는 것과 동일한 서비스가 가능할 수 있다.

이상으로, Fadis(NAOMASA MATSUBAYASHI) 님이 구현한 L2 WireGuard의 source code를 분석해 보고, NanoPi 환경에서 실제 동작하는 과정을 소개해 보았다.  L2 WireGuard 나름 훌륭하다~ 😍


5. GRETAP + L3 WireGuard로 L2 VPN 만들기

    이 장에서는 GRE Tunnel과 WireGuard를 조합하여 L2 VPN을 만드는 방법을 잠시 소개해 보고자 한다. 이전 장에서 설명했던 L2 WireGuard와의 차이점을 비교해 보는 것도 재밌을 것 같다. 👌

    • GRETAP over L3 WireGuard 방식을 사용하여 Layer 2 VPN 구현하기
    • 아래 site 참조

    https://gist.github.com/zOrg1331/a2a7ffb3cfe3b3b821d45d6af00cb8f6

    https://notes.superlogical.ch/pages/note_wg/nolayer2/


    [그림 5.1] GRE header [그림 출처 - 참고문헌 11]

    📌 GRE header는 일반적으로 4 byte이며, optional field가 추가될 수 있다.


    [그림 5.2] GRE를 이용한 터널링 구성 [그림 출처 - 참고문헌 11]


    배경 설명은 이미 앞장에서 충분히 하였기 때문에, 여기에서는 설정 방법과 시험 결과만을 언급해 보기로 하겠다.

    (당연한 얘기지만) 시험에 앞서 wireguard.ko는 original version(l3 wireguard)으로 교체해 준 상태에서 테스트를 진행해야 한다.


    <시험 환경>

    [그림 5.3] GRETAP + WireGuard 동작 원리(1)

    [그림 5.4] GRETAP + WireGuard 동작 원리(2)

    먼저 NanoPi R2S Plus 설정(Client 쪽)을 진행하도록 한다.

    <NanoPi R2S Plus>

    # wg genkey | tee ./privatekey | wg pubkey > ./publickey

    # ip link add dev wg0 type wireguard
    # ip address add dev wg0 10.1.1.1/24
    # ip link set wg0 mtu 1402 up

    # wg set wg0 listen-port 51820 private-key ./keys/privatekey peer FppVj8pZvJupnRI9admT8LPiXcwZ4eILHNhfUyU+XWk= allowed-ips 0.0.0.0/0 endpoint 192.168.8.125:51820

    # ip link add name gretap1 type gretap local 10.1.1.1 remote 10.1.1.2
    # ip link set gretap1 up

    # ifconfig br-lan down
    # brctl delbr br-lan
    # ifconfig eth1 0.0.0.0

    # brctl addbr br-lan
    # brctl addif br-lan eth1
    # brctl addif br-lan gretap1

    # brctl setfd br-lan 1
    # brctl stp br-lan 1
    # ip link set br-lan up


    다음으로, NanoPi R5C 쪽(서버 쪽) 설정을 진행하도록 한다.

    <NanoPi R5C>

    # wg genkey | tee ./privatekey | wg pubkey > ./publickey
    # ip link add dev wg0 type wireguard
    # ip address add dev wg0 10.1.1.2/24
    # ip link set wg0 mtu 1402 up
    # wg set wg0 listen-port 51820 private-key ./keys/privatekey peer                                        jkOW74X1CRS8fobzmYYPGDkyVhR5rdEa9uh8QA2O/0k= allowed-ips 0.0.0.0/0 endpoint 192.168.8.182:51820

    # ip link add name gretap1 type gretap local 10.1.1.2 remote 10.1.1.1
    # ip link set gretap1 up

    # ifconfig br-lan down
    # brctl delbr br-lan
    # ifconfig eth1 0.0.0.0

    # brctl addbr br-lan
    # brctl addif br-lan eth1
    # brctl addif br-lan gretap1
    # brctl setfd br-lan 1
    # brctl stp br-lan 1

    # ip address add dev br-lan 192.168.3.1/24
    # ip link set br-lan up

    # echo 1 > /proc/sys/net/ipv4/conf/br-lan/proxy_arp

    📌 L2 WireGuard를 설명하면서도 특별히 언급하지는 않았으나, L2 WireGuard는 물론이고 GRETAP + WireGuard의 경우 모두, 필요 시 TCPMSS 설정을 해야 할 수도 있다.

    iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

    <Linux Notebook>
    (Client 쪽) Linux notebook의 ip 설정을 DHCP로 변경한다.

    [그림 5.5] Linux Notebook에서의 routing table 확인


    이 상태에서 default gateway 즉 192.168.3.1로 ping을 시도한다. OK, ping이 된다.
    Web browser로 인터넷 연결을 시도해 본다. OK, 정상 연결된다. Cool~ ☕

    <NanoPi R2S Plus>
    # tcpdump -i eth0

    [그림 5.6] NanoPi R2S Plus에서 tcpdump 하기

    어라, 생각보다 괜찮다. 속도도 그렇게 느리지 않은 것 것 같고 ... Hmm, This method is quite useful, too. 😁


    6. 아직 못다한 이야기

    1) 같은 LAN에 위치하고 있는 Client와 Server간에 L2 WireGuard 적용하기

    • 같은 LAN에 존재하는 두 machine 간에 IP 설정 변경 없이, 암호 통신하고 싶은 상황
    • Loop 문제를 어떻게 해결할까 ? 이게 가능할까 ?

    [그림 6.1] 같은 LAN 상의 두 machine간의 L2 WireGuard


    2) wireguard-go code에 L2 WireGuard에 적용하기

    • userspace용 wireguard-go code에 l2 wireguard를 적용해 보자.


    3) L2 WireGuard에 CRYSTALS Kyber768 KEM 적용하기

    • 이전 blog post에서 잠시 소개했던 내용 처럼, Kyber KEM을 L2 WireGuard에 적용해 보도록 하자.

    https://slowbootkernelhacks.blogspot.com/2023/02/nanopi-r4s-pq-wireguard-vpn-router-part.html


    4) L2 WireGuard와 VPP 연결하기

    • tap interface를 이용해 VPP와 L2 WireGuard를 연결해 보자.
    • 가능하지 않을까 ?

    To be continued...

    _______________________________________________________

    WireGuard® is a registered trademark of Jason A. Donenfeld.

    May The Source Be With You


    7. References

    [1] https://speakerdeck.com/fadis/l2-wireguard?slide=2
    [2] https://www.youtube.com/watch?v=8UIieDEk5k8&ab_channel=Fadis
    [3] https://speakerdeck.com/fadis/zuo-tuteli-jie-suruwireguard
    [4] https://slowbootkernelhacks.blogspot.com/2023/02/nanopi-r4s-pq-wireguard-vpn-router.html
    [5] https://slowbootkernelhacks.blogspot.com/2020/09/wireguard-vpn.html
    [6] https://www.softether.org/
    [7] my blog post

        [a] https://slowbootkernelhacks.blogspot.com/2020/04/espressobin-wireguard-vpn.html
        [b] https://slowbootkernelhacks.blogspot.com/2020/05/openwrt-gainstrong-minibox3-wireguard.html
        [c] https://slowbootkernelhacks.blogspot.com/2020/09/wireguard-vpn.html
        [d] https://slowbootkernelhacks.blogspot.com/2023/01/orangepi-r1-plus-lts-pqc-wireguard-vpn.html
        [e] https://slowbootkernelhacks.blogspot.com/2023/02/esp32-wireguard-nat-router-pqc.html
        [f] https://slowbootkernelhacks.blogspot.com/2023/02/nanopi-r4s-pq-wireguard-vpn-router.html
        [g] https://slowbootkernelhacks.blogspot.com/2023/02/nanopi-r4s-pq-wireguard-vpn-router-part.html
        [h] https://slowbootkernelhacks.blogspot.com/2024/01/dpdk-fdio-vpp-security-gateway.html
        [i] https://slowbootkernelhacks.blogspot.com/2024/05/nanopi-wireguard-go-quantum-safe-vpn.html

    [8] https://gist.github.com/zOrg1331/a2a7ffb3cfe3b3b821d45d6af00cb8f6
    [9] https://notes.superlogical.ch/pages/note_wg/nolayer2/
    [10] https://www.netmanias.com/ko/post/blog/5348/dhcp-ip-allocation-network-protocol/understanding-the-basic-operations-of-dhcp
    [11] https://info.support.huawei.com/info-finder/encyclopedia/en/GRE.html
    [12] And Google~

    Slowboot






    댓글 없음:

    댓글 쓰기