2024년 1월 20일 토요일

DPDK와 FD.io VPP로 고성능 Security Gateway 만들기(두번째 시간)

이번 posting에서는 지난 시간에 이어 open source project인 DPDK와 FD.io VPP(Vector Packet Processing)를 이용하여 고성능 네트워크 장비를 만드는 방법을 소개해 보고자 한다. 이론적인 부분 보다는 실제 예제(네트워크 구성)를 중심으로 VPP를 파헤쳐 보도록 하자. 😎




목차
1. VPP Reloaded~
2. Interface 설정
3. L2 Bridge
4. L3 NAT Router 
5. VPP와 Host App 연동하기
6. ACL 설정하기
7. Load Balancer
8. WireGuard
9. IPSec
10. Snort 기반의 IPS
References
Keyword: VPP(Vector Packet Processing), Intel DPDK, Router, Switch, VPN, IPS, Load Balancer


이번 blog의 목적은 VPP를 이용하여 네트워크 주요 기능(Bridge, Router, NAT, ACL, VPN, IPS, Load Balancer 등)을 상세하게 소개하는 것이다.  Linux kernel을 사용하는 경우와 비교해 어떠한 차이가 있는지 음미해 보기 바란다.


1. VPP Reloaded~
오랫 만에 다시 VPP를 설치해 본다. VPP는 아래 그림에서 보는 것 처럼  On-premise 환경은 기본이고, NFV, Cloud 환경까지 확장성이 아주 많은 녀석(?)이다. 😁 앞으로 vpp를 활용하여 어디까지 나아갈 수 있는지 틈틈히 따져 볼 생각이다.


[그림 1.1] VPP의 응용 분야 [출처: 참고문헌 8]

이번 blog에서 소개하는 대부분의 내용은 아래 VPP documentation page를 토대로 하였다.



먼저 아래 2가지 방법을 이용하여 vpp를 설치해 보도록 하자. 설치 환경은 Ubuntu 22.04 LTS 이다.

1) Source code build 후 설치
$ git clone https://gerrit.fd.io/r/vpp
$ cd vpp
$ make build
make pkg-deb
$ cd build-root
$ ls -l *.deb
-rw-r--r-- 1 chyi chyi   161142 Jan  2 13:13 libvppinfra-dev_24.02-rc0~206-gb7e66f4a3_amd64.deb
-rw-r--r-- 1 chyi chyi   197308 Jan  2 13:13 libvppinfra_24.02-rc0~206-gb7e66f4a3_amd64.deb
-rw-r--r-- 1 chyi chyi    26782 Jan  2 13:13 python3-vpp-api_24.02-rc0~206-gb7e66f4a3_amd64.deb
-rw-r--r-- 1 chyi chyi 95306746 Jan  2 13:13 vpp-dbg_24.02-rc0~206-gb7e66f4a3_amd64.deb
-rw-r--r-- 1 chyi chyi  1346260 Jan  2 13:13 vpp-dev_24.02-rc0~206-gb7e66f4a3_amd64.deb
-rw-r--r-- 1 chyi chyi  5756790 Jan  2 13:13 vpp-plugin-core_24.02-rc0~206-gb7e66f4a3_amd64.deb
-rw-r--r-- 1 chyi chyi   410212 Jan  2 13:13 vpp-plugin-devtools_24.02-rc0~206-gb7e66f4a3_amd64.deb
-rw-r--r-- 1 chyi chyi  5235588 Jan  2 13:13 vpp-plugin-dpdk_24.02-rc0~206-gb7e66f4a3_amd64.deb
-rw-r--r-- 1 chyi chyi  5306706 Jan  2 13:13 vpp_24.02-rc0~206-gb7e66f4a3_amd64.deb

