v6plus+flets光クロスのルーターをVPPで作成する
Jul 03, 2021
TL;DR
フレッツ光+ v6plus 固定 IP サービスのルーターを vpp で作成した。(map-e も設定によっては可能だと思われる。)
dpdk や avf driver を利用することで 8Gbps 近くのパフォーマンスが出た。
dpdk は待機時消費電力が多いと思っていたが、今回の機材では消費電力はほぼ変わらなかった。
機材
- intel X710 DA2
- 家に転がっていたため¥ 0
- Minisforum EliteMini B550
- PCI スロットが刺さる小さい PC ということで購入。
- 補助電源ありでの運用が想定されている商品だが、NIC 程度なら補助電源無しで動作した。
- ¥74,390
- mikrotik CRS312-4C+8XG-RM
- 家で利用中の SW だったため¥ 0
- NTT から借りた 10G-EPON ONU
- 中を覗いてみると SFP+モジュールが刺さっていたが、、借り物なので分解はせず 10G-baseT で接続
- 借り物なので¥ 0
合計:¥74,390
構成
driver について
いろいろな driver を試してみたが、結局以下の理由で dpdk driver を使うことにした。
- af_xdp driver
- af_xdp を使って vpp までパケットを吸い上げるドライバー。
- worker を増やしたときにパケロスが worker 数の確率で(worker=2 のとき 50%)発生してしまうため、worker=1 の運用しかできない。
- 上記のバグがあり、色々とチューニングをしてみたが 6Gbps ほどで頭打ちになってしまった
- avf driver
- x710 の virtual function のみで利用できる driver。
- vfio-pci を直接(dpdk などを噛まさずに)vpp が操作する
- worker を増やしたときに worker 数の確率でレイテンシが跳ね上がる(3ms→20ms ほど)パケットが発生するため worker=1 固定
- interrupt モードが利用できパフォーマンスも良いとのことで、一番本命だった。
- multicast が通らず、nd に応答できないため、ipv6 通信ができない。致命的だったため見送り。
- pf=trust on + vf=allmulticast → 効果なし
ethtool --set-priv-flags {pf} vf-true-promisc-support on
→ enp1s0f0(wan)では設定できたが、enp1s0f1(lan)では Not supported に。
- dpdk driver
- pmd しか利用できないため、常時稼働するホームルーターでは抵抗があったが、待機電力では遜色ない電気消費量だったため採用。
- 一番安定+パフォーマンスが出たため採用。
- 参考だが、vf を dpdk に bind すると、avf driver のとき同様に ipv6 での通信ができない。
設定
dhcp や dns、tftp 周りの設定は省略。
/etc/default/grub
GRUB_DEFAULT=0
GRUB_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX="iommu=pt amd_iommu=pt"
iommu の設定だけ。
docker-compose.yaml
version: '3'
services:
vpp:
image: ligato/vpp-base
privileged: true
restart: unless-stopped
volumes:
- ./docker/startup.conf:/etc/vpp/startup.conf
- ./docker/setup.gate:/etc/vpp/setup.gate
- ./docker/start_vpp.sh:/vpp/start_vpp.sh
command: /vpp/start_vpp.sh
network_mode: host
br_setup:
image: alpine
privileged: true
restart: unless-stopped
volumes:
- ./docker/br_setup.sh:/root/br_setup.sh
entrypoint: /root/br_setup.sh
network_mode: host
dhcp:
build:
context: ./docker/
dockerfile: Dockerfile.dhcp
restart: unless-stopped
volumes:
- ./docker/kea-dhcp4.conf:/etc/kea/kea-dhcp4.conf
networks:
vpp:
ipv4_address: 192.168.255.2
dns:
build:
context: ./docker/
dockerfile: Dockerfile.dns
restart: unless-stopped
volumes:
- ./docker/unbound.conf:/etc/unbound/unbound.conf
networks:
vpp:
ipv4_address: 192.168.255.3
tftp:
build:
context: ./docker/
dockerfile: Dockerfile.tftp
restart: unless-stopped
volumes:
- ./docker/tftp:/var/tftpboot
networks:
vpp:
ipv4_address: 192.168.255.4
nginx:
image: nginx:alpine
restart: unless-stopped
volumes:
- ./docker/nginx.conf:/etc/nginx/conf.d/default.conf
- ./docker/html:/usr/share/nginx/html
networks:
vpp:
ipv4_address: 192.168.255.5
networks:
vpp:
driver: bridge
enable_ipv6: true
driver_opts:
com.docker.network.bridge.enable_ip_masquerade: 'false'
com.docker.network.bridge.name: br_vpp
ipam:
driver: default
config:
- subnet: 192.168.255.0/24
gateway: 192.168.255.1
- subnet: 240b:10:9ab0:22ff::/64
gateway: 240b:10:9ab0:22ff::1
docker/br_setup.sh
#!/bin/sh -u
IP="$(which ip)"
GW_ADDR="192.168.255.1/24"
TAP_NAME="tap0"
BR_NAME="br_vpp"
while true; do
if $IP link show $BR_NAME &>/dev/null; then
if $IP addr show | grep -q $GW_ADDR; then
$IP addr del $GW_ADDR dev $BR_NAME
fi
if ! $IP addr show $TAP_NAME | grep -q "master $BR_NAME"; then
$IP link set dev $TAP_NAME master $BR_NAME
fi
fi
sleep 1
done
tap0 は vpp から宣言して作成するインターフェース。
docker で br_vpp という bridge を作成し、docker 純正の interface を削除し、tap0 を gw として注入するイメージで vpp とサービス系 docker network を接続する。
docker/startup.conf
unix {
nodaemon
nobanner
log /dev/stdout
cli-listen /run/vpp/cli.sock
startup-config /etc/vpp/setup.gate
gid root
}
api-segment {
gid root
}
buffers {
buffers-per-numa 65536
}
plugins {
plugin default { disable }
plugin dpdk_plugin.so { enable }
plugin nat_plugin.so { enable }
plugin mss_clamp_plugin.so { enable }
plugin acl_plugin.so { enable }
plugin dns_plugin.so { enable }
plugin dhcp_plugin.so { enable }
plugin ping_plugin.so { enable }
plugin arping_plugin.so { enable }
plugin map_plugin.so { enable }
}
dpdk {
dev default {
num-rx-desc 512
num-tx-desc 512
}
no-multi-seg
dev 0000:01:00.0 {
name wan
num-rx-queues 2
num-tx-queues 2
}
dev 0000:01:00.1 {
name lan
num-rx-queues 2
num-tx-queues 2
}
}
cpu {
main-core 1
workers 4
}
logging {
default-log-level debug
}
docker/setup.gate
create tap id 0
wait 1
set interface rx-mode wan polling
set interface rx-mode lan polling
set interface rx-mode tap0 interrupt
set interface mtu packet 1500 wan
set interface mtu packet 1500 lan
set interface mtu packet 1500 tap0
set interface state wan up
set interface state lan up
set interface state tap0 up
wait 3
set ip6 address wan {{CE_ADDRESS}}
create ipip tunnel src {{CE_ADDRESS}} dst {{BR_ADDRESS}}
set interface ip address ipip0 {{IPV4_ADDRESS}}
set interface mtu packet 1500 ipip0
set interface rx-mode ipip0 interrupt
set interface tcp-mss-clamp ipip0 ip4 enable ip4-mss 1400 ip6 disable
set interface state ipip0 up
set interface ip address lan 10.196.0.1/24
set ip6 address lan prefix group flets 0:0:0:1::1/64
ip6 nd lan no ra-suppress
set interface ip address tap0 192.168.255.1/24
set ip6 address tap0 prefix group flets 0:0:0:ff::1/64
ip route add ::/0 via {{IPV6_NEXTHOP}} wan
ip route add 0.0.0.0/0 via ipip0
set acl-plugin acl permit src {{BR_ADDRESS}} dst {{CE_ADDRESS}}, permit src fe80::/64 dst fe80::/64 proto 0 sport 546 dport 547, permit src fe80::/64 dst fe80::/64 sport 547 dport 546, deny src ::/0 dst::/0
set acl-plugin acl permit+reflect src ::/0 dst::/0
set acl-plugin acl deny src 0.0.0.0/0 dst 0.0.0.0/0
set acl-plugin acl permit+reflect src 0.0.0.0/0 dst 0.0.0.0/0
set acl-plugin interface wan input acl 0
set acl-plugin interface wan output acl 1
set acl-plugin interface ipip0 input acl 2
set acl-plugin interface ipip0 output acl 3
set dhcp proxy server 192.168.255.2 src-address 192.168.255.1
wait 10
dhcp6 pd client wan prefix group flets
nat44 plugin enable
nat44 add interface address ipip0
set interface nat44 in lan in tap0 in ipip1 out ipip0
実際はもっとポートが空いてたりトンネルが生えてたりするが、余分な部分は省略。
docker/start_vpp.sh
#!/bin/bash -eux
setup_dpdk() {
cd /sys/bus/pci/devices/${1}
driver=$(basename $(readlink driver))
if [ "${driver}" != "vfio-pci" ]; then
ifname=$(basename net/*)
echo ${1} | tee driver/unbind > /dev/null
echo vfio-pci | tee driver_override > /dev/null
echo ${1} | tee /sys/bus/pci/drivers/vfio-pci/bind > /dev/null
echo | tee driver_override > /dev/null
fi
}
setup_dpdk 0000:01:00.0
setup_dpdk 0000:01:00.1
exec /usr/bin/vpp -c /etc/vpp/startup.conf
i40e を unbind して、vfio-pci を bind した後、vpp を起動するスクリプト。
消費電力計測
パフォーマンス計測は speedtest.net なので外的要因が強い、消費電力を図るための参考程度の指標として測定。
driver | config | 通常時 | MAX 電力(速度計測中) | Download(Mbps) | Upload(Mbps) |
---|---|---|---|---|---|
avf driver | main-core: 1,workers: 1 | 14.7W | 17.7W | 7802.00 | 7691.70 |
dpdk driver | main-core: 1,workers: 1 | 14.8W | 16.6W | 7460.93 | 7813.98 |
dpdk driver | main-core: 1,workers: 4 | 14.8W | 16.7W | 7390.56 | 7799.96 |
avf driver は待機時の cpu 利用率は数%ほどしかなかった。
一方、dpdk では worker の数分 100%で張り付いているコアがあるが、思っていたほどの消費電力上昇はなかった。