이번 시간에는 FD.io VPP와 WireGuard를 연동하여 고성능 WireGuard(a.k.a High Performance VPP WireGuard)를 만드는 다양한 방법에 관하여 소개해 보고자 한다. 😎
Let's make a high-performance vpp-wireguard gateway !
목차
1. Intel Xeon 장비에 VPP 올리기
2. WireGuard 성능(Throughput)에 관한 고찰 🚀🚀🚀
3. VPP WireGuard plugin 소개
4. VPP와 WireGuard-Go 연동하기#1 - Server ony mode
5. VPP와 WireGuard-Go 연동하기#2 - Gateway mode
6. VPP와 WireGuard-Go 연동하기#3 - 개선 방향
7. References
1. Intel Xeon 장비에 VPP 올리기
Alibaba에서 Intel appliance를 하나 구입했다. 중국산 제품이 quality 면에서 문제가 있다는 편견이 있는 것도 사실이지만, 가격대 성능비 면에서 탁월한 선택이라는 점도 부인할 수 없는 사실이다. 특히, 다양한 제품이 즐비하고 있으며, 자신이 원하는 스펙에 맞게 nego가 가능하다는 점은 참으로 매력적으로 다가온다. 😍
[그림 1.1] Intel Xeon appliance [출처 - 참고문헌 1]
1) CPU: Intel Xeon E5-2695 v4 @ 2.10GHz (CPU core: 18개, 36 threads)
2) RAM: 16GB
3) NVME: 128GB
4) NIC: 8 x 2.5G(i226V)
5) SFP+: No
6) USB: 4 x USB3.0
7) VGA 1
________________________________
ASIC이나 FPGA의 도움없이 S/W 만으로 고성능 패킷 처리를 하고자 한다면, DPDK나 XDP(eBPF) 같은 기술이 반드시 필요하다. 아래 2개의 그림은 VPP의 근간을 이루는 DPDK의 동작 원리를 한눈에 파악하는데 도움을 준다.
[그림 1.2] DPDK 동작 원리(1) [출처 - 참고문헌 7]
[그림 1.3] DPDK 동작 원리(2) [출처 - 참고문헌 2]
1.1 VPP build하기
-> vpp 최신 code를 download 받아 build해 보기로 한다.
<Ubuntu 22.04 LTS PC>
$ git clone https://github.com/FDio/vpp
$ cd vpp
$ git checkout stable/2510
-> 반드시 stable version을 사용하도록 하자.
$ make build
$ make pkg-deb
$ cd build-root
$ ls -l *.deb
-rw-r--r-- 1 chyi chyi 166802 9월 25 13:50 libvppinfra-dev_25.10-rc1~0-g4f366b5bb_amd64.deb
-rw-r--r-- 1 chyi chyi 207158 9월 25 13:50 libvppinfra_25.10-rc1~0-g4f366b5bb_amd64.deb
-rw-r--r-- 1 chyi chyi 30460 9월 25 13:50 python3-vpp-api_25.10-rc1~0-g4f366b5bb_amd64.deb
-rw-r--r-- 1 chyi chyi 1334194 9월 25 13:50 vpp-crypto-engines_25.10-rc1~0-g4f366b5bb_amd64.deb
-rw-r--r-- 1 chyi chyi 105094774 9월 25 13:50 vpp-dbg_25.10-rc1~0-g4f366b5bb_amd64.deb
-rw-r--r-- 1 chyi chyi 1429224 9월 25 13:50 vpp-dev_25.10-rc1~0-g4f366b5bb_amd64.deb
-rw-r--r-- 1 chyi chyi 5071642 9월 25 13:50 vpp-plugin-core_25.10-rc1~0-g4f366b5bb_amd64.deb
-rw-r--r-- 1 chyi chyi 416232 9월 25 13:50 vpp-plugin-devtools_25.10-rc1~0-g4f366b5bb_amd64.deb
-rw-r--r-- 1 chyi chyi 5519694 9월 25 13:50 vpp-plugin-dpdk_25.10-rc1~0-g4f366b5bb_amd64.deb
-rw-r--r-- 1 chyi chyi 5603736 9월 25 13:50 vpp_25.10-rc1~0-g4f366b5bb_amd64.deb
1.2 VPP 설치하기
-> Target 장비에 Ubuntu 24.04 server LTS 버젼을 설치한다(간단한 내용이라 생략함).
-> 이후, Ubuntu 22.04 LTS PC -> target 장비로 deb package를 복사한 후, 설치를 진행한다.
<Target 장비>
$ sudo dpkg -i ./*.deb
Selecting previously unselected package libvppinfra-dev.
(Reading database ... 96114 files and directories currently installed.)
Preparing to unpack .../libvppinfra-dev_25.10-rc1~0-g4f366b5bb_amd64.deb ...
Unpacking libvppinfra-dev (25.10-rc1~0-g4f366b5bb) ...
Selecting previously unselected package libvppinfra.
Preparing to unpack .../libvppinfra_25.10-rc1~0-g4f366b5bb_amd64.deb ...
Unpacking libvppinfra (25.10-rc1~0-g4f366b5bb) ...
Selecting previously unselected package python3-vpp-api.
Preparing to unpack .../python3-vpp-api_25.10-rc1~0-g4f366b5bb_amd64.deb ...
Unpacking python3-vpp-api (25.10-rc1~0-g4f366b5bb) ...
Selecting previously unselected package vpp-crypto-engines.
Preparing to unpack .../vpp-crypto-engines_25.10-rc1~0-g4f366b5bb_amd64.deb ...
Unpacking vpp-crypto-engines (25.10-rc1~0-g4f366b5bb) ...
Selecting previously unselected package vpp-dbg.
Preparing to unpack .../vpp-dbg_25.10-rc1~0-g4f366b5bb_amd64.deb ...
Unpacking vpp-dbg (25.10-rc1~0-g4f366b5bb) ...
Selecting previously unselected package vpp-dev.
Preparing to unpack .../vpp-dev_25.10-rc1~0-g4f366b5bb_amd64.deb ...
Unpacking vpp-dev (25.10-rc1~0-g4f366b5bb) ...
Selecting previously unselected package vpp-plugin-core.
Preparing to unpack .../vpp-plugin-core_25.10-rc1~0-g4f366b5bb_amd64.deb ...
Unpacking vpp-plugin-core (25.10-rc1~0-g4f366b5bb) ...
Selecting previously unselected package vpp-plugin-devtools.
Preparing to unpack .../vpp-plugin-devtools_25.10-rc1~0-g4f366b5bb_amd64.deb ...
Unpacking vpp-plugin-devtools (25.10-rc1~0-g4f366b5bb) ...
Selecting previously unselected package vpp-plugin-dpdk.
Preparing to unpack .../vpp-plugin-dpdk_25.10-rc1~0-g4f366b5bb_amd64.deb ...
Unpacking vpp-plugin-dpdk (25.10-rc1~0-g4f366b5bb) ...
Selecting previously unselected package vpp.
Preparing to unpack .../vpp_25.10-rc1~0-g4f366b5bb_amd64.deb ...
start-stop-daemon: unable to stat /usr/bin/vpp (No such file or directory)
Unpacking vpp (25.10-rc1~0-g4f366b5bb) ...
Setting up libvppinfra-dev (25.10-rc1~0-g4f366b5bb) ...
Setting up libvppinfra (25.10-rc1~0-g4f366b5bb) ...
Setting up vpp-dbg (25.10-rc1~0-g4f366b5bb) ...
Setting up vpp-dev (25.10-rc1~0-g4f366b5bb) ...
Setting up vpp (25.10-rc1~0-g4f366b5bb) ...
* Applying /usr/lib/sysctl.d/10-apparmor.conf ...
* Applying /etc/sysctl.d/10-bufferbloat.conf ...
* Applying /etc/sysctl.d/10-console-messages.conf ...
* Applying /etc/sysctl.d/10-ipv6-privacy.conf ...
* Applying /etc/sysctl.d/10-kernel-hardening.conf ...
* Applying /etc/sysctl.d/10-magic-sysrq.conf ...
* Applying /etc/sysctl.d/10-map-count.conf ...
* Applying /etc/sysctl.d/10-network-security.conf ...
* Applying /etc/sysctl.d/10-ptrace.conf ...
* Applying /etc/sysctl.d/10-zeropage.conf ...
* Applying /usr/lib/sysctl.d/50-pid-max.conf ...
* Applying /etc/sysctl.d/80-vpp.conf ...
* Applying /usr/lib/sysctl.d/99-protect-links.conf ...
* Applying /etc/sysctl.d/99-sysctl.conf ...
* Applying /etc/sysctl.conf ...
kernel.apparmor_restrict_unprivileged_userns = 1
net.core.default_qdisc = fq_codel
kernel.printk = 4 4 1 7
net.ipv6.conf.all.use_tempaddr = 2
net.ipv6.conf.default.use_tempaddr = 2
kernel.kptr_restrict = 1
kernel.sysrq = 176
vm.max_map_count = 1048576
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.all.rp_filter = 2
kernel.yama.ptrace_scope = 1
vm.mmap_min_addr = 65536
kernel.pid_max = 4194304
vm.nr_hugepages = 1024
vm.hugetlb_shm_group = 0
fs.protected_fifos = 1
fs.protected_hardlinks = 1
fs.protected_regular = 2
fs.protected_symlinks = 1
Setting up python3-vpp-api (25.10-rc1~0-g4f366b5bb) ...
Setting up vpp-crypto-engines (25.10-rc1~0-g4f366b5bb) ...
Setting up vpp-plugin-core (25.10-rc1~0-g4f366b5bb) ...
Setting up vpp-plugin-devtools (25.10-rc1~0-g4f366b5bb) ...
Setting up vpp-plugin-dpdk (25.10-rc1~0-g4f366b5bb) ...
Processing triggers for libc-bin (2.39-0ubuntu8.6) ...
1.3 VPP 돌려 보기 - NAT44 Router 구성
vpp가 설치되었으니, 아래 테스트 베드를 참조하여 vpp 설정을 진행해 보도록 하자.
<Target 장비>
vpp 설치와 관련 자세한 사항은 (여러 차례 소개한 바 있으므로) 이전 posting을 참조하기 바란다. 가급적 반복되는 내용은 생략하도록 한다. 💢
먼저, target 장치의 network interface를 확인해 보니, 2.5Gbps NIC 8개(enp10s0 ~ enp17s0)가 보인다.
$ ifconfig -a
enp10s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.5.254 netmask 255.255.255.0 broadcast 192.168.5.255
inet6 fe80::4e4b:f9ff:fe45:1ff8 prefixlen 64 scopeid 0x20<link>
ether 4c:4b:f9:45:1f:f8 txqueuelen 1000 (Ethernet)
RX packets 309 bytes 24712 (24.7 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 19 bytes 1202 (1.2 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device memory 0xfaf00000-faffffff
enp11s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.8.10 netmask 255.255.255.0 broadcast 192.168.8.255
inet6 fe80::4e4b:f9ff:fe45:1ff9 prefixlen 64 scopeid 0x20<link>
ether 4c:4b:f9:45:1f:f9 txqueuelen 1000 (Ethernet)
RX packets 159 bytes 15294 (15.2 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 109 bytes 15289 (15.2 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device memory 0xfad00000-fadfffff
enp12s0: flags=4098<BROADCAST,MULTICAST> mtu 1500
ether 4c:4b:f9:45:1f:fa txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device memory 0xfab00000-fabfffff
enp13s0: flags=4098<BROADCAST,MULTICAST> mtu 1500
ether 4c:4b:f9:45:1f:fb txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device memory 0xfa900000-fa9fffff
enp14s0: flags=4098<BROADCAST,MULTICAST> mtu 1500
ether 4c:4b:f9:45:1f:fc txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device memory 0xfa700000-fa7fffff
enp15s0: flags=4098<BROADCAST,MULTICAST> mtu 1500
ether 4c:4b:f9:45:1f:fd txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device memory 0xfa500000-fa5fffff
enp16s0: flags=4098<BROADCAST,MULTICAST> mtu 1500
ether 4c:4b:f9:45:1f:fe txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device memory 0xfa300000-fa3fffff
enp17s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.1.254 netmask 255.255.255.0 broadcast 192.168.1.255
inet6 fe80::4e4b:f9ff:fe45:1fff prefixlen 64 scopeid 0x20<link>
inet6 fd22:c8f9:a15f::4c6 prefixlen 128 scopeid 0x0<global>
inet6 fd22:c8f9:a15f:0:4e4b:f9ff:fe45:1fff prefixlen 64 scopeid 0x0<global>
ether 4c:4b:f9:45:1f:ff txqueuelen 1000 (Ethernet)
RX packets 23 bytes 2566 (2.5 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 33 bytes 2525 (2.5 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device memory 0xfa100000-fa1fffff
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 164 bytes 11792 (11.7 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 164 bytes 11792 (11.7 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
$ ethtool enp10s0
Settings for enp10s0:
Supported ports: [ TP ]
Supported link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Full
2500baseT/Full
Supported pause frame use: Symmetric
Supports auto-negotiation: Yes
Supported FEC modes: Not reported
Advertised link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Full
2500baseT/Full
Advertised pause frame use: Symmetric
Advertised auto-negotiation: Yes
Advertised FEC modes: Not reported
Speed: 2500Mb/s
Duplex: Full
Auto-negotiation: on
Port: Twisted Pair
PHYAD: 0
Transceiver: internal
MDI-X: off (auto)
netlink error: Operation not permitted
Current message level: 0x00000007 (7)
drv probe link
Link detected: yes
_________________________________
다음으로, dpdk bind 상태를 확인해 보기로 하자.
$ sudo dpdk-devbind.py -s
-> *Active*가 3군데 보이는 이유는 eth0(LAN), eth7(WAN), 그리고 eth1(ssh 접속 용)에 LAN cable을 연결해 두었기 때문이다.
Network devices using kernel driver
===================================
0000:0a:00.0 'Ethernet Controller I226-V 125c' if=enp10s0 drv=igc unused=uio_pci_generic *Active* //eth0: LAN
0000:0b:00.0 'Ethernet Controller I226-V 125c' if=enp11s0 drv=igc unused=uio_pci_generic *Active* //eth1: ssh 접속용
0000:0c:00.0 'Ethernet Controller I226-V 125c' if=enp12s0 drv=igc unused=uio_pci_generic
0000:0d:00.0 'Ethernet Controller I226-V 125c' if=enp13s0 drv=igc unused=uio_pci_generic
0000:0e:00.0 'Ethernet Controller I226-V 125c' if=enp14s0 drv=igc unused=uio_pci_generic
0000:0f:00.0 'Ethernet Controller I226-V 125c' if=enp15s0 drv=igc unused=uio_pci_generic
0000:10:00.0 'Ethernet Controller I226-V 125c' if=enp16s0 drv=igc unused=uio_pci_generic
0000:11:00.0 'Ethernet Controller I226-V 125c' if=enp17s0 drv=igc unused=uio_pci_generic *Active* //eth7: WAN
No 'Baseband' devices detected
==============================
No 'Crypto' devices detected
============================
No 'DMA' devices detected
=========================
No 'Eventdev' devices detected
==============================
No 'Mempool' devices detected
=============================
No 'Compress' devices detected
==============================
No 'Misc (rawdev)' devices detected
===================================
No 'Regex' devices detected
===========================
No 'ML' devices detected
========================
_________________________________
이 상태에서 dpdk binding 설정을 해보면 다음과 같다.
<dpdk 설정>
$ sudo modprobe vfio-pci
Linux 커널의 VFIO(Virtual Function I/O) 프레임워크의 일부로, PCI 장치를 사용자 공간 애플리케이션이 직접 제어할 수 있도록 하는 드라이버. IOMMU 하드웨어를 사용하여 안전하게 I/O 메모리에 접근하며, IOMMU가 없으면 NO-IOMMU 모드를 사용함.
📌 [주의 사항] 처음에는 uio_pci_generic driver를 이용하여 설정해 보았으나, vppcli 상태에서 network interface가 하나도 출력되지 않는 문제가 있어, 위의 방법을 적용하게 되었다.
$ sudo ifconfig enp10s0 down
$ sudo ifconfig enp17s0 down
$ sudo dpdk-devbind.py -b vfio-pci 0000:0a:00.0
$ sudo dpdk-devbind.py -b vfio-pci 0000:11:00.0
$ sudo dpdk-devbind.py -s
Network devices using DPDK-compatible driver
============================================
0000:0a:00.0 'Ethernet Controller I226-V 125c' drv=vfio-pci unused=igc,uio_pci_generic
0000:0c:00.0 'Ethernet Controller I226-V 125c' drv=vfio-pci unused=igc,uio_pci_generic
0000:0d:00.0 'Ethernet Controller I226-V 125c' drv=vfio-pci unused=igc,uio_pci_generic
0000:0e:00.0 'Ethernet Controller I226-V 125c' drv=vfio-pci unused=igc,uio_pci_generic
0000:0f:00.0 'Ethernet Controller I226-V 125c' drv=vfio-pci unused=igc,uio_pci_generic
0000:10:00.0 'Ethernet Controller I226-V 125c' drv=vfio-pci unused=igc,uio_pci_generic
0000:11:00.0 'Ethernet Controller I226-V 125c' drv=vfio-pci unused=igc,uio_pci_generic
📌 앞서 2개의 NIC에 대해서만 binding을 시도했는데, (Active 상태인 것을 제외하고) down 상태에 있는 7개 모두가 binding이 되어 버렸다.
Network devices using kernel driver
===================================
0000:0b:00.0 'Ethernet Controller I226-V 125c' if=enp11s0 drv=igc unused=vfio-pci,uio_pci_generic *Active*
No 'Baseband' devices detected
==============================
No 'Crypto' devices detected
============================
No 'DMA' devices detected
=========================
No 'Eventdev' devices detected
==============================
No 'Mempool' devices detected
=============================
No 'Compress' devices detected
==============================
No 'Misc (rawdev)' devices detected
===================================
No 'Regex' devices detected
===========================
No 'ML' devices detected
========================
dpdk 설정이 끝났으니, vpp를 재구동한 후, 실제 nat44 router 설정에 들어가 보도록 하자.
<vpp 재구동 및 설정>
$ sudo service vpp restart
$ sudo vppctl
_______ _ _ _____ ___
__/ __/ _ \ (_)__ | | / / _ \/ _ \
_/ _// // / / / _ \ | |/ / ___/ ___/
/_/ /____(_)_/\___/ |___/_/ /_/
vpp# show interface
Name Idx State MTU (L3/IP4/IP6/MPLS) Counter Count
TwoDotFiveGigabitEthernet10/0/0 4 down 9000/0/0/0
TwoDotFiveGigabitEthernet11/0/0 5 down 9000/0/0/0 //wan port로 사용하자.
TwoDotFiveGigabitEtherneta/0/0 1 down 9000/0/0/0 //lan port로 사용하자
TwoDotFiveGigabitEthernete/0/0 2 down 9000/0/0/0
TwoDotFiveGigabitEthernetf/0/0 3 down 9000/0/0/0
local0 0 down 0/0/0/0
-> TwoDotFiveGigabitEtherneta/0/0는 linux host 상에서는 enp10s0(= eth0)로 인식되던 녀석이다.
Name Idx State MTU (L3/IP4/IP6/MPLS) Counter Count
TwoDotFiveGigabitEthernet10/0/0 4 down 9000/0/0/0
TwoDotFiveGigabitEthernet11/0/0 5 down 9000/0/0/0 //wan port로 사용하자.
TwoDotFiveGigabitEtherneta/0/0 1 down 9000/0/0/0 //lan port로 사용하자
TwoDotFiveGigabitEthernete/0/0 2 down 9000/0/0/0
TwoDotFiveGigabitEthernetf/0/0 3 down 9000/0/0/0
local0 0 down 0/0/0/0
-> TwoDotFiveGigabitEtherneta/0/0는 linux host 상에서는 enp10s0(= eth0)로 인식되던 녀석이다.
-> 또한, TwoDotFiveGigabitEthernet11/0/0는 linux host 상에서 enp17s0(= eth7)에 해당한다.
vpp# bvi create instance 0
vpp# set int l2 bridge bvi0 1 bvi
vpp# set int ip address bvi0 192.168.5.254/24
vpp# set int state bvi0 up
vpp# set int l2 bridge TwoDotFiveGigabitEtherneta/0/0 1
vpp# set int state TwoDotFiveGigabitEtherneta/0/0 up
-> bridge interface bvi0를 만들고, TwoDotFiveGigabitEtherneta/0/0를 여기에 포함시킨다.
-> 여기까지 LAN port(linux 개념으로 eth0)에 대해 설정한다.
vpp# set int state TwoDotFiveGigabitEthernet11/0/0 up
vpp# set int ip address TwoDotFiveGigabitEthernet11/0/0 192.168.1.254/24
vpp# ip route add 0.0.0.0/0 via 192.168.1.1 TwoDotFiveGigabitEthernet11/0/0
-> 여기까지 WAN port(linux 개념으로 eth7)와 default gateway 설정을 한다.
vpp# nat44 forwarding enable
vpp# nat44 plugin enable sessions 65536
vpp# set interface nat44 in bvi0 out TwoDotFiveGigabitEthernet11/0/0
vpp# nat44 add interface address TwoDotFiveGigabitEthernet11/0/0
-> 마지막으로 NAT44 설정을 하도록 하자.
vpp# show int
-> 지금까지 설정된 interface 상태를 확인한다.
[그림 1.6] show interface 명령 실행 모습
vpp# show int addr
-> interface에 할당된 ip 주소를 확인한다.
[그림 1.7] show int addr 명령 실행 모습
이 상태에서 내부망(LAN side)의 Ubuntu notebook(192.168.5.100/24)에서 internet 연결을 시도해 보니, 예상대로 정상 동작한다. 😀
2. WireGuard 성능(Throughput)에 관한 고찰
Jason A. Donenfeld가 만든 WireGuard는 Linux kernel 버젼에서 예술의 극치를 보여주었다.
"Can I just once again state my love for it and hope it gets merged
soon? Maybe the code isn’t perfect, but I’ve skimmed it, and compared
to the horrors that are OpenVPN and IPSec, it’s a work of art."
— Linus Torvalds on Linux Kernel Mailing List
하지만, 불행하게도 linux kernel의 networking code가 wireguard의 성능을 따라가지 못하는 관계로, wireguard kernel code의 우수성이 극대화되지 못하고 있는 것이 사실이다.
📌 linux kernel networking speed가 느린 이유를 단적으로 설명하기는 어려우나, 아래와 같은 다양한 요인을 생각해 볼 수 있을 듯하다.
1) specific network hardware에 맞게 tuning되지 않은 kernel 설정
2) NIC 카드 문제(suboptimal network driver 사용)
3) 복잡하고 많은 linux networking code
Kernel version의 wireguard 사용 시, 1GbE 환경에서는 거의 wire speed에 육박하는 성능치를 보여주지만, 10GbE 환경에서는 1-2Gbps 정도의 throughput 밖에 나오지 않는 심각한 문제가 있다.
# iperf3 -c 10.10.1.2 -b 10G -w 4096K
[그림 2.1] 10GbE 환경에서의 kernel wireguard udp 성능 측정 결과 - Intel XL710 사용
위의 시험 결과하고는 다르게, 보통 linux kernel wireguard는 10GbE 환경에서 2.6 Gbps의 throughput을 낸다고들 한다. 하지만 10GbE NIC을 사용한 상황에서, 2.6 Gpbs의 성능 밖에 내지 못한다면, 심각한 문제가 아닐 수 없다.
따라서, 이를 개선할 방법이 필요해 보이는데, 여기에 몇가지 대안을 정리해 보기로 한다. 💢
<고성능 WireGuard에 대한 고찰>
1) Tailscale에서 개선한 것 처럼 (사용자 영역에서 동작하는) WireGuard-Go 코드를 이용하기
2) Wireguard linux kernel + Smart NIC 활용하기
3) Windows Server에 WireGuard Windows 버젼을 올려 사용하기
4) Linux kernel wireguard - multiple wireguard interface & load balancing 하기
5) VPP와 WireGuard를 연동하기: 이번 posting의 주제
6) ASIC or FPGA 등 h/w의 도움을 받기
___________________________
2.1 wireguard-go를 이용한 방법
아래 link는 tailscale사 엔지니어(Jordan Whited)가 wireguard-go를 이용해서 10GbE 환경에서 성능을 끌어올린 아주 흥미로운 내용을 소개하고 있다. 커널 모드는 2.6 Gbps의 throughput을 보인 반면, 최적화된 사용자 모드인 Tailscale의 wireguard-go 버전은 7.33 Gbps로 약 264% 향상된 성능치를 보여주었다.
일반적으로 kernel에서 동작하는 코드가 사용자 영역에서 동작하는 코드(빈번한 context switching으로 속도 저하 발생) 에 비해 빠를 것으로 예상하지만, 통념을 깨고, tailscale engineer가 큰 일을 해냈다. 😋 TUN interface를 기반으로 사용자 영역에서 동작한다고 우습게 볼 일이 결코 아니다.
2.2 Wireugard linux kernel + Smart NIC 활용하기
2.1절에서 소개한 tailscale engineer에 의하면, Smart NIC을 잘 활용할 경우, linux kernel wireguard를 사용 시, 11.8 Gbps 정도의 성능을 낼 수가 있다. 물론, 동일 조건에서 (개선된) wireguard-go를 사용할 경우, 13 Gbps라는 우수한 성능을 낼 수도 있지만 말이다.
[그림 2.4] 10GbE 환경에서의 wireguard 성능 비교(2) [출처 - 참고문헌 8]
아래 NIC이 위의 성능 시험에서 사용된 Smart NIC에 해당한다.
[그림 2.5] Mellanox MCX512A-ACAT ConnectX-5EN NIC(10/25GbE Dual Port, SFP28, PCIe3.0 x8) [출처 - 참고문헌 11]
📌 최근들어 Mellanox는 Nvidia에 인수되었는데, Nvidia는 자신들의 GPU 기술과 Mellanox의 우수한 network 기술(InfiniBand와 Ethernet)을 접목하여 강력한 AI infrastucture를 구축하고 있다.2.3 Windows Server를 이용한 방법
(불행하게도) windows용 wireguard(NT kernel 버젼) 버젼의 성능이 linux kernel의 그것을 능가하는 것으로 나왔다. 아래 site의 시험 결과에 의하면 10GbE 환경에서 7.5 Gbps 정도가 나오는 것 같다.
📌 요즘은 M$에서도 다양한 open source project를 진행하고 있으니, M$를 너무 미워하지는 말자. 나만 그런가 ? ㅋ 😋
[그림 2.6] wireguard windows(NT version) 사용시 throughput 측정 결과 [출처 - 위의 link]
📌 위의 시험은 ethr.exe(M$에서 golang으로 구현한 성능 측정 tool - open source)으로 진행하였으며, tcp를 기반으로 하고 있다.
참고로, wireguard windows 버젼은 WinTun driver(이것도 Jason이 개발함)를 기반으로 사용자 영역에서 동작하는 버젼으로 출발했으나, 4-5년 전에 NT(kernel) 버젼(wireguard.sys)을 개발하면서 linux kernel용 wireguard code와 흡사한 형태로 발전하게 되었다.
2.4 Linux kernel wireguard: 복수개의 wireguard interface를 사용하는 방법
Linux kernel의 networking 병목 문제를 근본적으로 해결할 수는 없는 노릇이고, 다른 대안으로 아래와 같이 4개 혹은 8개의 wireguard interface를 만들어, 이들 간에 부하를 분산하는 방식으로 전체적인 throughput(정확하게는 throughput의 총합)을 끌어 올리는 방법을 생각해 볼 수 있다.
[그림 2.7] wireguad virtual bonding
아, 물론 이 방법은 실제 성능을 개선하는 것이라기 보다는, 복수개의 터널(예: 5000개 터널)을 사용하고, 1개의 터널당 1-2MB 정도의 data가 오고가는 환경이라면 고민해 볼만한 가치가 있는 방법이라고 말할 수 있다. 💢
wg0 + wg1 + wg2 + wg3 => 2Gbps + 2Gbps + 2Gbps + 2Gbps => 8Gbps
어쨌든 여러 면에서 볼 때, Linux를 기반으로 하는 network 장비가 유리한 면이 크기 때문에, 이를 기반으로 하면서도, 위의 조건을 충족하는 경우라면, 시도해 볼만한 가치가 있는 방법이다.
2.5 VPP + WireGuard 연동 하기
이번 posting의 주제에 해당하는 내용으로, DPDK 기반의 FD.io VPP 위에 wireguard를 올려 성능을 극대화 시킬 수가 있다.
참고로, VPP + WireGuard에 Intel QAT 기술을 접목하여 성능을 획기적으로 개선시키는 방법도 생각해 볼 수 있다.
2.6 ASIC이나 FPGA 등 H/W의 도움을 받기
FPGA를 기반으로 wireguard 기능을 구현한 project도 있다. 아직은 PoC 단계인 듯 보인다.
📌 개발 보드를 판매하고는 있으나, BSP 관련 부분이 보이질 않아, 구매할지 여부를 고민하고 있다. 현재 1GbE만 지원하는 것도 구매를 망설이게 하는 요인이다. 😂
아래 site도 참고해 볼 만하다.
_____________________
이상으로, WireGuard의 고성능 패킷 처리와 관련하여 필자가 생각해 본 방안을 정리해 보았다. 이어지는 장부터는 본격적으로 이번 posting의 주제로 들어가 보기로 하자. 😎
3. VPP WireGuard Plugin 소개
이번 장에서는 VPP에서 기본으로 제공하는 wireguard plugin을 소개해 볼 차례이다. 시험을 위한 테스트 환경은 다음과 같다.
[그림 3.1] VPP Wireguard 테스트 환경 구성
📌 Home에서 구축한 환경이다 보니, 다소 scale이 작을 수 밖에 없다. 하지만, 기본 동작을 확인하는데는 전혀 문제가 없다. 😁
3.1 wireguard plugin 설정하기
사실 vpp wireguard에 관해서는 이전 posting을 통해 이미 여러 차례 소개한 바 있다. 따라서 여기서는 간단히 소개하고 넘어가기로 하자. 😋
[주의 사항] nat44 설정 상태에서 wireguard를 돌려 보니, 동작에 문제가 있다. 따라서 이번 시험에서는 nat44 설정이 없는 환경에서 테스트를 진행하였다. 근본적인 이유는 추후에 파악해 보기로 한다.
<Target 장비>
comment {LAN}
vpp# set int state TwoDotFiveGigabitEtherneta/0/0 up
vpp# set int ip address TwoDotFiveGigabitEtherneta/0/0 192.168.5.254/24
comment {WAN}
vpp# set int state TwoDotFiveGigabitEthernet11/0/0 up
vpp# set int ip address TwoDotFiveGigabitEthernet11/0/0 192.168.1.254/24
vpp# ip route add 0.0.0.0/0 via 192.168.1.1 TwoDotFiveGigabitEthernet11/0/0
-> default gateway를 설정한다.
vpp# set int state TwoDotFiveGigabitEtherneta/0/0 up
vpp# set int ip address TwoDotFiveGigabitEtherneta/0/0 192.168.5.254/24
comment {WAN}
vpp# set int state TwoDotFiveGigabitEthernet11/0/0 up
vpp# set int ip address TwoDotFiveGigabitEthernet11/0/0 192.168.1.254/24
vpp# ip route add 0.0.0.0/0 via 192.168.1.1 TwoDotFiveGigabitEthernet11/0/0
-> default gateway를 설정한다.
comment {wireguard}
vpp# wireguard create listen-port 51820 private-key WJjQ7Nkl6hpQwZ3bs8yaAzXc9+unIZ0iSPDaEptKX1k= src 192.168.1.254
vpp# set interface state wg0 up
vpp# set interface ip address wg0 10.1.1.100/24
vpp# set interface mtu packet 1420 wg0
-> wireguard interface 설정을 한다.
📌 wireguard curve25519 keypair는 wg tool을 사용하여 사전에 만들어 둔 것을 이용한다. 즉, wg genkey | tee ./privatekey | wg pubkey > ./publickey
-> wireguard peer를 추가한다.
-> vpp에서는 endpoint를 설정하는 방식이 vanilla wireguard하고는 약간 다르다. dst-port 부분을 endpoint 내에 포함시켰더라면 좋았을 듯한데, 그렇지 못하다.
vpp# ip route add 10.1.1.200/32 via 10.1.1.100 wg0
📌[중요] wireguard용 routing entry를 추가해 준다. 희한하게도 아래 명령으로는 안된다.
ip route add 10.1.1.0/24 via 10.1.1.100 wg0 ................................. (A)
comment {lcp configurations}
vpp# lcp create TwoDotFiveGigabitEtherneta/0/0 host-if eth0
vpp# lcp create TwoDotFiveGigabitEthernet11/0/0 host-if eth7
vpp# lcp create wg0 host-if wg0 tun
vpp# lcp lcp-sync on
-> 이렇게 해 주면, linux host 상에 LAN, WAN port 각각에 대응하는 eth0, eth7 interface가 자동으로 생성된다.
<Target 장비 - linux host 상태>
$ sudo ip route default gw 192.168.1.1
________________________________________
지금까지 설정한 vpp network configuration을 확인해 보면 다음과 같다.
vpp# show int
vpp# show int addr
다음은 wireguard 설정 부분이다.
vpp# show wireguard interface
vpp# show wireguard peer
📌 [주의 사항] vpp cli 상태에는 peer vpn ip(10.1.1.200)로의 ping은 먹히나, (희한하게도) 자기 자신의 vpn ip(10.1.1.100)로의 ping은 안된다.
case#2) vpp linux host -> windows PC vpnip(10.1.1.200) ping test
$ ping 10.1.1.200
PING 10.1.1.200 (10.1.1.200) 56(84) bytes of data.
64 bytes from 10.1.1.200: icmp_seq=1 ttl=128 time=3.69 ms
64 bytes from 10.1.1.200: icmp_seq=2 ttl=128 time=2.47 ms
64 bytes from 10.1.1.200: icmp_seq=3 ttl=128 time=7.02 ms
64 bytes from 10.1.1.200: icmp_seq=4 ttl=128 time=5.57 ms
64 bytes from 10.1.1.200: icmp_seq=5 ttl=128 time=4.57 ms
^C
--- 10.1.1.200 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4006ms
rtt min/avg/max/mdev = 2.468/4.662/7.017/1.557 ms
case#3) 내부망 Linux PC(192.168.5.10) -> windows PC vpnip(10.1.1.200) ping test
$ ping 10.1.1.200
PING 10.1.1.200 (10.1.1.200) 56(84) bytes of data.
64 bytes from 10.1.1.200: icmp_seq=1 ttl=127 time=3.49 ms
64 bytes from 10.1.1.200: icmp_seq=2 ttl=127 time=4.39 ms
64 bytes from 10.1.1.200: icmp_seq=3 ttl=127 time=3.59 ms
64 bytes from 10.1.1.200: icmp_seq=4 ttl=127 time=3.26 ms
64 bytes from 10.1.1.200: icmp_seq=5 ttl=127 time=3.37 ms
64 bytes from 10.1.1.200: icmp_seq=6 ttl=127 time=3.26 ms
OK, vpp 장비 자체 및 LAN side에서 해 볼 수 있는 모든 ping 시험은 정상 동작한다.
3.2 Windows peer wireguard 설정하기
이번에는 반대 방향 즉, 외부망 Windows PC에서 wireguard 설정을 진행한 후, ping test를 해 보도록 하자.
<vpn 연결 시험 하기#2>
C:\> ping 10.1.1.100
-> case#4) Ping to peer vpnip is OK.
C:\> ping 192.168.5.10
-> case#5) Ping to vpp LAN side PC is OK.
C:\> ping 192.168.5.254
-> case#6) Ping to vpp LAN interface is OK.
[그림 3.8] 외부망 Windows PC로 부터의 ping test 결과
역시, 이번에도 모든 경우에 정상적으로 ping 연결에 성공하였다. 😍
마지막으로, wireguard plugin의 단점을 정리하는 것으로 이 장을 마무리하도록 하겠다.
1) (앞서 소개하지는 않았지만) wireguard peer를 삭제하기 위해 peer index를 이용해야만 한다.
wireguard peer remove <peer_idx>
하지만, 일반적으로 field에서 원하는 방식은 peer의 publickey를 기준으로 삭제하는 것이다.
2) (linux kernel 버젼과 비교할 경우) NAT44 환경에서는 동작하지 않는다.
3) routing entry 추가 관련하여 일반적이지 못한 면이 보인다. - (A)
4. VPP와 WireGuard-Go 연동하기#1 - Server only mode
VPP Wireguard plugin도 좋지만, 개선해야 할 점이 보이는 만큼, 이번 장에서는 (WireGuard 원저자인 Jason A. Donenfeld가 만든) WireGuard-Go version을 코드 수정 없이 VPP에 연동시키는 방법에 관하여 고민해 보고자 한다. 사실 이 내용은 일전에 한차례 소개한 바 있다.
이번 시험에서 하려는 바를 이해하기 쉽게 그림으로 표현해 보면 다음과 같다. 아래 내용은, 가령 특정 server를 vpp host 상에 올려둔 상태에서 wireguard를 설치한 client(peer)에서 접근하고자 하는 경우에 사용하면 좋은 구성이라고 보면 된다. 물론 이러한 client & server 간의 암호 통신은 일반적으로 TLS를 기반으로 해결하는게 보통이지만, TLS를 사용하기 위해서는 특정 client & server 코드를 수정해야 하는 만큼, code 수정이 불가한 경우에는 wireguard 구성도 좋은 대안이 될 수 있을 것으로 보인다.
역시, 반복되는 부분이라 곧 바로 설정으로 들어가 보도록 하자.
4.1 vpp 설정하기
comment {LAN}
vpp# set int state TwoDotFiveGigabitEtherneta/0/0 up
vpp# set int ip address TwoDotFiveGigabitEtherneta/0/0 192.168.5.254/24
comment {WAN}
vpp# set int state TwoDotFiveGigabitEthernet11/0/0 up
vpp# set int ip address TwoDotFiveGigabitEthernet11/0/0 192.168.1.254/24
vpp# ip route add 0.0.0.0/0 via 192.168.1.1 TwoDotFiveGigabitEthernet11/0/0
comment {lcp configuration}
vpp# lcp create TwoDotFiveGigabitEtherneta/0/0 host-if eth0
vpp# lcp create TwoDotFiveGigabitEthernet11/0/0 host-if eth7
comment {bvi1 interface creation}
vpp# bvi create instance 1
vpp# set int l2 bridge bvi1 2 bvi
vpp# set int ip address bvi1 10.1.1.100/24
vpp# set int state bvi1 up
-> bvi1 bridge interface를 하나 만든다.
vpp# set int l2 bridge TwoDotFiveGigabitEthernet11/0/0 2
vpp# set int l2 bridge tap4097 2
-> bvi1 bridge에 WAN port 및 wan port용 tap interface(= tap4097)를 포함시킨다.
vpp# lcp lcp-sync on
다음으로, (Target 장비의 linux host 상에서) wireguard-go 설정을 진행하도록 한다.
4.2 wireguard-go 설정하기
$ sudo ./wireguard-go wg0
$ sudo ip address add dev wg0 10.1.1.100/24
$ sudo ip link set up dev wg0
$ sudo wg set wg0 listen-port 51820 private-key /home/chyi/workspace/wg/privatekey peer HMRtHJAw9doVvryBWKCqz2cd3ErCw1IavlStkv/PyjE= allowed-ips 10.1.1.0/24 endpoint 192.168.8.169:51820
$ sudo route add default gw 192.168.1.1
4.3 Windows peer wireguard 설정하기
모든 설정이 끝났으니, [그림 3.1] 테스트베드를 기준으로 vpn 연결 시험을 진행해 보도록 한다.
case#1) vpp cli -> windows vpn ip(10.1.1.200)
-> Ping 안됨.
case#2) vpp linux host -> windows vpn ip(10.1.1.200)
-> Ping OK
case#3) 내부망 PC -> windows vpn ip(10.1.1.200)
-> Ping 안됨.
case#4) windows PC -> vpp vpn ip(10.1.1.100)
-> Ping OK
case#5) windows PC -> vpp LAN ip(192.168.5.254)
-> Ping OK
case#6) windows PC -> vpp LAN PC(192.168.5.10)
-> Ping 안됨.
예상대로, vpp linux host <-> windows 간에만 통신이 되는 것을 알 수가 있다.
_________________________________
이번 장에서 설명한 방법의 단점은 크게 2가지가 있다.
1) vpp + wireguard-go를 gateway mode에서는 사용할 수가 없다. 즉, server 형태로만 사용 가능하다.
2) TUN interface 부분을 linux kernel에 의존하므로, 이 부분에서 병목이 발생할 수 있다.
사실, 위와 같은 중대한 문제점이 있는 방법임에도 불구하고, 굳이 이번 장의 내용을 소개한 이유는, 5장을 설명하기 위해서이다.
5. VPP와 WireGuard-Go 연동하기#2 - Gateway mode
이번 장에서는 앞 장에서 제시한 문제점을 해결하기 위해, VPP에서 제공하는 libmemif library를 사용하여, VPP와 wireguard-go를 연동시키는 방법에 관하여 소개하고자 한다.
[그림 5.1] VPP memif library [출처 - 참고문헌 5]
[그림 5.2] memif interface를 이용하여 VPP와 wireguard-go 연동하기
Libmemif interface를 사용하기 위해서는 wireguard-go 코드 수정이 불가피한데, 아래 site에서 이와 관련한 hint를 얻을 수 있다. 4년전에 진행되었던 project(vpp 최신 버젼과 맞지 않는 문제가 있음)이고, 행간에 숨겨져 있는 내용이 많이 있지만, (그럼에도 불구하고) 동작 가능하도록 만들어 보도록 하자. 🚀
5.1 wireguard-go-vpp build 하기
vpp는 일반 사용자 application과의 통신을 위한 창구로 memif라는 interface를 제공한다고 하였다. 그런데, vpp 기본 build에는 이의 내용이 포함되어 있지 않은 관계로, 아래와 같이 직접 build해야만 한다.
<libmemif.so library 준비하기>
-> Ubuntu 22.04 LTS PC를 기준으로 설명한다.
$ git clone https://github.com/FDio/vpp
$ cd vpp
$ git checkout stable/2510
$ cd vpp/extras/libmemif
$ mkdir build; cd build
$ cmake ..
$ make
$ cd lib
$ ls -l libmemif.so
$ sudo cp ./libmemif.so /usr/lib/x86_64-linux-gnu/
$ sudo ldconfig
-> 아, 이 부분은 target 장비에서도 진행해 주어야 한다. 즉, libmemif.so를 target 장비의 동일한 위치에 복사해 주어야 한다.
$ sudo mkdir -p /usr/include/memif
$ cd vpp/extras/libmemif/src
$ sudo cp ./libmemif.h /usr/include/memif
-> memif library를 사용하는 application에서 이 header file을 사용한다.
자, 이번에는 wireguard-go-vpp project를 build할 차례이다.
<wireguard-go-vpp build 하기>
$ git clone https://github.com/KusakabeShi/wireguard-go-vpp
$ cd wireguard-go-vpp
$ make
go mod vendor && \
patch -p0 -i govpp_remove_crcstring_check.patch && \
go build -v -o "wireguard-go-vpp"
patching file vendor/git.fd.io/govpp.git/adapter/socketclient/socketclient.go
git.fd.io/govpp.git/extras/libmemif
# git.fd.io/govpp.git/extras/libmemif
vendor/git.fd.io/govpp.git/extras/libmemif/adapter.go:540:26: could not determine kind of name for C.memif_cleanup
vendor/git.fd.io/govpp.git/extras/libmemif/adapter.go:489:17: could not determine kind of name for C.memif_init
make[1]: *** [Makefile:21: wireguard-go-vpp] 오류 1
make: *** [Makefile:12: generate-version-and-build] 오류 2
어라, 에러가 발생한다. 에러가 발생하는 이유는 wireguard-go-vpp에서 사용하는 libmemif version이 3.1이고, vpp 최신 버젼에는 4.0 version이 포함되어 있기 때문이다. 따라서 이를 해결하기 위해서는 아래 GoVPP project code를 이용해야 한다.
[그림 5.3] GoVPP project [출처 - 아래 github link]
$ git clone https://github.com/FDio/govpp
$ cd govpp
$ cd extras/libmemif
$ ls -l
-rw-rw-r-- 1 chyi chyi 10158 9월 30 13:06 README.md
-rw-rw-r-- 1 chyi chyi 40165 9월 30 13:40 adapter.go
-rw-rw-r-- 1 chyi chyi 276 9월 30 13:06 doc.go
-rw-rw-r-- 1 chyi chyi 3938 9월 30 13:06 error.go
drwxrwxr-x 6 chyi chyi 4096 9월 30 13:06 examples
-rw-rw-r-- 1 chyi chyi 3671 9월 30 13:06 packethandle.go
결론만 얘기하자면, 여기에 있는 go file이 최신 버젼이므로, 이것으로 아래 디렉토리의 내용을 교체해 주어야 한다.
wireguard-go-vpp/vendor/git.fd.io/govpp.git/extras/libmemif
-rw-rw-r-- 1 chyi chyi 10073 9월 30 15:07 README.md
-rw-rw-r-- 1 chyi chyi 40165 9월 30 15:08 adapter.go
-rw-rw-r-- 1 chyi chyi 276 9월 30 15:08 doc.go
-rw-rw-r-- 1 chyi chyi 3938 9월 30 15:08 error.go
-rw-rw-r-- 1 chyi chyi 3671 9월 30 15:08 packethandle.go
📌 (궁극적으로는) go.mod 파일을 수정하여 자동으로 최신 버젼을 내려 받아 build할 수 있어야 한다.
수정 작업 후, 다시 build해 보면 다음과 같다.
$ make
go build -v -o "wireguard-go-vpp"
$ ls -l wireguard-go-vpp
-rwxrwxr-x 1 chyi chyi 10762200 10월 2 12:22 wireguard-go-vpp
최종 build된 파일인 wireguard-go-vpp 파일을 target 장비로 복사하여 돌려 보도록 하자.
그런데, 본격적인 wireguard-go-vpp 설정에 들어가기에 앞서, vpp memif library가 하는 역할을 파악해 보는 것이 순서일 것 같다.
5.2 vpp memif interface example 소개
vpp는 물론이고, govpp project에는 libmemif 관련 예제 코드가 준비되어 있다.
<vpp에서 제공하는 예제>
vpp/extras/libmemif/examples$ ls -la
drwxrwxr-x 2 chyi chyi 4096 9월 30 21:49 icmp_responder
drwxrwxr-x 2 chyi chyi 4096 9월 28 15:08 loopback
drwxrwxr-x 2 chyi chyi 4096 9월 29 21:48 test_app
<govpp에서 제공하는 예제>
govpp/extras/gomemif/examples$ ls -la
drwxrwxr-x 2 chyi chyi 4096 10월 1 12:07 bridge
drwxrwxr-x 2 chyi chyi 4096 10월 1 12:07 icmp_responder_cb
drwxrwxr-x 2 chyi chyi 4096 10월 1 15:45 icmp_responder_poll
govpp/extras/libmemif/examples$ ls -la
drwxrwxr-x 2 chyi chyi 4096 10월 1 12:07 gopacket
drwxrwxr-x 2 chyi chyi 4096 10월 1 12:07 icmp-responder
drwxrwxr-x 2 chyi chyi 4096 10월 1 12:07 jumbo-frames
drwxrwxr-x 2 chyi chyi 4096 10월 1 12:07 raw-data
이 중, icmp_responder라는 예제 program을 하나 돌려 보기로 하자. 자세한 설명은 하지 않겠지만, memif interface를 사용하기 위해서는 vpp에서 해줘야 할 일(master mode memif 생성)과 application에서 해야할 일(slave mode memif 생성, packet read/write 처리)이 있다는 점을 쉽게 알 수가 있다.
<Target 장비>
vpp# create interface memif id 0 master
vpp# set int state memif0/0 up
vpp# set int ip address memif0/0 172.16.1.1/24
vpp# show memif
sockets
id listener filename
0 yes (1) /run/vpp/memif.sock
interface memif0/0
remote-name "icmp_responder_example"
remote-interface "libmemif0"
socket-id 0 id 0 mode ethernet
flags admin-up connected
listener-fd 44 conn-fd 45
num-s2m-rings 1 num-m2s-rings 1 buffer-size 0 num-regions 2
region 0 size 33024 fd 46
region 1 size 4194304 fd 47
master-to-slave ring 0:
region 0 offset 16512 ring-size 1024 int-fd 49
head 1029 tail 5 flags 0x0000 interrupts 5
slave-to-master ring 0:
region 0 offset 0 ring-size 1024 int-fd 48
head 5 tail 5 flags 0x0001 interrupts 0
sockets
id listener filename
0 yes (1) /run/vpp/memif.sock
interface memif0/0
remote-name "icmp_responder_example"
remote-interface "libmemif0"
socket-id 0 id 0 mode ethernet
flags admin-up connected
listener-fd 44 conn-fd 45
num-s2m-rings 1 num-m2s-rings 1 buffer-size 0 num-regions 2
region 0 size 33024 fd 46
region 1 size 4194304 fd 47
master-to-slave ring 0:
region 0 offset 16512 ring-size 1024 int-fd 49
head 1029 tail 5 flags 0x0000 interrupts 5
slave-to-master ring 0:
region 0 offset 0 ring-size 1024 int-fd 48
head 5 tail 5 flags 0x0001 interrupts 0
<Target 장비 - linux host 상태>
$ sudo ./icmp_responder -a 172.16.1.2 -r slave
INFO: memif connected!
MEMIF DETAILS
==============================
interface name: libmemif0
app name: icmp_responder_example
remote interface name: memif0/0
remote app name: VPP 25.10-rc1~0-g4f366b5bb
id: 0
secret: (null)
role: slave
mode: ethernet
socket path: /run/vpp/memif.sock
rx queues:
queue id: 0
ring size: 1024
buffer size: 2048
tx queues:
queue id: 0
ring size: 1024
buffer size: 2048
link: up
INFO: memif connected!
MEMIF DETAILS
==============================
interface name: libmemif0
app name: icmp_responder_example
remote interface name: memif0/0
remote app name: VPP 25.10-rc1~0-g4f366b5bb
id: 0
secret: (null)
role: slave
mode: ethernet
socket path: /run/vpp/memif.sock
rx queues:
queue id: 0
ring size: 1024
buffer size: 2048
tx queues:
queue id: 0
ring size: 1024
buffer size: 2048
link: up
이 상태에서 vpp(172.16.1.1)로 부터 icmp_responder application(172.16.1.2)으로 ping을 해보면, 다음과 같이 정상적으로 ping이 된다.
vpp# ping 172.16.1.2
116 bytes from 172.16.1.2: icmp_seq=1 ttl=64 time=.0268 ms
116 bytes from 172.16.1.2: icmp_seq=2 ttl=64 time=.0591 ms
116 bytes from 172.16.1.2: icmp_seq=3 ttl=64 time=.0401 ms
116 bytes from 172.16.1.2: icmp_seq=4 ttl=64 time=.0551 ms
116 bytes from 172.16.1.2: icmp_seq=5 ttl=64 time=.0417 ms
Statistics: 5 sent, 5 received, 0% packet loss
116 bytes from 172.16.1.2: icmp_seq=1 ttl=64 time=.0268 ms
116 bytes from 172.16.1.2: icmp_seq=2 ttl=64 time=.0591 ms
116 bytes from 172.16.1.2: icmp_seq=3 ttl=64 time=.0401 ms
116 bytes from 172.16.1.2: icmp_seq=4 ttl=64 time=.0551 ms
116 bytes from 172.16.1.2: icmp_seq=5 ttl=64 time=.0417 ms
Statistics: 5 sent, 5 received, 0% packet loss
아, 그런데, memif interface를 이용하면 vpp 자체 <-> application간의 통신이 가능하다는 것은 알겠는데, LAN or WAN side에 있는 외부 장치로 부터 유입된 packet이 자연스럽게 memif interface 까지 도달하려면 어떻게 해야 할까 ?
5.3 wireguard-go와 vpp를 연결하기 전 고려 사항
Wireguard-go와 vpp를 연결하기 위해 고민되는 부분이 크게 2가지가 있다. ✌
1) wireguard-go의 어디를 수정하여 memif interface 코드(packet read/write code)를 넣을 것인가 ?
2) LAN or WAN side에서 들어온 패킷이 memif interface 까지 자동 전달되게 하려면 어찌해야 할까 ?
[1] wireguard-go의 어디를 수정하여 memif interface 코드를 넣을 것인가 ?
이를 위해서는 우선 wireguard-go의 동작 원리를 이해할 필요가 있다.
[그림 5.4] wireguard-go architecture - proxy 방식
<wireguard-go의 동작 원리>
1) (2분 간격으로 주기적으로) NoiseIK handshaking 과정을 통해 peer와 shared secret을 생성 및 공유한다.
2) linux kernel에서 제공하는 TUN interface를 하나 생성한다.
3) 먼저, (LAN port에서 routing 과정을 거쳐) TUN interface로 들어온 패킷을 읽은 후, 암호 처리(Tunneling 처리) 과정을 거친 후, wan interface로 해당 패킷을 내보낸다.
-> Read from TUN and Write to WAN
4) 한편, WAN interface로 들어온 암호화된 패킷(wireguard packet - UDP 51820 port 사용)은 tcp/ip stack을 타고, wireguard-go daemon으로 전달된다(packet read). 이후, wireguard-go daemon으로 전달된 패킷을 복호화(Tunnel 제거) 한 후, TUN interface로 내보낸다.
-> Read from WAN and Write to TUN
5) 끝으로, TUN interface로 전달된 packet은 routing 과정을 거쳐 LAN port를 통해 외부로 나가게 된다.
📌 정말이지 이 내용은 백만번도 더 언급했던 것 같다. 😋
따라서, 위의 내용대로라면, wireguard-go는 2개의 지점을 수정해야 할 것처럼 보인다. 즉,
1) TUN interface를 생성한 후, 이를 통해 packet을 read 및 write하는 부분
2) WAN interface를 통해 들어온 패킷을 read하거나 WAN interface로 패킷을 write하는 부분
이는 memif 관점에서 보면, 2개의 memif port를 만든 후, 각각을 TUN 및 WAN port 대용으로 사용해야 한다는 뜻이기도 하다.
[그림 5.5] vpp + wiregurd-go 정합 시 고려 사항(1) - 암호화 과정
[그림 5.6] vpp + wiregurd-go 정합 시 고려 사항(2) - 복호화 과정
[2] LAN or WAN port에서 들어온 패킷이 memif interface 까지 전달되게 하려면 어떻게 해야 할까 ?
정답은 bridge interface를 활용하는 것이다. 즉, 아래 그림과 같이 LAN 및 WAN 용으로 2개의 bridge interface를 생성하면 위 문제가 자연스럽게 해결된다.
[그림 5.7] vpp + wiregurd-go 정합 시 고려 사항(3) - 2개의 bridge interface 추가
<LAN bridge> bvi0 = LAN port + memif0/0
<WAN bridge> bvi1 = WAN port + memif0/1
Bridge interface로 묶여진 interface(port) 간에는 packet이 공유되는 특성(broadcast)이 있으므로, LAN or WAN port로 유입된 패킷은 각각 memif0/0 및 memif0/1 interface까지 (자연스럽게) 전달되게 된다. 이후 wireguard-go application에서는 polling이나 interrupt 방식에 의거하여 해당 packet을 읽어드리기만 하면 된다.
5.4 wireguard-go-vpp 프로젝트 관련 설정하기
지금까지 설명한 내용을 토대로, 본격적으로 wireguard-go-vpp 설정에 돌입해 보도록 하자. 한가지 특이한 점은 wireguard-go-vpp code를 들여다 보니, 당초 예상과는 달리 신기하게도 tun/tun_linux.go code만을 수정했다는 점이다. 이는 TUN interface 관련 코드에 대해서는 memif code로 교체하였으나, WAN interface 관련 read/write code 부분은 기존 코드를 그대로 사용한다는 뜻을 의미한다. 그렇다면 도대체 이것이 어떻게 가능한 것일까 ? 😁
우리는 앞서 아래와 같이 lcp plugin을 이용해 host interface를 생성하는 설정을 한 바 있다.
vpp# lcp create TwoDotFiveGigabitEtherneta/0/0 host-if eth7
$ ifconfig eth7
eth7: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 9000
inet 192.168.1.254 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::4e4b:f9ff:fe45:1fff prefixlen 64 scopeid 0x20<link>
ether 4c:4b:f9:45:1f:ff txqueuelen 1000 (Ethernet)
RX packets 3823 bytes 564864 (564.8 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 4992 bytes 2608096 (2.6 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
_________________________________________
eth7: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 9000
inet 192.168.1.254 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::4e4b:f9ff:fe45:1fff prefixlen 64 scopeid 0x20<link>
ether 4c:4b:f9:45:1f:ff txqueuelen 1000 (Ethernet)
RX packets 3823 bytes 564864 (564.8 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 4992 bytes 2608096 (2.6 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
_________________________________________
이미 알고 있는 것 처럼, 위의 명령을 수행하게 되면, vpp 쪽에는 tapXXX interface가, linux host 쪽에는 eth7 interface가 자동으로 만들어지게 된다. 한편, wireguard-go에서 linux host 상으로 암호화된 패킷을 내보낼 경우, wan ip를 갖는 eth7이 wan port에 해당하므로, 이를 통해 패킷이 외부로 나가게 된다. 그런데 이때, wireguard-go -> eth7으로 전달된 (암호화된) packet은 linux tcp/ip stack을 타고 외부로 나가는 것이 아니라, eth7이 VPP wan port에 bridging 되어 있으므로, DPDK를 타고 외부로 나가게 되는 것으로 이해하면 된다.
📌 eth7 interface는 VPP에서 만든 tap interface(ex: tap4097)에 해당한다. 한편, WAN port와 tap4097 interface는 (lcp create 명령에 의해) 별도로 설정하지 않았지만 내부적으로 bridge 구성을 하게 된다.
아래 그림을 보라~ 방금 설명한 부분이 이해가 되는가 ? 😋
[그림 5.9] wireguard-go-vpp 암호화 & 터널링 처리 흐름
[그림 5.10] wireguard-go-vpp 복호화 & 터널 제거 처리 흐름
<Target 장비>
comment {LAN}
vpp# set int state TwoDotFiveGigabitEtherneta/0/0 up
comment {WAN}
vpp# set int state TwoDotFiveGigabitEthernet11/0/0 up
vpp# set int ip address TwoDotFiveGigabitEthernet11/0/0 192.168.1.254/24
vpp# ip route add 0.0.0.0/0 via 192.168.1.1 TwoDotFiveGigabitEthernet11/0/0
vpp# lcp create TwoDotFiveGigabitEtherneta/0/0 host-if eth0
vpp# lcp create TwoDotFiveGigabitEthernet11/0/0 host-if eth7
vpp# lcp lcp-sync on
comment {loop42 bridge interface}
vpp# create loopback interface mac 42:42:42:42:42:42 instance 42
vpp# set int l2 bridge loop42 4242 bvi
vpp# set interface mtu packet 1500 loop42
vpp# set int state loop42 up
vpp# set int state TwoDotFiveGigabitEtherneta/0/0 up
comment {WAN}
vpp# set int state TwoDotFiveGigabitEthernet11/0/0 up
vpp# set int ip address TwoDotFiveGigabitEthernet11/0/0 192.168.1.254/24
vpp# ip route add 0.0.0.0/0 via 192.168.1.1 TwoDotFiveGigabitEthernet11/0/0
vpp# lcp create TwoDotFiveGigabitEtherneta/0/0 host-if eth0
vpp# lcp create TwoDotFiveGigabitEthernet11/0/0 host-if eth7
vpp# lcp lcp-sync on
comment {loop42 bridge interface}
vpp# create loopback interface mac 42:42:42:42:42:42 instance 42
vpp# set int l2 bridge loop42 4242 bvi
vpp# set interface mtu packet 1500 loop42
vpp# set int state loop42 up
-> loop42라는 bridge interface를 하나 만든다.
vpp# set int l2 bridge TwoDotFiveGigabitEtherneta/0/0 4242
vpp# set int l2 bridge tap4096 4242
vpp# set interface ip address loop42 192.168.5.254/24
-> loop42라는 bridge interface에 LAN port를 포함시켜 준다.
-> wireguard-go-vpp code에서는 memif0/0 interface를 자동 생성한 후, loop42 bridge 내에 합류시켜 준다.
vpp# ip route add 10.1.1.0/24 via 10.1.1.100 loop42
-> vpn peer와의 vpn 통신을 위해서는 반드시 이러한 형태의 routing entry를 추가해 줘야 한다.
<Target 장비 Linux host 상태>
$ sudo vi /etc/wggo-vpp/if/wg0.json
-> wireguard-go-vpp project site를 참조하여 아래 파일을 하나 만든다.
{
"uid": 3, //memif3/3 port 생성 시 사용
"secret": "some_secret",
"IPv4ArpResponseRanges": [
"10.1.1.100/32" //vpn ip 설정
],
"IPv6NdpNeighAdvRanges": [
"fd28:cb8f:4c92::33/128","fe80::42:1817:1/128"
],
"GratuitousARPOnStartUP": false,
"IPv4ArpLearningRanges": [ ],
"IPv6NdpLearningRanges": [ ],
"VppBridgeID": 4242 //loop42 bridge id
}
"uid": 3, //memif3/3 port 생성 시 사용
"secret": "some_secret",
"IPv4ArpResponseRanges": [
"10.1.1.100/32" //vpn ip 설정
],
"IPv6NdpNeighAdvRanges": [
"fd28:cb8f:4c92::33/128","fe80::42:1817:1/128"
],
"GratuitousARPOnStartUP": false,
"IPv4ArpLearningRanges": [ ],
"IPv6NdpLearningRanges": [ ],
"VppBridgeID": 4242 //loop42 bridge id
}
~
$ sudo vi /etc/wggo-vpp/gw/4242.json
-> 또한, 아래 파일도 만든다.
{
"GatewayMacAddr":"42:42:42:42:42:42",
"WgIfMacaddrPrefix":"98:D2:93",
"VppIfMacaddrPrefix":"A4:77:33",
"VppBridgeLoop_InstallMethod":"api",
"VppBridgeLoop_CheckRouteConflict":true,
"VppBridgeLoop_CheckRouteConfigPaths":[
"/etc/wggo-vpp"
],
"VppBridgeLoop_VppctlBin":"vppctl",
"VppBridgeLoop_SwIfName":"loop42",
"VppBridgeLoop_SwIfIndex":8, //loop42 swifindex
"VppBridgeLoop_InstallNeighbor":{
"IPv4":true,
"IPv6":false,
"IPv6 link-local":false
},
"VppBridgeLoop_InstallNeighbor_Flag":{
"static":false,
"no-fib-entry":false
},
"VppBridgeLoop_InstallRoutes":{
"IPv4":true,
"IPv6":false,
"IPv6 link-local":true
}
}
"GatewayMacAddr":"42:42:42:42:42:42",
"WgIfMacaddrPrefix":"98:D2:93",
"VppIfMacaddrPrefix":"A4:77:33",
"VppBridgeLoop_InstallMethod":"api",
"VppBridgeLoop_CheckRouteConflict":true,
"VppBridgeLoop_CheckRouteConfigPaths":[
"/etc/wggo-vpp"
],
"VppBridgeLoop_VppctlBin":"vppctl",
"VppBridgeLoop_SwIfName":"loop42",
"VppBridgeLoop_SwIfIndex":8, //loop42 swifindex
"VppBridgeLoop_InstallNeighbor":{
"IPv4":true,
"IPv6":false,
"IPv6 link-local":false
},
"VppBridgeLoop_InstallNeighbor_Flag":{
"static":false,
"no-fib-entry":false
},
"VppBridgeLoop_InstallRoutes":{
"IPv4":true,
"IPv6":false,
"IPv6 link-local":true
}
}
~
-> 이 2개 파일 설정 부분이 다소 난해할 수 있는데, wireguard-go-vpp daemon에서 이를 사용한다.
$ sudo rm -f /var/run/wggo-vpp/wg0.sock
-> wireguard-go-vpp 재 구동시 이 파일이 남아 있다면 지워야 한다.
$ sudo ./wireguard-go-vpp -f wg0
-> wireguard-go-vpp daemon을 foreground로 구동시킨다.
$ sudo wg set wg0 listen-port 51820 private-key /home/chyi/workspace/wg/privatekey peer HMRtHJAw9doVvryBWKCqz2cd3ErCw1IavlStkv/PyjE= allowed-ips 0.0.0.0/0 endpoint 192.168.8.169:51820
-> wireguard plugin 대신 wireguard-go를 사용할 경우, peer 설정(삭제, 제거)이 자유로은 장점이 있다.
-> 즉, wireguard plugin의 경우 기존에 추가되었던 peer entry를 peer의 publickey로 제거할 방법이 없다(오직 index 값만을 사용할 수 있다).
$ sudo route add default gw 192.168.1.1
-> linux host에서도 default gw 지정이 반드시 필요하다. lcp create로는 이부분이 자동 설정되지는 않는다.
이 상태에서 wireguard-go-vpp의 설정 정보를 확인해 보기로 하자.
$ sudo wg show wg0
또한, 지금까지 설정한 vpp 설정 내용을 확인해 보면 다음과 같다.
📌 memif3/3은 wireguard-go-vpp에 의해 자동으로 생성된 것이다.
vpp# show int addr
$ ps aux|grep vpp
root 1893 99.8 1.1 153021548 183912 ? RLsl 06:25 77:39 /usr/bin/vpp -c /etc/vpp/startup.conf
root 1913 100 0.1 3538124 23680 ? Rl 06:26 77:25 ./wireguard-go-vpp -f wg0
root 1893 99.8 1.1 153021548 183912 ? RLsl 06:25 77:39 /usr/bin/vpp -c /etc/vpp/startup.conf
root 1913 100 0.1 3538124 23680 ? Rl 06:26 77:25 ./wireguard-go-vpp -f wg0
_______________________
끝으로, 내부망 PC에서 peer로 ping 연결을 시도해 보도록 한다.
<내부망 PC에서 Wireguard windows로 ping test>
$ ping 10.1.1.200
PING 10.1.1.200 (10.1.1.200) 56(84) bytes of data.
64 bytes from 10.1.1.200: icmp_seq=35 ttl=127 time=2.59 ms
64 bytes from 10.1.1.200: icmp_seq=36 ttl=127 time=4.03 ms
64 bytes from 10.1.1.200: icmp_seq=37 ttl=127 time=3.48 ms
또한, 이 상태에서 wireguard 통신이 제대로 진행되고 있는 지, tcpdump로 확인해 보면 다음과 같다.
$ sudo tcpdump -i eth7
<Wireguard windows에서 내부망 PC로 ping test>
이번에는 반대 방향 즉, 외부망 Windows PC에서 wireguard 설정을 진행한 후, ping test를 진행하도록 하자.
C:\> ping 192.168.5.10 -t
[그림 5.17] 외부망 Windows PC -> 내부망 Linux PC(192.168.5.10)로 ping하는 모습
📌 [주의 사항] 외부망 Windows PC(vpnip : 10.1.1.200)에서 wireguard-go vpnip 10.1.1.100으로의 ping은 안된다.끝으로, 이번 장에서 설명한 방법의 단점을 정리해 보면 다음과 같다.
1) wireguard-go-vpp를 server mode에서는 사용할 수가 없다.
2) WAN port로의 통신 부분에 memif가 사용되지 않으므로, 이 부분에서 병목이 있을 수 있다.
-> 어쩌면 아닐 수도 있다. 즉, tap interface를 사용한 방식이 생각 보다 빠를 수도 있다.
3) vpp & wireguard-go 설정 외에 wg0.json, 4242.json 등 추가 설정이 다소 복잡하다. 설정 내용을 자동화할 필요가 있다.
4) wireguard-go 최신 버젼을 사용하지 않는다.
📌 이렇게 볼 때, 이 방법이 vpp wireguard plugin 방법에 비해 우수하다고 말할 수 있을까 ?
6. VPP와 WireGuard-Go 연동하기#3 - 개선 방향
이번 장에서는 5장에서 소개한 wireguard-go-vpp 방식의 문제점을 개선하는 내용에 관하여 소개해 보고자 한다.
<개선 Point>
1) memif interface 2개 사용하도록 개선 - 병목 현상 제거 목적
2) wireguard-go 최신 버젼 반영
3) 기타 운영적인 측면에서의 개선 사항 반영
이 밖에도 (필자가 준비 중인) 상용화 작업을 위해서는 아래 내용이 추가되어야 한다.
<상용화 작업 - TODO>
1) wireguard auto connection 기능 정합
2) vpp 전용 CLI 추가(설정 내용 저장 가능, Cisco style 지원)
3) WebUI 추가
To be continued...
7. References
[1] https://www.alibaba.com/product-detail/BKHD-1U-Rack-Xeon-C612-8_1601404003898.html?spm=a2700.details.you_may_like.3.78b925aeaZmhTi
[2] https://docs.ngkore.com/blogs/dpdk/dpdk.html#hardware-requirements
[3] https://s3-docs.fd.io/vpp/25.10/developer/plugins/wireguard.html
[4] https://github.com/FDio/govpp/tree/master/extras/libmemif
[5] https://s3-docs.fd.io/vpp/25.10/index.html
[6] https://github.com/chili-chips-ba/wireguard-fpga
[7] https://ettrends.etri.re.kr/ettrends/152/0905002032/30-2_87-94.pdf
[9] https://www.alibaba.com/product-detail/Zynshield-2U-OPNsense-8-2-5G_1601471550789.html?fromPlat=buyer_ops&mt=mail&crm_mtn_tracelog_log_id=106387222670&crm_mtn_tracelog_task_id=push-task_110693&crm_mtn_tracelog_template=2000114762&crm_mtn_tracelog_creative_id=32452&as_token=TgKJF9vY%2FDSIs72pWyl91qsxD0JFwpNQb2HfpZkvT9ws7dvf&to=chunghan.yi%40gmail.com&from=news%40notice.alibaba.com
[10] https://media.frnog.org/FRnOG_28/FRnOG_28-3.pdf
[11] https://network.nvidia.com/files/doc-2020/pb-connectx-5-en-card.pdf
[12] https://network.nvidia.com/products/infiniband-drivers/linux/mlnx_ofed/
[13] my postings for vpp
[14] And, Google~
Slowboot