‹ hrntknr's blog

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%で張り付いているコアがあるが、思っていたほどの消費電力上昇はなかった。