$ sudo dpkg -i ./*.deb
  => 설치 중 에러가 발생한다.
$ sudo apt-get install -y libnl-route-3-dev
$ sudo apt --fix-broken install
$ dpkg -l | grep vpp
  => 모두 9개의 vpp package가 설치되었다.
ii  libvppinfra                           24.02-rc0~206-gb7e66f4a3                amd64        Vector Packet Processing--runtime libraries
ii  libvppinfra-dev                       24.02-rc0~206-gb7e66f4a3                amd64        Vector Packet Processing--runtime libraries
ii  python3-vpp-api                       24.02-rc0~206-gb7e66f4a3                amd64        VPP Python3 API bindings
ii  vpp                                   24.02-rc0~206-gb7e66f4a3                amd64        Vector Packet Processing--executables
ii  vpp-dbg                               24.02-rc0~206-gb7e66f4a3                amd64        Vector Packet Processing--debug symbols
ii  vpp-dev                               24.02-rc0~206-gb7e66f4a3                amd64        Vector Packet Processing--development support
ii  vpp-plugin-core                       24.02-rc0~206-gb7e66f4a3                amd64        Vector Packet Processing--runtime core plugins
ii  vpp-plugin-devtools                   24.02-rc0~206-gb7e66f4a3                amd64        Vector Packet Processing--runtime developer tool plugins
ii  vpp-plugin-dpdk                       24.02-rc0~206-gb7e66f4a3                amd64        Vector Packet Processing--runtime dpdk plugin


<여기서 잠깐>
  => vpp clean build를 하려면 ...
------------------------------------------------------------------------
$ make wipe; make build
or
$ make rebuild

기타 다양한 vpp make option(Makefile 내용)은 다음과 같다.

[그림 1.2] make options
------------------------------------------------------------------------

2) release binary 설치 
$ curl -s https://packagecloud.io/install/repositories/fdio/release/script.deb.sh | sudo bash

$ sudo apt-get update
$ sudo apt-get install vpp vpp-plugin-core vpp-plugin-dpdk
$ sudo apt-get install vpp-api-python python3-vpp-api vpp-dbg vpp-dev
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
E: Unable to locate package vpp-api-python    📌 얘에서 에러가 난다. 일단 얘는 무시하고 나머지 설치 진행하자.

sudo apt-get install python3-vpp-api vpp-dbg vpp-dev

$ sudo dpkg -l | grep vpp
   => 설치된 vpp package를 확인해 본다.
ii  libvppinfra                           23.10-release                           amd64        Vector Packet Processing--runtime libraries
ii  libvppinfra-dev                       23.10-release                           amd64        Vector Packet Processing--runtime libraries
ii  python3-vpp-api                       23.10-release                           amd64        VPP Python3 API bindings
ii  vpp                                   23.10-release                           amd64        Vector Packet Processing--executables
ii  vpp-dbg                               23.10-release                           amd64        Vector Packet Processing--debug symbols
ii  vpp-dev                               23.10-release                           amd64        Vector Packet Processing--development support
ii  vpp-plugin-core                       23.10-release                           amd64        Vector Packet Processing--runtime core plugins
ii  vpp-plugin-dpdk                       23.10-release                           amd64        Vector Packet Processing--runtime dpdk plugin

<여기서 잠깐>
  => 설치한 vpp package를 삭제하고자 한다면 ...

$ sudo apt-get remove --purge "vpp*"
$ sudo dpkg -l | grep vpp
$ sudo apt-get remove libvppinfra
$ sudo dpkg -l | grep vpp
$ sudo apt-get remove libvppinfra-dev
--------------------------------------------------------------------------------------

vpp 설치가 정상적으로 이루어졌으니, 다음으로 해야 할 일은 ethernet port를 DPDK용으로 binding해 주는 것이다. 참고로 이번 시험에 사용된 LAN card는 Intel XL710(quad port)이다.
[그림 1.3] Intel XL710-BM1 Based Ethernet Network Interface Card, 10G Quad-Port SFP+


[그림 1.4] DPDK 구조1 [출처 - 참고문헌 11]

📌 ASIC 이나 FPGA의 힘을 빌리지 않고도 사용자 영역의 s/w만으로 고성능을 낼 수 있는 것은 절대적으로 DPDK의 힘에 기인한다.

$ sudo apt install dpdk
  => dpdk package를 설치한다.
$ sudo /usr/bin/dpdk-devbind.py -s
  => dpdk binding 상태를 확인한다.
Network devices using kernel driver
===================================
0000:3b:00.0 'Ethernet Controller X710 for 10GbE SFP+ 1572' if=enp59s0f0 drv=i40e unused=vfio-pci,uio_pci_generic *Active*
0000:3b:00.1 'Ethernet Controller X710 for 10GbE SFP+ 1572' if=enp59s0f1 drv=i40e unused=vfio-pci,uio_pci_generic 
0000:3b:00.2 'Ethernet Controller X710 for 10GbE SFP+ 1572' if=enp59s0f2 drv=i40e unused=vfio-pci,uio_pci_generic *Active*
0000:3b:00.3 'Ethernet Controller X710 for 10GbE SFP+ 1572' if=enp59s0f3 drv=i40e unused=vfio-pci,uio_pci_generic 
0000:60:00.0 'Ethernet Connection X722 for 1GbE 37d1' if=eno1 drv=i40e unused=vfio-pci,uio_pci_generic 
0000:60:00.1 'Ethernet Connection X722 for 1GbE 37d1' if=eno2 drv=i40e unused=vfio-pci,uio_pci_generic 

No 'Baseband' devices detected
==============================

No 'Crypto' devices detected
============================

DMA devices using kernel driver
===============================
0000:00:04.0 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:00:04.1 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:00:04.2 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:00:04.3 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:00:04.4 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:00:04.5 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:00:04.6 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:00:04.7 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.0 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.1 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.2 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.3 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.4 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.5 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.6 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.7 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 

No 'Eventdev' devices detected
==============================

No 'Mempool' devices detected
=============================

No 'Compress' devices detected
==============================

No 'Misc (rawdev)' devices detected
===================================

No 'Regex' devices detected
===========================

4개의 ethernet port에 대해 dpdk binding을 시도한다.

$ sudo /usr/bin/dpdk-devbind.py --bind uio_pci_generic 3b:00.0
Warning: routing table indicates that interface 0000:3b:00.0 is active. Not modifying

$ sudo /usr/bin/dpdk-devbind.py --bind uio_pci_generic 3b:00.1
Error: bind failed for 0000:3b:00.1 - Cannot bind to driver uio_pci_generic: [Errno 19] No such device
Error: unbind failed for 0000:3b:00.1 - Cannot open /sys/bus/pci/drivers//unbind: [Errno 13] Permission denied: '/sys/bus/pci/drivers//unbind'

$ sudo /usr/bin/dpdk-devbind.py --bind uio_pci_generic 3b:00.2
Warning: routing table indicates that interface 0000:3b:00.2 is active. Not modifying

$ sudo /usr/bin/dpdk-devbind.py --bind uio_pci_generic 3b:00.3
Error: bind failed for 0000:3b:00.3 - Cannot bind to driver uio_pci_generic: [Errno 19] No such device
Error: unbind failed for 0000:3b:00.3 - Cannot open /sys/bus/pci/drivers//unbind: [Errno 13] Permission denied: '/sys/bus/pci/drivers//unbind'

📌 위의 명령을 실행하기 전에 실제 동작 중인 network interface가 있다면, 먼저 down(ifconfig XXX down or ifconfig XXX 0.0.0.0) 시켜 주어야 한다.

$ sudo service vpp restart
  => dpdk binding 내용을 반영하기 위해 vpp를 재구동시킨다.

$ sudo dpdk-devbind.py --status
  => 정상적으로 binding이 이루어진 경우, 아래와 같이 DPDK-compatible driver에 내용이 출력될 것이다. 아래의 경우는 4개의 10GbE port 중 실제로 2 포트만 cable이 연결되어 있어 그렇게 출력된 것이다.

Network devices using DPDK-compatible driver
============================================
0000:3b:00.1 'Ethernet Controller X710 for 10GbE SFP+ 1572' drv=vfio-pci unused=i40e,uio_pci_generic
0000:3b:00.3 'Ethernet Controller X710 for 10GbE SFP+ 1572' drv=vfio-pci unused=i40e,uio_pci_generic

Network devices using kernel driver
===================================
0000:3b:00.0 'Ethernet Controller X710 for 10GbE SFP+ 1572' if=enp59s0f0 drv=i40e unused=vfio-pci,uio_pci_generic *Active*
0000:3b:00.2 'Ethernet Controller X710 for 10GbE SFP+ 1572' if=enp59s0f2 drv=i40e unused=vfio-pci,uio_pci_generic *Active*
0000:60:00.0 'Ethernet Connection X722 for 1GbE 37d1' if=eno1 drv=i40e unused=vfio-pci,uio_pci_generic 
0000:60:00.1 'Ethernet Connection X722 for 1GbE 37d1' if=eno2 drv=i40e unused=vfio-pci,uio_pci_generic 

No 'Baseband' devices detected
==============================

No 'Crypto' devices detected
============================

DMA devices using kernel driver
===============================
0000:00:04.0 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:00:04.1 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:00:04.2 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:00:04.3 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:00:04.4 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:00:04.5 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:00:04.6 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:00:04.7 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.0 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.1 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.2 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.3 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.4 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.5 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.6 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 
0000:80:04.7 'Sky Lake-E CBDMA Registers 2021' drv=ioatdma unused=vfio-pci,uio_pci_generic 

No 'Eventdev' devices detected
==============================

No 'Mempool' devices detected
=============================

No 'Compress' devices detected
==============================

No 'Misc (rawdev)' devices detected
===================================

No 'Regex' devices detected
===========================

[그림 1.5] DPDK binding 예

이제 vpp process 상태와 cpu 사용량을 확인해 본 후, vppctl 명령을 실행하여 cli 모드로 진입해 보자.

$ ps aux|grep vpp
root        5765 72.5  0.1 287622924 123200 ?    Rsl  05:35   0:01 /usr/bin/vpp -c /etc/vpp/startup.conf
ztnabox     5832  0.0  0.0   6476  2392 pts/0    S+   05:35   0:00 grep --color=auto vpp

top 명령으로 확인해 보니, cpu 사용량이 100%를 넘는다.

[그림 1.6] vpp process의 cpu 사용량 - 100% 육박


[그림 1.7] DPDK 구조 2 [출처 - 참고문헌 11]


$ sudo vppctl
    _______    _        _   _____  ___ 
 __/ __/ _ \  (_)__    | | / / _ \/ _ \
 _/ _// // / / / _ \   | |/ / ___/ ___/
 /_/ /____(_)_/\___/   |___/_/  /_/    

vpp# show version 
vpp v23.10-release built by root on 4e2336ec7fb8 at 2023-10-25T14:46:06
  
vpp# show int
              Name               Idx    State  MTU (L3/IP4/IP6/MPLS)     Counter          Count     
TenGigabitEthernet3b/0/1          1     down         9000/0/0/0     
TenGigabitEthernet3b/0/3          2     down         9000/0/0/0     
local0                            0     down          0/0/0/0  

==================

<여기서 잠깐>
  => VPP의 성능을 좀 더 끌어 올리고 싶다면 어찌해야 할까 ?
-------------------------------------------------------------------------------------
답은 간단하다. /etc/vpp/startup.conf 파일의 설정을 아래과 같이 변경해 주면 된다.

</etc/vpp/startup.conf>
...
cpu { 
      main-core 1 
      corelist-workers 2-3,18-19 
}
...
-------------------------------------------------------------------------------------
위와 같이 설정 변경을 할 경우, CPU 사용량이 100% -> 400%로 늘어난 것을 알 수 있다. 그만큼 성능 향상이 있을 수 있음을 의미한다.

[그림 1.8] vpp process의 cpu 사용량 - 400% 육박

현재 테스트 중인 서버의 CPU core 갯수는 총 32개(thread 64)이다. 그렇다면 아래와 같이 32개를 거의 대부분 vpp용으로 할당할해 주면 어떻게 될까 ? 

cpu { 
    main-core 0
    corelist-workers 2-32,33-60
}

[그림 1.9] vpp process의 cpu 사용량 - 1000%를 넘어감

이 상태에서 vppctl show threads 명령을 실행해 보면, 다음과 같이 vpp worker threads가 cpu core별로 할당되어 동작하고 있음을 알 수 있다.

[그림 1.10] vpp show threads

📌 시험을 해 보면 CPU 점유율이 1000%(심지어는 2000%)를 넘어가는 경우가 발생하는 것을 알 수 있다. 다만, 항상 1000% 대를 유지하는 것은 아니고, 500% 대로 줄었다가, CPU  다시 1000% 대로 넘어가는 것을 볼 수가 있다.
-------------------------------------------------------------------------------------

자, 여기까지 vpp 관련 기본 설치가 모두 끝났다. 이제 부터 본격적으로 network 설정으로 들어가 보도록 하자.


2. Interface 설정
VPP 설치 후, network 설정 관련하여 가장 먼저 해야 할 일은 아무래도 interface 설정이 아닐까 싶다. 설정 방법은 간단하다.

vpp# show interface
              Name               Idx    State  MTU (L3/IP4/IP6/MPLS)     Counter          Count     
TenGigabitEthernet3b/0/0          1     down         9000/0/0/0     
TenGigabitEthernet3b/0/2          2     down         9000/0/0/0     
local0                            0     down          0/0/0/0    

vpp# set interface state TenGigabitEthernet3b/0/0 up
  => Interface를 up 시킨다.
vpp# set int ip address TenGigabitEthernet3b/0/0 192.168.10.1/24
  => Interface에 ip 주소를 부여한다.

vpp# set interface state TenGigabitEthernet3b/0/2 up
vpp# set int ip address TenGigabitEthernet3b/0/2 192.168.3.33/24

[그림 2.1] vpp interface 설정 예 - interface 상태 확인

vpp# set interface address
  => interface에 할당된 ip 주소를 확인한다.

[그림 2.2] show int addr 명령 실행 예 - interface 별 ip 할당 상태 확인

다음으로 소개할 내용은 bonding에 관한 것이다. 다행히도 현재 Quad ethernet NIC(그림 1.2)을 사용하고 있는 관계로 LAN 및 WAN 용으로 각각 2개의 port를 할당하고, 2개를 하나로 묶는 bonding 설정을 해 볼 수 있다.

vpp# create bond ?
  create bond                              create bond mode {round-robin | active-backup | broadcast | {lacp | xor} [load-balance { l2 | l23 | l34 } [numa-only]]} [hw-addr <mac-address>] [id <if-id>] [gso]

vpp# create bond mode active-backup
  => active/backup 형태로 bonding 설정을 한다.
BondEthernet0
vpp# bond add BondEthernet0 TenGigabitEthernet3b/0/0
vpp# bond add BondEthernet0 TenGigabitEthernet3b/0/1
vpp# set interface state BondEthernet0 up

vpp# create bond mode active-backup
BondEthernet1
vpp# bond add BondEthernet1 TenGigabitEthernet3b/0/2
vpp# bond add BondEthernet1 TenGigabitEthernet3b/0/3
vpp# set interface state BondEthernet1 up

vpp# show bond 
interface name   sw_if_index  mode          load balance  active members members
BondEthernet0    5            active-backup active-backup 1              2
BondEthernet1    6            active-backup active-backup 1              2

vpp# show interfaces

[그림 2.3] vpp interface 설정 예 - bonding interface 추가 


vpp# show int addr
BondEthernet0 (up):
  L2 bridge bd-id 1 idx 1 shg 0  
BondEthernet1 (up):
  L3 192.168.3.33/24
TenGigabitEthernet3b/0/0 (up):
TenGigabitEthernet3b/0/1 (dn):
TenGigabitEthernet3b/0/2 (up):
TenGigabitEthernet3b/0/3 (dn):
bvi0 (up):
  L2 bridge bd-id 1 idx 1 shg 0 bvi
  L3 192.168.10.1/24
local0 (dn):
vpp# 

vpp# ping 192.168.3.254
  => wan에 존재하는 다른 장치(192.168.3.254)로 ping을 해 본다.
116 bytes from 192.168.3.254: icmp_seq=1 ttl=64 time=.1512 ms
116 bytes from 192.168.3.254: icmp_seq=2 ttl=64 time=.1249 ms
116 bytes from 192.168.3.254: icmp_seq=3 ttl=64 time=.1393 ms
116 bytes from 192.168.3.254: icmp_seq=4 ttl=64 time=.1115 ms
116 bytes from 192.168.3.254: icmp_seq=5 ttl=64 time=.1246 ms

Statistics: 5 sent, 5 received, 0% packet loss



3. L2 Bridge
Layer 2 bridging과 Layer 3 routing은 네트워크의 기본에 해당하는 내용이다. 먼저 이 번 장에서는 Layer 2 bridge 설정 기능을 소개해 보도록 하자.


    +------------------+
    | 192.168.10.0/24 |
    +------------------+
    |
+----------------------------+
| TenGigabitEthernet3b/0/0 |
+----------------------------+
+----------------------------+
| TenGigabitEthernet3b/0/2 |
+----------------------------+
    |
    +------------------+
    | 192.168.10.0/24 |
    +------------------+

[그림 3.1] L2 Bridge 네트워크 구성


vpp# set interface state GigabitEthernet3/0/0 up
vpp# set interface state GigabitEthernet2/0/0 up
 => WAN, LAN interface를 up 시킨다.
vpp# create bridge-domain 1
 => l2 bridge domain(1번 domain)을 하나 생성한다.
vpp# set interface l2 bridge GigabitEthernet3/0/0 1
vpp# set interface l2 bridge GigabitEthernet2/0/0 1
 => l2 bridge domain 1에 WAN, LAN interface를 포함시킨다.

vpp# show l2fib bd_id 1        
  => 1번 bridge domain에 대한 l2 fib(forwarding information base) table(흔히 mac table이라고 부름)내용을 확인한다. 주의: 실제로는 GigabitEthernet3/0/0, GigabitEthernet2/0/0 정보가 보여야 하나, 나중에 정리하다 보니, 다른 내용이 보인다. 그냥 무시하기 바란다.
Mac-Address     BD-Idx If-Idx BSN-ISN Age(min) static filter bvi         Interface-Name        
 50:7c:6f:3a:a8:60    1      1      0/1      -       -      -     -     TenGigabitEthernet3b/0/0   
 24:5e:be:80:bc:6d    1      1      0/1      -       -      -     -     TenGigabitEthernet3b/0/0   
 02:fe:af:c2:00:0b    1      6      0/1      -       -      -     -               tap0                 #나중에 추가한 내용이라 이런게 들어가 있음. 무시하세요.
 b0:b0:00:00:00:00    1      5      0/0      no      *      -     *               bvi0             
 dc:a6:32:7f:6d:08    1      1      0/1      -       -      -     -     TenGigabitEthernet3b/0/0   
 94:83:c4:0c:01:9a    1      1      0/1      -       -      -     -     TenGigabitEthernet3b/0/0   
L2FIB total/learned entries: 15/12  Last scan time: 0.0000e0sec  Learn limit: 16777216 

<Windows PC>
C:\> ping 8.8.8.8
  => 내부 PC에서 internet으로 ping을 시도해 본다.
116 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=30.5226 ms
116 bytes from 8.8.8.8: icmp_seq=2 ttl=116 time=30.4559 ms
116 bytes from 8.8.8.8: icmp_seq=3 ttl=116 time=30.4174 ms

📌 l2 bridge 관련해서는 사실 이것 보다 훨씬 복잡한 내용이 포함되어 있으나, 일단 여기에서는 이정도 선에서 pass하기로 한다. 추가로 필요한 사항은 나중에 좀 더 살펴 보기로 하자.




4. L3 NAT Router 
이 장에서는 L3 router 설정 기능(기본 NAT 설정 포함)을 소개할 차례이다.


    +----------------------+
    | 192.168.10.0/24 |
    +----------------------+
    |
+------------------------------------+
| TenGigabitEthernet3b/0/0 |
+------------------------------------+
+------------------------------------+
| TenGigabitEthernet3b/0/2 |
+------------------------------------+
    |
    +--------------------+
    | 192.168.3.0/24 |
    +--------------------+

[그림 4.1] L3 Router 네트워크 구성

vpp# bvi create instance 0
vpp# set int l2 bridge bvi0 1 bvi
vpp# set int ip address bvi0 192.168.10.1/24
vpp# set int state bvi0 up
  => bridge virtual interface를 하나 만들고, bridge domain 1에 포함시킨다. 이어  ip 주소를 할당한다.

vpp# set int l2 bridge TenGigabitEthernet3b/0/0 1
vpp# set int state TenGigabitEthernet3b/0/0 up
  => 물리 LAN port(TenGigabitEthernet3b/0/0)를 bridge domain 1에 포함시킨다.

vpp# set interface state TenGigabitEthernet3b/0/2 up
vpp# set int ip address TenGigabitEthernet3b/0/2 192.168.3.33/24
  => WAN port(TenGigabitEthernet3b/0/2)를 up시키고, ip를 할당한다.

vpp# ip route add 0.0.0.0/0 via 192.168.3.254 TenGigabitEthernet3b/0/2
  => default gateway를 추가한다.
📌 routing table은 show ip fib 명령으로 확인 가능하다.

vpp# nat44 forwarding enable
   => nat44 forwardng을 enable 시킨다.
vpp# nat44 plugin enable sessions 10000
vpp# set interface nat44 in bvi0 out TenGigabitEthernet3b/0/2
  => bvi0를 input으로 하고, WAN port를 output으로 하는 NAT44 rule을 추가한다.
vpp# nat44 add interface address TenGigabitEthernet3b/0/2
 => NAT44 interface로 WAN port를 등록한다.

<Windows PC>
C:\> ping 8.8.8.8
  => 내부 PC에서 internet으로 ping을 시도해 본다.


<여기서 잠깐 - Port Forwarding 설정하기>
==================================
nat44를 이용하여 port forwarding 설정을 하려면 어떻게 해야 할까 ? 답은 간단하다. 아래와 같이 nat44 static mapping 기능을 이용하면 된다.

vpp# nat44 add static mapping tcp local 192.168.10.200 22 external 192.168.3.33 22
  => 외부 PC --> 192.168.3.33:22 -> 192.168.10.200:22
vpp# nat44 add static mapping tcp local 192.168.10.100 22 external 192.168.3.33 2022
  => 외부 PC --> 192.168.3.33:2022 -> 192.168.10.100:22
📌 주의: 보통의 경우는 external ip가 먼저 오고, internal(local) ip 설정이 뒤에 오는게 일반적인데, vpp의 경우는 그렇지 못하다.

<외부 PC>
$ ssh pi@192.168.3.33
pi@192.168.3.33's password: 
Linux raspberrypi 5.15.84-v7l+ #1613 SMP Thu Jan 5 12:01:26 GMT 2023 armv7l
...
==================================

이 밖에도 vpp에는 아주 다양한(모든) NAT 기능이 포함되어 있다. 궁금한 사항은 아래 page를 참고하기 바란다.



5. VPP와 Host App 연동하기
이 장에서 소개할 내용은 Host(Linux) 상의 application을 VPP를 통해 연결하는 것에 관한 것이다. 즉, host 상에서 동작하는 ssh server나 web server를 VPP를 통해 연결하는 방법을 소개하고자 한다. 

[그림 5.1] Host app과 VPP 연동 (내부망에서 접근 예)

📌 이 내용은 switching chip(ASIC)을 사용하는 경우, switching chip과 CPU를 연결하는 cpu trap 의 개념과 유사하다고도 볼 수 있다.

<VPP>
comment { This is the LAN bridge interface }
comment { LAN bridge = bvi0 + }
vpp# bvi create instance 0
vpp# set int l2 bridge bvi0 1 bvi
vpp# set int ip address bvi0 192.168.10.1/24
vpp# set int state bvi0 up
  => bridge virtual interface를 하나 만들고, bridge domain 1에 포함시킨다. 이어  ip 주소를 할당한다.

comment { LAN bridge = bvi0 + TenGigabitEthernet3b/0/0 }
vpp# set int l2 bridge TenGigabitEthernet3b/0/0 1
vpp# set int state TenGigabitEthernet3b/0/0 up
  => 물리 LAN port를 bridge 1에 포함시킨다.

comment { LAN bridge = bvi0 + TenGigabitEthernet3b/0/0 + tap0 }
vpp# create tap host-if-name lantap host-ip4-addr 192.168.10.2/24 host-ip4-gw 192.168.10.1
vpp# set int l2 bridge tap0 1
vpp# set int state tap0 up
  => tap0 interface를 하나 만들고 bridge 1에 포함시킨다. 이때 Linux host 상에는 lantap 이라는 tap interface가 생성된다.

comment { This is the WAN interface }
vpp# set interface state TenGigabitEthernet3b/0/2 up
vpp# set int ip address TenGigabitEthernet3b/0/2 192.168.3.33/24
  => WAN port를 up시키고, ip를 할당한다.

comment { Add default gateway }
vpp# ip route add 0.0.0.0/0 via 192.168.3.254 TenGigabitEthernet3b/0/2
  => default gateway를 추가한다.

comment { Add nat44 rules }
vpp# nat44 forwarding enable
   => nat44 forwarding을 enable 시킨다.
vpp# nat44 plugin enable sessions 10000
vpp# set interface nat44 in bvi0 out TenGigabitEthernet3b/0/2
  => bvi0를 input으로 하고, WAN port를 output으로 하는 NAT44 rule을 추가한다.
vpp# nat44 add interface address TenGigabitEthernet3b/0/2
 => NAT44 interface로 WAN port를 등록한다.


<Linux Host>
$ ifconfig -a
...
lantap: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.10.2  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::fe:97ff:fe0c:43fb  prefixlen 64  scopeid 0x20<link>
        ether 02:fe:97:0c:43:fb  txqueuelen 1000  (Ethernet)
        RX packets 12  bytes 594 (594.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 18  bytes 1180 (1.1 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0


<Windows PC>
C\> ping 192.168.10.2
116 bytes from 192.168.10.2: icmp_seq=1 ttl=64 time=.1005 ms
116 bytes from 192.168.10.2: icmp_seq=2 ttl=64 time=.0934 ms
Connection to 10.30.0.100 closed.

C\> ssh test@192.168.10.2
...

C\> ping 8.8.8.8
116 bytes from 8.8.8.8: icmp_seq=1 ttl=64 time=.1005 ms
116 bytes from 8.8.8.8: icmp_seq=2 ttl=64 time=.0934 ms
Connection to 10.30.0.100 closed.

📌 vpp 환경에서는 bvi0 interface 즉, 192.168.10.1로 ssh 연결을 시도할 경우, 동작하지 않는다. 이유는 vpp application 자체에는 ssh server가 포함(plugin)되어 있지 않기 때문이다. 따라서 이런 경우를 위해 tap interface가 사용되는 것이다.

지금까지 내부망에서 VPP host(Host 상의 application)에 접근하는 설정에 대해서 살펴 보았다. 그렇다면 만일 외부망에서 Host 상의 application에 접근하려면 어찌해야 할까 ? 

[그림 5.2] Host app과 VPP 연동 (외부망에서 접근 예)

답은 동일하다. 아래 설정을 참조하기 바란다(WAN 이후 부분만 정리함).

<VPP>
comment { This is the WAN interface }
comment { WAN bridge = bvi1 + }
vpp# bvi create instance 1
vpp# set int l2 bridge bvi1 2 bvi
vpp# set int ip address bvi1 192.168.3.33/24
vpp# set int state bvi1 up
  => bridge virtual interface를 하나 만들고, bridge domain 2에 포함시킨다. 이어  ip 주소(wan ip 주소)를 할당한다.

comment { WAN bridge = bvi1 + TenGigabitEthernet3b/0/2 }
vpp# set int l2 bridge TenGigabitEthernet3b/0/2 2
vpp# set interface state TenGigabitEthernet3b/0/2 up
  => 물리 WAN port를 bridge 2에 포함시킨다.

comment { WAN bridge = bvi1 + TenGigabitEthernet3b/0/2 + tap1 }
comment { create tap host-if-name wantap host-ip4-addr 192.168.3.51/24 host-ip4-gw 192.168.3.33 }
vpp# create tap host-if-name wantap host-ip4-addr 192.168.3.51/24 host-ip4-gw 192.168.3.33
vpp# set int l2 bridge tap1 2
vpp# set int state tap1 up
  => tap1 interface를 하나 만들고 bridge 2에 포함시킨다. 이때 Linux host 상에는 wantap 이라는 tap interface가 생성된다.

comment { Add default gateway }
comment { ip route add 0.0.0.0/0 via 192.168.3.254 bvi1 }
vpp# ip route add 0.0.0.0/0 via 192.168.3.254 bvi1
  => default gateway를 추가한다. 단, 이때 대표 인터페이스는 TenGigabitEthernet3b/0/2가 아니라 bridge interface bvi1으로 한다.

comment { Add nat44 rules }
vpp# nat44 forwarding enable
   => nat44 forwarding을 enable 시킨다.
vpp# nat44 plugin enable sessions 10000
comment { set interface nat44 in bvi0 out bvi1 }
comment { nat44 add interface address bvi1 }
vpp# set interface nat44 in bvi0 out bvi1
=> bvi0를 input으로 하고, bvi1을 output으로 하는 NAT44 rule을 추가한다.
vpp# nat44 add interface address bvi1
 => NAT44 interface로 WAN bridge interface bvi1을 등록한다.


<Linux Host>
ifconfig -a
...
wantap: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.3.51  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::fe:ddff:fe57:a101  prefixlen 64  scopeid 0x20<link>
        ether 02:fe:dd:57:a1:01  txqueuelen 1000  (Ethernet)
        RX packets 2025  bytes 267992 (267.9 KB)
        RX errors 0  dropped 51  overruns 0  frame 0
        TX packets 1691  bytes 158942 (158.9 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

<Windows PC>
C\> ping 192.168.3.51
PING 192.168.3.51 (192.168.3.51): 56 data bytes
64 bytes from 192.168.3.51: seq=0 ttl=64 time=0.258 ms
64 bytes from 192.168.3.51: seq=1 ttl=64 time=0.169 ms
64 bytes from 192.168.3.51: seq=2 ttl=64 time=0.239 ms
^C
--- 192.168.3.51 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.169/0.222/0.258 ms

만일, 외부망의 Windows PC에서 wantap interface로 들어오는 packet을 차단하고 싶다면, 어찌해야 할까 ? 이 부분은 6장의 주제이기도 한데, 여기에서는 tap interface가 kernel에서 생성되는 것이니 만큼, 특별히 iptables를 활용하면 된다.

<Linux Host>
$ sudo iptables -I INPUT -p icmp -s 0.0.0.0/0 -d 192.168.3.51/32 -j DROP

-------------------------------------------------------------------------------------------

<여기서 잠깐>
---------------
vpp와 host application을 연결하는 방법으로 tap interface만 있는 것은 아니다. veth(tap interface와 비교해 최근에 등장한 개념)를 이용한 방법과 shared memory를 기반으로 하는 packet interface인 memif interface를 이용하는 방법도 있다.
먼저 veth interface를 이용한 방법은 tap interface를 이용한 방법과 개념적으로 유사하다고 볼 수 있다. 자세한 사용 방법은 아래 page에서 확인할 수 있다.


한편, memif interface 방법(vpp 간의 통신시에도 사용)은 shared library를 사용하는 만큼 속도가 빠른게 특징인데, host application은 vpp와 통신하기 위해 master 혹은 slave mode로 동작하게 된다.

[그림 5.3] VPP memif library

-------------------------------------------------------------------------------------------

마지막으로, tap interface를 이용해 vpp와 host app을 연동하는 것과 관련하여, 아주 심오한(?) 그림(VPP 기반의 router 구현)이 하나 있어, 여기에 옮겨 본다. 이렇게 구성하면 vpp 기반의 고성능 router(BIRD project 사용)를 만들 수도 있다.😛

[그림 5.4] VPP 기반 router [출처 - 참고문헌 4]



6.  ACL 설정하기
이 장에서는 ACL 즉, Access Control List(일명 packet filter)을 통해 패킷을 차단/허용하는 예제를 소개해 보고자 한다. 편의상 네트워크 구성은 4장의 그것(L3 NAT router)과 동일하게 가져가도록 하자.

[그림 6.1] ACL Test 구성도

<VPP>
comment { This is the LAN bridge interface }
comment { LAN bridge = bvi0 + }
vpp# bvi create instance 0
vpp# set int l2 bridge bvi0 1 bvi
vpp# set int ip address bvi0 192.168.10.1/24
vpp# set int state bvi0 up
 => bridge virtual interface를 하나 만들고, bridge domain 1에 포함시킨다. 이어  ip 주소를 할당한다.

comment { LAN bridge = bvi0 + TenGigabitEthernet3b/0/0 }
vpp# set int l2 bridge TenGigabitEthernet3b/0/0 1
vpp# set int state TenGigabitEthernet3b/0/0 up
  => 물리 LAN port를 bridge 1에 포함시킨다.

comment { LAN bridge = bvi0 + TenGigabitEthernet3b/0/0 + tap0 }
vpp# create tap host-if-name lantap host-ip4-addr 192.168.10.2/24 host-ip4-gw 192.168.10.1
vpp# set int l2 bridge tap0 1
vpp# set int state tap0 up
  => tap0 interface를 하나 만들고 bridge 1에 포함시킨다.

comment { This is the WAN interface }
vpp# set interface state TenGigabitEthernet3b/0/2 up
vpp# set int ip address TenGigabitEthernet3b/0/2 192.168.3.33/24
  => WAN port를 up시키고, ip를 할당한다.

comment { Add default gateway }
vpp# ip route add 0.0.0.0/0 via 192.168.3.254 TenGigabitEthernet3b/0/2
  => default gateway를 추가한다.

comment { Add nat44 rules }
vpp# nat44 forwarding enable
   => nat44 forwardng을 enable 시킨다.
vpp# nat44 plugin enable sessions 65535
vpp# set interface nat44 in bvi0 out TenGigabitEthernet3b/0/2
  => bvi0를 input으로 하고, WAN port를 output으로 하는 NAT44 rule을 추가한다.
vpp# nat44 add interface address TenGigabitEthernet3b/0/2
 => NAT44 interface로 WAN port를 등록한다.


ACL 설정 명령 syntax는 다음과 같다.
set acl-plugin acl [index <idx>] <permit|deny|permit+reflect> src <PREFIX> dst <PREFIX> [proto X] [sport X[-Y]] [dport X[-Y]] [tcpflags <int> mask <int>] [tag FOO] {use comma separated list for multiple rules}

set acl-plugin interface <interface> <input|output> <acl INDEX> [del]

먼저, 아래와 같이 하여 ssh(tcp(6), 목적지 port 22) 서버로 향하는 패킷을 허용해 보도록 하자.
vpp#  set acl-plugin acl permit+reflect src 192.168.10.77/32 dst 192.168.10.2/32 proto 6 sport 0-65535 dport 22
vpp#  set acl-plugin interface bvi0 input acl 0

이번에는 permit+reflect 대신에 deny를 주어 해당 패킷을 차단해 보도록 하자.
vpp#  set acl-plugin acl deny src 192.168.10.77/32 dst 192.168.10.2/32 proto 6 sport 0-65535 dport 22
vpp#  set acl-plugin interface bvi0 input acl 0

... 여러 차례 반복 시험 ...

근데, 뭔가 좀 이상하다. 생각 처럼 제어가 안되는 느낌이다. 아래 내용은 이후 이것 저것 시험해 본 것인데, acl 적용 순서는 제대로 먹히는 듯 한데, 앞서 설정해 본 것과 같이 protocol 제어가 안되는 것 같고, tcp/udp port 제어도 안된다. 왜 일까 ? 😈

-----------------------------------------------------------------------------------
vpp# set acl-plugin acl deny src 0.0.0.0/0 dst 0.0.0.0/0 tag BBB
ACL index:0
vpp# set acl-plugin interface TenGigabitEthernet3b/0/2 input acl 0
vpp# show acl-plugin acl 
acl-index 0 count 1 tag {BBB}
          0: ipv4 deny src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 3
  used in lookup context index: 0
vpp# ping 8.8.8.8

Statistics: 5 sent, 0 received, 100% packet loss
=> ping이 안된다. 이건 OK

vpp# set acl-plugin acl permit+reflect src 0.0.0.0/0 dst 0.0.0.0/0 tag AAA
ACL index:1
vpp# set acl-plugin interface TenGigabitEthernet3b/0/2 input acl 1        
vpp# 
vpp# 
vpp# show acl-plugin acl                                                  
acl-index 0 count 1 tag {BBB}
          0: ipv4 deny src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 3
  used in lookup context index: 0
acl-index 1 count 1 tag {AAA}
          0: ipv4 permit+reflect src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 3
  used in lookup context index: 0
vpp# 
vpp# 
vpp# 
vpp# ping 8.8.8.8                                                         

Statistics: 5 sent, 0 received, 100% packet loss
=> permit+reflect rule을 추가했으나, acl 0에서 deny하고 있으니, ping이 안되는 것은 맞다. 이건 OK

vpp# set acl-plugin interface TenGigabitEthernet3b/0/2 input acl 0 del
vpp# ping 8.8.8.8                                                     
116 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=30.5226 ms
116 bytes from 8.8.8.8: icmp_seq=2 ttl=116 time=30.4559 ms
116 bytes from 8.8.8.8: icmp_seq=3 ttl=116 time=30.4174 ms
116 bytes from 8.8.8.8: icmp_seq=4 ttl=116 time=30.3815 ms
116 bytes from 8.8.8.8: icmp_seq=5 ttl=116 time=30.3922 ms

Statistics: 5 sent, 5 received, 0% packet loss
=> acl 0은 지웠으니, ping이 되는 것도 정상 OK

vpp# set acl-plugin acl deny src 0.0.0.0/0 dst 0.0.0.0/0 tag CCC
ACL index:2
vpp# set acl-plugin interface TenGigabitEthernet3b/0/2 input acl 2
vpp# show acl-plugin acl 
acl-index 0 count 1 tag {BBB}
          0: ipv4 deny src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 
  used in lookup context index: 
acl-index 1 count 1 tag {AAA}
          0: ipv4 permit+reflect src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 3
  used in lookup context index: 0
acl-index 2 count 1 tag {CCC}
          0: ipv4 deny src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 3
  used in lookup context index: 0
vpp# ping 8.8.8.8
116 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=30.5507 ms
116 bytes from 8.8.8.8: icmp_seq=2 ttl=116 time=30.4598 ms
116 bytes from 8.8.8.8: icmp_seq=3 ttl=116 time=30.3940 ms
116 bytes from 8.8.8.8: icmp_seq=4 ttl=116 time=30.3909 ms
116 bytes from 8.8.8.8: icmp_seq=5 ttl=116 time=30.3897 ms

Statistics: 5 sent, 5 received, 0% packet loss
=> 새로 추가한 deny rule은 acl 2에 위치하므로 acl 1(permit+reflect)가 적용되어 ping OK

vpp# set acl-plugin interface TenGigabitEthernet3b/0/2 input acl 1 del
vpp# 
vpp# 
vpp# show acl-plugin acl                                              
acl-index 0 count 1 tag {BBB}
          0: ipv4 deny src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 
  used in lookup context index: 
acl-index 1 count 1 tag {AAA}
          0: ipv4 permit+reflect src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 
  used in lookup context index: 
acl-index 2 count 1 tag {CCC}
          0: ipv4 deny src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 3
  used in lookup context index: 0
vpp# 
vpp# ping 8.8.8.8

Statistics: 5 sent, 0 received, 100% packet loss
=> acl 1(permit+reflect) 삭제했으니, ping 안되는 것 역시 OK

vpp# set acl-plugin interface TenGigabitEthernet3b/0/2 input acl 2 del
vpp# ping 8.8.8.8                                                     
116 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=30.4948 ms
116 bytes from 8.8.8.8: icmp_seq=2 ttl=116 time=30.4885 ms
116 bytes from 8.8.8.8: icmp_seq=3 ttl=116 time=30.3899 ms
116 bytes from 8.8.8.8: icmp_seq=4 ttl=116 time=30.4338 ms
116 bytes from 8.8.8.8: icmp_seq=5 ttl=116 time=30.4061 ms

Statistics: 5 sent, 5 received, 0% packet loss
=> acl 2(deny) rule 삭제했으니 ping 되는 것 OK

vpp# set acl-plugin interface TenGigabitEthernet3b/0/2 input acl 0
vpp# ping 8.8.8.8                                                 

Statistics: 5 sent, 0 received, 100% packet loss
=> acl 0(deny rule) 다시 enable했으니, ping 안되는 것은 OK

vpp# set acl-plugin interface TenGigabitEthernet3b/0/2 input acl 1
vpp# show acl-plugin acl 
acl-index 0 count 1 tag {BBB}
          0: ipv4 deny src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 3
  used in lookup context index: 0
acl-index 1 count 1 tag {AAA}
          0: ipv4 permit+reflect src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 3
  used in lookup context index: 0
acl-index 2 count 1 tag {CCC}
          0: ipv4 deny src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 
  used in lookup context index: 
vpp# 
vpp# 
vpp# ping 8.8.8.8                                                 

Statistics: 5 sent, 0 received, 100% packet loss
=> acl 1(permit+reflect) 다시 enable 했으나, acl 0(deny)가 적용되어 ping 안되는 것 OK

vpp# show acl-plugin acl                                              
acl-index 0 count 1 tag {BBB}
          0: ipv4 deny src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 3
  used in lookup context index: 0
acl-index 1 count 1 tag {AAA}
          0: ipv4 permit+reflect src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 3
  used in lookup context index: 0
acl-index 2 count 1 tag {CCC}
          0: ipv4 deny src 0.0.0.0/0 dst 0.0.0.0/0 proto 0 sport 0-65535 dport 0-65535
  applied inbound on sw_if_index: 
  used in lookup context index: 
vpp# 
-----------------------------------------------------------------------------------

여기까지 확인 결과, acl 적용 순서는 제대로 먹히는게 분명하다. 문제는 tcp/udp/icmp protocol에 대한 제어가 안되는 것 같다는 것인데 ... 뭐가 문제일까 ? 😂

📌 vpp ACL과 관련해서는 좀 더 분석 후 다시 정리해 보도록 하자. vppctl 대신 vpp_api_test program을 이용하는 방법도 함께 고민해 보도록 하자.




7. Load Balancer
이 장에서는 vpp load balancing의 개념을 살펴본 후, (아래 그림에서 사용하는 lb 명령 보다 상대적으로 간단한) NAT44 기반의 Load balancer를 만드는 방법을 소개해 보고자 한다.

1) VPP 전용 Load Balancer
VPP에는 아래 그림에 해당하는 load balancing 전용 명령(lb)이 포함되어 있다. 먼저 이 방법을 이용해 load balancing 설정을 해 보려고 보니, 머리가 좀 지끈거린다(준비할게 한두가지가 아니다). 😂

[그림 7.1] VPP 기반 Load Balancer [출처: 참고 문헌 4]

vpp# lb ?
  lb as                                    lb as <vip-prefix> [protocol (tcp|udp) port <n>] [<address> [<address> [...]]] [del] [flush]
  lb conf                                 lb conf [ip4-src-address <addr>] [ip6-src-address <addr>] [buckets <n>] [timeout <s>]
  lb flush vip                          lb flush vip <prefix> [protocol (tcp|udp) port <n>]
  lb set interface nat4          lb set interface nat4 in <intfc> [del]
  lb set interface nat6          lb set interface nat6 in <intfc> [del]
  lb vip                                   lb vip <prefix> [protocol (tcp|udp) port <n>] [encap (gre6|gre4|l3dsr|nat4|nat6)] [dscp <n>] [type (nodeport|clusterip) target_port <n>] [new_len <n>] [src_ip_sticky] [del]

<load balacning을 위한 encapsulation 방법>
  => Load balancing을 위해 packet을 전달하는 방법에는 크게 3가지 기법이 있다(단, 아래 내용에는 IPv6는 편의상 생략).
a) IPv4+GRE
b) IPv4+L3DSR - VIP <-> DSCP에 mapping하는 방법
c) NAT4

이 중, GRE 터널을 이용한 방법과 NAT를 이용한 방법은 그럭저럭 알겠는데, L3DSR은 단어(L3DSR - L3 Direct Server Return)만 보아서는 무슨 말인지 잘 이해가 안된다. 이건 뭘까 ?

백문이 불여일견~ 아래에 L3DSR를 보다 이해하기 쉽도록 하나의 그림으로 표현해 보았다. 아래 그림이 이해가 되는가 ?

[그림 7.2] VPP 기반 L3DSR Load Balancing 개념도 [출처: 참고 문헌 5]

📌 DSCP는 우선순위 정보를 담기 위해 사용(QoS)하는 ipv4 header 내의 필드이다. 이 값에 적절한 정보를 실어 보내고 이를 VIP 값과 matching 즉, DSCP 000010 => VIP x.x.x.2 하는 방법을 사용하여 load balancing을 구현한 것이 L3DSR이다.

여기까지의 구성은 생각보다 좀 복잡하다. 그 이유는 VPP가 LB 장비 위에만 올라가는 구조가 아니기 때문이다. 즉, VPP는 (그림 7.1 기준으로) Router, LB, Proxy 세곳에 모두 올라가 있어야만 제대로 동작한다.

2) NAT 기반 초 간단 Load Balancer
아, 그렇다면, load balancer가 반드시 위와 같은 구조이어야만 할까 ? 일반적인 L4 switch는 장비 하나로만 구성되어 있지 않은가 ? 

[그림 7.3] VPP 기반 Load Balancer Testbed - NAT 기반

이제부터는 NAT44 static mapping 기능을 사용하여 L4 switch와 유사한 load balacner를 구현하는 예제를 소개해 보기로 한다.

======================
vpp# bvi create instance 0
vpp# set int l2 bridge bvi0 1 bvi
vpp# set int ip address bvi0 192.168.10.1/24
vpp# set int state bvi0 up
  => bridge virtual interface를 하나 만들고, bridge domain 1에 포함시킨다. 이어  ip 주소를 할당한다.

vpp# set int l2 bridge TenGigabitEthernet3b/0/0 1
vpp# set int state TenGigabitEthernet3b/0/0 up
  => 물리 LAN port를 bridge 1에 포함시킨다.

vpp# set interface state TenGigabitEthernet3b/0/2 up
vpp# set int ip address TenGigabitEthernet3b/0/2 192.168.3.33/24
  => WAN port를 up시키고, ip를 할당한다.

vpp# ip route add 0.0.0.0/0 via 192.168.3.254 TenGigabitEthernet3b/0/2
  => default gateway를 추가한다.

vpp# nat44 forwarding enable
   => nat44 forwardng을 enable 시킨다.
vpp# nat44 plugin enable sessions 10000
vpp# set interface nat44 in bvi0 out TenGigabitEthernet3b/0/2
  => bvi0를 input으로 하고, WAN port를 output으로 하는 NAT44 rule을 추가한다.
vpp# nat44 add interface address TenGigabitEthernet3b/0/2
 => NAT44 interface로 WAN port를 등록한다.
======================

여기까지는 4장의 L3 NAT router 설정과 동일하며, 마지막에 아래 명령을 추가해 주면 된다.

vpp# nat44 add load-balancing static mapping protocol tcp external 192.168.3.33:80 local 192.168.10.100:80 probability 50 local 192.168.10.200:80 probability 50
=> 내부에 server가 여러 개일 경우, local ip:port 형태로 반복해서 열거해 주면 된다. 물론 이때  probability 값도 그에 맞게 조정해 주어야 한다. 2개의 내부 서버를 설정해 주었으므로 probability 값은 50이다. 그림 7.3 처럼 4개의 서버가 있다면 probability 값은 25가 되어야 한다.

vpp# show nat44 static mappings
NAT44 static mappings:
 TCP external 192.168.3.33:22  
  local 192.168.10.200:22 vrf 0 probability 50
  local 192.168.10.100:22 vrf 0 probability 50

<Windows PC>
web browser: http://192.168.3.33
  => 테스트 결과, 1:1 비율로 web service가 분배되는 것을 알 수 있다.
  => 설정을 바꾸어, web 대신 ssh를 이용해서 테스트해 보아도 좋다.


8. WireGuard
이 장에서는 WireGuard  plugin을 이용하여 vpn 터널을 만드는 방법을 소개하고자 한다. WireGuard(linux kernel 기반)와 관련해서는 이미 여러 차례 내용 정리를 한 바가 있다.

a) WireGuard Original => 

b) PQ-WireGuard(양자내성 암호 알고리즘이 통합된 wireguard) =>



[그림 8.1] WireGuard VPN 터널(VPP wireguard <=> ubuntu wireguard)


vpp# set interface state TenGigabitEthernet3b/0/0 up    #vpp 장비 LAN port up
vpp# set interface state TenGigabitEthernet3b/0/2 up    #vpp 장비 WAN port up

vpp# set int ip address TenGigabitEthernet3b/0/0 192.168.10.1/24   #vpp LAN ip 설정
vpp# set int ip address TenGigabitEthernet3b/0/2 192.168.3.33/24   #vpp WAN ip 설정 

vpp# wireguard create listen-port 59760 src 192.168.3.33       #wireguard interface 생성(keypair 생성 포함)
wg0
vpp# show wireguard interface 
[0] wg0 src:192.168.3.33 port:59760 private-key:EAAAADAAAAAg72qwxn8AAAoAAAAAAAAAwPzoB8d/AAA= 100000003000000020ef6ab0c67f00000a00000000000000c0fce807c77f0000 public-key:Fp57l81Z2RCToioDipRIN54/a+bVeD4Lpc1CDdDyJBo= 169e7b97cd59d91093a22a038a9448379e3f6be6d5783e0ba5cd420dd0f2241a mac-key: 72d149c21d7b81ba57fb201daded7c62775c107a4192a4fc49d1c131ed87bece

📌 위의 create 명령을 실행할 경우, 매번 새로운 public/privatekey pair가 생성되는 문제(?) 가 있다. 한번 생성한 public/privatekey pair를 일정 기간 유지하기 위해서는 아래와 같이, vpp 외부에서 keypair를 생성 후 사용하면 된다.
$ wg genkey | tee ./privatekey | ./wg pubkey > ./publickey
vpp# wireguard create listen-port 59760 private-key <privatekey> src 192.168.3.33

vpp# set interface state wg0 up                          #wg0 interface up
vpp# set int ip address wg0 172.16.1.99/24     #wg0 interface ip 설정
vpp# show int addr
TenGigabitEthernet3b/0/0 (up):
TenGigabitEthernet3b/0/1 (dn):
TenGigabitEthernet3b/0/2 (up):
  L3 192.168.3.33/24
TenGigabitEthernet3b/0/3 (dn):
local0 (dn):
wg0 (up):
  L3 172.16.1.99/24

vpp# wireguard peer add wg0 public-key EgJxYfRtnM1iYO4W223wstMy/45iMCRGf/b4qSryzT0= endpoint 192.168.3.101 dst-port 59760 allowed-ip 0.0.0.0/0 allowed-ip 172.16.1.254/32 persistent-keepalive 25    #wireguard peer 설정

vpp# set interface mtu packet 1420 wg0                   #wireguard mtu 설정 
 
vpp# ip route add 0.0.0.0/0 via 172.16.1.99 wg0      #wg0를 이용하는 routing entry 설정

vpp# ping 8.8.8.8    
참고) vpp에서 kernel wireguard 장비를 통해 internet(8.8.8.8)으로 ping 시도
116 bytes from 8.8.8.8: icmp_seq=1 ttl=56 time=36.5289 ms
116 bytes from 8.8.8.8: icmp_seq=2 ttl=56 time=36.1525 ms

sudo tcpdump -i eth2 port 59760     #kernel wireguard 장비에서 wireguard packet capture
19:19:27.564590 IP 192.168.3.33.59760 > 192.168.3.101.59760: UDP, length 128
19:19:27.600483 IP 192.168.3.101.59760 > 192.168.3.33.59760: UDP, length 128
19:19:28.564570 IP 192.168.3.33.59760 > 192.168.3.101.59760: UDP, length 128
19:19:28.600506 IP 192.168.3.101.59760 > 192.168.3.33.59760: UDP, length 128
...

vpp# ip route del 0.0.0.0/0 via 172.16.1.99 wg0 
vpp# ip route add 192.168.5.0/24 via 172.16.1.99 wg0 

vpp# ping 192.168.5.1
참고) vpp에서 kernel wireguard 장비 LAN ip로 ping 시도
116 bytes from 192.168.5.1: icmp_seq=1 ttl=64 time=.3942 ms
116 bytes from 192.168.5.1: icmp_seq=2 ttl=64 time=.3891 ms

sudo tcpdump -i eth2 port 59760     #kernel wireguard 장비에서 wireguard packet capture
19:21:27.238980 IP 192.168.3.101.59760 > 192.168.3.33.59760: UDP, length 128
19:21:28.238698 IP 192.168.3.33.59760 > 192.168.3.101.59760: UDP, length 128
19:21:28.238996 IP 192.168.3.101.59760 > 192.168.3.33.59760: UDP, length 128
19:21:29.238651 IP 192.168.3.33.59760 > 192.168.3.101.59760: UDP, length 128
19:21:29.238940 IP 192.168.3.101.59760 > 192.168.3.33.59760: UDP, length 128
19:21:30.238610 IP 192.168.3.33.59760 > 192.168.3.101.59760: UDP, length 128
19:21:30.238815 IP 192.168.3.101.59760 > 192.168.3.33.59760: UDP, length 128

아, 그런데 반대 방향으로의 ping이 안된다. 왜 일까 ?

좌측 Ubuntu --> wireguard tunnel --> vpp -> internal network(192.168.10.200)

<Ubuntu>
$ ip route add 192.168.10.0/24 dev wg0
$ ping 192.168.10.200
  -> 아무래도 응답을 못하는 것 같다 😓

sudo tcpdump -i eth2 port 59760     #kernel wireguard 장비에서 wireguard packet capture
19:21:27.238980 IP 192.168.3.101.59760 > 192.168.3.33.59760: UDP, length 128
19:21:28.238996 IP 192.168.3.101.59760 > 192.168.3.33.59760: UDP, length 128
19:21:29.238940 IP 192.168.3.101.59760 > 192.168.3.33.59760: UDP, length 128
19:21:30.238815 IP 192.168.3.101.59760 > 192.168.3.33.59760: UDP, length 128
...


<여기서 잠깐>
  => WireGuard의 chacha20-poly1305 AEAD를 h/w적으로 가속화할 수는 없을까 ? 아래에 답이 있다.

[그림 8.2] Intel QAT 8960/8970 암호 가속기

[그림 8.3] Intel QAT를 이용한 wireguard 암호 가속[출처 - 참고문헌 12]

참고로, 아래 supermicro appliance에는 intel QAT chip이 자체적으로 포함되어 있어, 매우 편리하다.

[그림 8.4] Test 장비(IoT SuperServer SYS-110D-8C-FRAN8TP) [출처 - 참고문헌 13]

📌 Intel QAT와 관련해서는 참고문헌 14를 참고하기 바란다.

-------------------------------------------------------------------------------------------------



9. IPSec
이 장에서는 (개인적으로는 WireGuard 골수 왕 팬이지만, 그래도 30년 이상 명맥을 이어온) IPSec에 관하여 소개해 보고자 한다. VPP에 구현되어 있는 코드의 경우, 아직까지는 WireGuard(development 수준) 보다는 IPSec(production 수준)이 좀 더 안정화되어 있는 느낌이다. 

[그림 9.1] IPSec VPN 터널 구성



vpp# ipsec ?
  ipsec itf create                         ipsec itf create [instance <instance>]
  ipsec itf delete                         ipsec itf delete <interface>
  ipsec policy                             ipsec policy [add|del] spd <id> priority <n> 
  ipsec sa bind                            ipsec sa [unbind] <sa-id> <worker>
  ipsec sa                                 ipsec sa [add|del]
  ipsec select backend                     ipsec select backend <ah|esp> <backend index>
  ipsec spd                                ipsec spd [add|del] <id>
  ipsec tunnel protect                     ipsec tunnel protect <interface> input-sa <SA> output-sa <SA> [add|del]

vpp# show ipsec ?
  show ipsec all                           show ipsec all
  show ipsec backends                      show ipsec backends
  show ipsec interface                     show ipsec interface
  show ipsec protect                       show ipsec protect
  show ipsec protect-hash                  show ipsec protect-hash
  show ipsec sa                            show ipsec sa [index]
  show ipsec spd                           show ipsec spd [index]
  show ipsec tunnel                        show ipsec tunnel

vpp# ikev2 ?
  ikev2 dpd disable                        ikev2 dpd disable
  ikev2 initiate                           ikev2 initiate sa-init <profile id>
ikev2 initiate del-child-sa <child sa ispi>
ikev2 initiate del-sa <sa ispi>
ikev2 initiate rekey-child-sa <child sa ispi>
  ikev2 profile                            ikev2 profile [add|del] <id>
ikev2 profile set <id> auth [rsa-sig|shared-key-mic] [cert-file|string|hex] <data>
ikev2 profile set <id> id <local|remote> <type> <data>
ikev2 profile set <id> tunnel <interface>
ikev2 profile set <id> udp-encap
ikev2 profile set <id> traffic-selector <local|remote> ip-range <start-addr> - <end-addr> port-range <start-port> - <end-port> protocol <protocol-number>
ikev2 profile set <id> responder <interface> <addr>
ikev2 profile set <id> ike-crypto-alg <crypto alg> <key size> ike-integ-alg <integ alg> ike-dh <dh type>
ikev2 profile set <id> esp-crypto-alg <crypto alg> <key size> [esp-integ-alg <integ alg>]
ikev2 profile set <id> sa-lifetime <seconds> <jitter> <handover> <max bytes>ikev2 profile set <id> disable natt
  ikev2 set liveness                       ikev2 set liveness <period> <max-retires>
  ikev2 set logging level                  ikev2 set logging level <0-5>

vpp# show ikev2 ?
  show ikev2 profile                       show ikev2 profile
  show ikev2 sa                            show ikev2 sa [rspi <rspi>] [details]

IPSec & IKEv2 설정 방법과 관련해서는 아래 wiki page에 아주 잘 설명되어 있으며, 이를 참조하는게 답일 듯 하다. 근데, vpp old version을 기준으로 설명되어 있어, 일부 명령이 좀 달라졌으니, 주의를 요한다.


또한 아래 youtube 영상도 VPP 기반 IPSec을 이해하는데 도움이 될 것으로 보인다.


10. Snort  기반의 IPS
VPP를 활용하면, SnortSuricata 등과 연합하여 S/W 기반의 고성능 IPS(Intrusion Prevention System) 개발도 가능하다. 이번 장에서는 IPS를 만드는 과정을 소개해 보고자 한다.


[그림 10.1] VPP & Snort IPS 구성 [출처 - 참고문헌 10]


[그림 10.2] VPP & Snort IPS 구성 [출처 - 참고문헌 10]

To be continued...



References
[1] https://s3-docs.fd.io/vpp/24.02/
[2] https://ipng.ch/s/articles/
  => VPP examples
[3] https://events19.linuxfoundation.org/wp-content/uploads/2017/12/Implementing-A-High-Performance-Virtualized-CPE-Solution-Hongjun-Ni-Xingfu-Li-Intel.pdf
  => VPP로 할 수 있는 것들을 한눈에 ...
[4] https://www.dpdk.org/wp-content/uploads/sites/35/2018/12/A-Hierarchical-SW-Load-Balancing-Solution-for-Cloud-Deployment.pdf
[5] https://events19.linuxfoundation.org/wp-content/uploads/2018/07/ONS.NA_.2019.VPP_LB_public.pdf
  => Load balancing 관련 
[6] https://www.youtube.com/watch?v=C8cbJCT2blY&ab_channel=LFNetworking
[7] https://docs.nxp.com/bundle/GUID-3FFCCD77-5220-414D-8664-09E6FB1B02C6/page/GUID-EBA9A5C6-2407-4AC7-87B6-C1470B2CD92D.html
  => IPSec 관련
[8] https://www.netgate.com/resources/articles-vector-packet-processing
[9] https://wiki.fd.io/view/VPP/NAT
[10] Next_Generation_Firewall_Optimizations_Solution_Brief_765277v1.pdf
[11]  Impressive Packet Processing Performance Enables Greater Workload Consolidation
[12] Intel® AVX-512 and Intel® QAT - Accelerate WireGuard Processing with Intel® Xeon® D-2700 Processor
[13] https://www.supermicro.com/en/products/system/iot/1u/sys-110d-8c-fran8tp
[14] https://cdrdv2-public.intel.com/784475/784475_Network%20Security%20with%20Intel%C2%AEQuickAssist%20Technology.pdf
[15] https://github.com/mwang005/a-deep-dive-into-vpp  (Good site)




Slowboot