🌐

VPP+Linux-CP(+FRR)環境のHomeGatewayをdocker-composeで作った話

2024/06/06に公開

はじめに

以前にVPPのHomeGatewayを立てていたのですが,最近アップデートしました.具体的には,VPPの環境をDockerで構築して,FRRとLinux-CPを用いて連携させました.VPPとFRRを連携させるときには,Dockerのネットワークネームスペースを利用するようにしました.誰かの参考になるかもしれないので共有.(普通にネームスペース作って,そこでFRR動かすほうが楽ですが…)

https://zenn.dev/shu1r0/articles/faec5365486708

Linux-CPとは?

Linuxをコントロールプレーンとして動作させるプラグインです.VPPとLinux間でインターフェースを同期したり,Linuxのルート情報をVPPに反映させたりします.

以下の資料が詳しいです.本記事では,Linux-CPを使ってFRRとVPPを連携させます.

https://s3-docs.fd.io/vpp/24.02/developer/plugins/lcp.html

https://github.com/pimvanpelt/lcpng

https://ipng.ch/s/articles/2021/08/12/vpp-1.html

構成

基本構成は図のようになっています.GigabitEthernet1/0/0 (g1/0/0)でインターネットと接続ます.Ubuntuホストと接続するためのtap0とLAN用インターフェースの(g3/0/0)(g4/0/0)をブリッジ接続します.g2/0/0をルーティング用のインターフェースとして設定しましたが,LANのインターフェースであるbvi0でOSPFを設定しても動くと思います.

OSPF用として,DockerでFRRを起動し,FRRのコンテナの名前スペースにLinux-CPでルーティングに必要なインターフェースをマッピングします.

ネットワーク構成図

全体の構成

フォルダ階層は以下となってます.基本的には,各フォルダにDockerfileと設定ファイルを格納しています.ルータに関する部分は,vpp/frr/scripts/です.その他の部分については割愛します.

$ tree --dirsfirst
.
├── bind
│   ├── etc
│   │   ├── bind
│   │   │   ├── named.conf
│   │   │   ├── named.conf.local
│   │   │   └── named.conf.options
│   │   └── systemd
│   │       └── resolved.conf
│   └── Dockerfile
├── dhcpd
│   ├── etc
│   │   ├── default
│   │   │   └── isc-dhcp-server
│   │   └── dhcp
│   │       └── dhcpd.conf
│   └── Dockerfile
├── frr
│   ├── etc
│   │   └── frr
│   │       ├── daemons
│   │       ├── frr.conf
│   │       └── vtysh.conf
├── scripts
│   └── setup_netns.sh
├── vpp
│   ├── etc
│   │   └── vpp
│   │       ├── startup.conf
│   │       └── startup.gate
│   ├── run
│   │   └── vpp
│   ├── var
│   │   └── log
│   │       └── vpp
│   ├── Dockerfile
│   └── run.sh
└── docker-compose.yml

Docker Compose

docker-compose.ymlにルータの構築に関する設定が書かれています.重要なサービスとして,frrsetup_nsvppを説明します.

  • frr
    • FRRを動作させるDockerコンテナ
    • networknoneとして孤立したネットワークネームスペースを作成し,後述するsetup_nsでVPPと連携
  • setup_ns
    • FRRのネットワークネームスペースを/var/run/netnsに配置するコンテナ
    • FRRのプロセスを検索するためにdockerコマンドを使用するので/usr/bin/dockerをホストと共有
  • vpp
    • VPPを動作させるコンテナ
    • FRRのネットワークネームスペースとLinux-CPでF連携
./docker-compose.yml
version: '3'

services:

  frr:
    image: frrouting/frr
    container_name: gateway_frr
    restart: unless-stopped
    privileged: true
    volumes:
      - ./frr/etc/frr/daemons:/etc/frr/daemons
      - ./frr/etc/frr/frr.conf:/etc/frr/frr.conf
      - ./frr/etc/frr/vtysh.conf:/etc/frr/vtysh.conf
      # - ./frr/var/run/frr/:/var/run/frr/
      - ./frr/var/log/:/var/log/
    environment:
      TZ: Asia/Tokyo
    network_mode: none

  setup_ns:
    image: ubuntu:22.04
    restart: unless-stopped
    privileged: true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /usr/bin/docker:/usr/bin/docker
      - /var/run/netns/:/var/run/netns/
      - /proc/:/proc/
      - ./scripts/setup_netns.sh:/root/setup_netns.sh
    command: /root/setup_netns.sh
    network_mode: host
    depends_on:
      - "frr"

  vpp:
    build:
      context: .
      dockerfile: ./vpp/Dockerfile
    restart: unless-stopped
    pid: host
    privileged: true
    volumes:
      - /var/run/netns/:/var/run/netns/
      - ./vpp/etc/vpp/startup.conf:/etc/vpp/startup.conf
      - ./vpp/etc/vpp/startup.gate:/etc/vpp/startup.gate
      - ./vpp/run/vpp/:/run/vpp/
      - ./vpp/var/log/vpp/:/var/log/vpp/
      - ./vpp/run.sh:/root/run.sh
    environment:
      TZ: Asia/Tokyo
    command: /root/run.sh
    network_mode: host
    depends_on:
     - "setup_ns"

  bind:
    build:
      context: .
      dockerfile: ./bind/Dockerfile
    restart: unless-stopped
    volumes:
      - ./bind/etc/bind/named.conf:/etc/bind/named.conf
      - ./bind/etc/bind/named.conf.options:/etc/bind/named.conf.options
      - ./bind/etc/bind/named.conf.local:/etc/bind/named.conf.local
      - ./bind/etc/bind/db.shu1r0.net:/etc/bind/db.shu1r0.net
      - ./bind/etc/systemd/resolved.conf:/etc/systemd/resolved.conf
    environment:
      TZ: Asia/Tokyo
    command: /bin/bash -ec "/usr/sbin/named -f -u bind"
    network_mode: host
    depends_on:
      - "vpp"
    
  dhcpd:
    build:
      context: .
      dockerfile: ./dhcpd/Dockerfile
    restart: unless-stopped
    volumes:
      - ./dhcpd/etc/default/isc-dhcp-server:/etc/default/isc-dhcp-server
      - ./dhcpd/etc/dhcp/dhcpd.conf:/etc/dhcp/dhcpd.conf
    environment:
      TZ: Asia/Tokyo
    command: /bin/bash -ec '[ -e /var/lib/dhcp/dhcpd.leases ] || touch /var/lib/dhcp/dhcpd.leases; chown root:dhcpd /var/lib/dhcp /var/lib/dhcp/dhcpd.leases; chmod 775 /var/lib/dhcp ; chmod 664 /var/lib/dhcp/dhcpd.leases; /usr/sbin/dhcpd -user dhcpd -group dhcpd -f -4 -pf /run/dhcp-server/dhcpd.pid -cf /etc/dhcp/dhcpd.conf'
    network_mode: host
    depends_on:
      - "vpp"

setup_ns

FRRのプロセスIDをdockerコマンドで検索し,/proc/<プロセスID>/ns/netのネットワークネームスペースのファイルを/var/run/netns/gateway_frrにリンクします.これにより,VPPのLinux-CPからこのネットワークネームスペースにアクセスできるようになります.

scripts/setup_ns.sh
#!/usr/bin/env bash

frr_name=gateway_frr

pid=$(docker inspect $frr_name --format '{{.State.Pid}}')

mkdir -p /var/run/netns/
rm -rf /var/run/netns/$frr_name
ln -sfT /proc/$pid/ns/net /var/run/netns/$frr_name

tail -f /dev/null  # dockerコンテナの永続用

VPP

次にVPPです.VPPのDockerfileは以前と同様にインストールしているだけです.

./vpp/Dockerfile
FROM ubuntu:22.04

RUN apt-get update && apt-get install -y --no-install-recommends \
		apt-transport-https \
		ca-certificates \
		curl \
		gnupg \
		iproute2 \
		iputils-ping

WORKDIR /vpp
RUN curl -s https://packagecloud.io/install/repositories/fdio/release/script.deb.sh | bash
RUN apt-get update && apt-get install -y vpp vpp-plugin-core vpp-plugin-dpdk python3-vpp-api vpp-dbg vpp-dev
RUN mkdir -p /var/log/vpp

VPPを起動する場合においては,DPDKを動作させるインターフェースをダウンさせてからVPPを起動します.

./vpp/run.sh
#!/usr/bin/env bash

set -eux

# disable all interface
for iface in $(ip l | awk -F ":" '/^[0-9]+:/{dev=$2 ; if ( dev !~ /^ lo|docker0$/) {print $2}}')
do
  ip link set down dev $iface 
done

exec /usr/bin/vpp -c /etc/vpp/startup.conf

VPPの設定ファイルでは,Linux-CPプラグインを有効化し,linux-cpセクションにおいて,名前空間の指定等を行っていきます.

./vpp/etc/vpp/startup.conf
unix {
	# backgroundで実行しない
  nodaemon
  log /var/log/vpp/vpp.log
  # すべてのメモリマップされたアドレス領域をダンプ
  full-coredump
  cli-listen /run/vpp/cli.sock

  startup-config /etc/vpp/startup.gate
  # メインループ固定のスリープを追加する.
  poll-sleep-usec 100
  # 共有メモリセグメントの所有権を設定するために使用するグループID
  gid vpp
}

api-segment {
  gid vpp
}

dpdk {
   no-multi-seg
   no-tx-checksum-offload

   dev 0000:01:00.0 {
      name GigabitEthernet1/0/0
   }
   dev 0000:02:00.0 {
      name GigabitEthernet2/0/0
   }
   dev 0000:03:00.0{
      name GigabitEthernet3/0/0
   }
   dev 0000:04:00.0{
      name GigabitEthernet4/0/0
   }
 }

plugins {
   plugin default { disable }
   plugin dhcp_plugin.so { enable }
   plugin dpdk_plugin.so { enable }
   plugin nat_plugin.so { enable }
   plugin ping_plugin.so { enable }
   ## linux control place integration
   plugin linux_cp_plugin.so { enable }
   plugin linux_nl_plugin.so { enable }
}

logging {
  size 4096
  default-log-level debug
}

linux-cp {
  # デフォルトのネームスペース
  default netns gateway_frr
  # リンク状態やMTUを同期
  lcp-sync
  # サブインターフェースの同期
  lcp-auto-subint
}

VPPの起動時の初期設定において,lcpコマンドを追加していきます.基本的には,以下のコマンドを実行することで,デフォルトの名前空間にtapで接続されたインターフェースが生成されます.

lcp create <VPPのインターフェース> host-if <ホストのインターフェース名>

今回の設定では,以下のようにアドレスとLinux-CPを設定しています.

./vpp/etc/vpp/startup.gate
comment { Define variables }
define LOCAL_PRE 24
define LOCAL_GATE0 192.168.0.1
define LOCAL_GATE1 192.168.10.1
define LOCAL_HOST1 192.168.10.2
define HOSTNAME vpp-homegate

comment { WAN interface }
set int state GigabitEthernet1/0/0 up
lcp create GigabitEthernet1/0/0 host-if g1
set dhcp client intfc GigabitEthernet1/0/0 hostname $(HOSTNAME)

comment { BVI interface }
bvi create instance 0
set int l2 bridge bvi0 1 bvi
lcp create bvi0 host-if bvi0
set int ip address bvi0 $(LOCAL_GATE1)/$(LOCAL_PRE)
set int state bvi0 up

comment { LAN interfaces }
lcp create GigabitEthernet2/0/0 host-if g2
set int ip address GigabitEthernet2/0/0 $(LOCAL_GATE0)/$(LOCAL_PRE)
set int state GigabitEthernet2/0/0 up

set int l2 bridge GigabitEthernet3/0/0 1
set int state GigabitEthernet3/0/0 up
set int l2 bridge GigabitEthernet4/0/0 1
set int state GigabitEthernet4/0/0 up

comment { Host-stack interface }
create tap host-if-name tap0 host-ip4-addr $(LOCAL_HOST1)/$(LOCAL_PRE) host-ip4-gw $(LOCAL_GATE1)
set int l2 bridge tap0 1
set int state tap0 up

comment { Configure NAT44 }
nat44 forwarding enable
nat44 plugin enable sessions 63000
nat44 add interface address GigabitEthernet1/0/0
set interface nat44 out GigabitEthernet1/0/0 output-feature

comment { OSPF Multicast Route for LCP }
ip mroute add 224.0.0.0/24 via local Forward
ip mroute add 224.0.0.0/24 via GigabitEthernet2/0/0 Accept

基本設定は,以前の記事と同じですが,NATの部分でoutput-featureのオプションを付けています.以前のset interface nat44 in <インターフェース>で設定した場合,ルーティングの前にNATされるため,アドレス変換後のパケットがLinuxに送信されます.output-featureの場合,ルーティング後にNATされるため,アドレス変換されずにパケットをLinuxに送信してくれます.(これに気付くまでにだいぶ沼りました...)

Linux-CPの確認

docker-compose up -dによって起動した後,動作しているのかを確認していきます.

VPP

まず,dockerコマンドを用いてVPPのCLIに入ります.

$ sudo docker compose exec vpp vppctl 
    _______    _        _   _____  ___ 
 __/ __/ _ \  (_)__    | | / / _ \/ _ \
 _/ _// // / / / _ \   | |/ / ___/ ___/
 /_/ /____(_)_/\___/   |___/_/  /_/    

vpp#

show lcpにより,LCPの状態を確認します.ログの情報からインターフェースが作成できていることがわかります.

vpp# show lcp
lcp default netns 'gateway_frr'
lcp lcp-auto-subint on
lcp lcp-sync on
lcp del-static-on-link-down off
lcp del-dynamic-on-link-down off
itf-pair: [0] GigabitEthernet1/0/0 tap1 g1 3 type tap netns gateway_frr
itf-pair: [1] bvi0 tap6 bvi0 4 type tap netns gateway_frr
itf-pair: [2] GigabitEthernet2/0/0 tap2 g2 5 type tap netns gateway_frr

また,インターフェースを見てみます.後半にあるtap1tap2tap6は,Linux-CPでホストに作成されたインターフェースとVPPのインターフェースをつなぐタップです.

vpp# show interface  
              Name               Idx    State  MTU (L3/IP4/IP6/MPLS)     Counter          Count     
GigabitEthernet1/0/0              1      up          2026/0/0/0     rx packets              11539883
                                                                    rx bytes             14131659473
                                                                    tx packets               3535092
                                                                    tx bytes               688165622
                                                                    drops                      17619
                                                                    ip4                     11531033
                                                                    ip6                         5323
GigabitEthernet2/0/0              2      up          2026/0/0/0     rx packets                639080
                                                                    rx bytes               649147083
                                                                    tx packets                172489
                                                                    tx bytes                20954253
                                                                    drops                          1
                                                                    ip4                       638180
GigabitEthernet3/0/0              3      up          2026/0/0/0     rx packets               1838881
                                                                    rx bytes               399652609
                                                                    tx packets               7293863
                                                                    tx bytes              8999871920
                                                                    drops                      53016
                                                                    tx-error                      35
GigabitEthernet4/0/0              4      up          2026/0/0/0     rx packets               2400463
                                                                    rx bytes               390620683
                                                                    tx packets               5285460
                                                                    tx bytes              5842453495
                                                                    tx-error                      68
bvi0                              6      up          9000/0/0/0     rx packets               4161276
                                                                    rx bytes               731518599
                                                                    tx packets              12136823
                                                                    tx bytes             14775726321
                                                                    drops                     364964
                                                                    ip4                      3917227
                                                                    ip6                       134021
local0                            0     down          0/0/0/0       
tap0                              9      up          9000/0/0/0     rx packets                371424
                                                                    rx bytes                52876802
                                                                    tx packets                905929
                                                                    tx bytes               147537278
                                                                    drops                         85
tap1                              5      up          2026/0/0/0     tx packets                   162
                                                                    tx bytes                    9792
tap2                              8      up          2026/0/0/0     rx packets                 24839
                                                                    rx bytes                 2041378
                                                                    tx packets                 25071
                                                                    tx bytes                 2066727
                                                                    drops                          4
                                                                    ip4                        24838
tap6                              7      up          9000/0/0/0     rx packets                  1209
                                                                    rx bytes                  113646
                                                                    tx packets                122817
                                                                    tx bytes                26181866
                                                                    ip4                          806
vpp# 

FRR

FRRも同様に,dockerコマンドを使用して,CLIに入ります.

$ sudo docker compose exec frr vtysh

Hello, this is FRRouting (version 8.4_git).
Copyright 1996-2005 Kunihiro Ishiguro, et al.

gateway_frr#

FRRのネームスペース上に,VPPで設定されたインターフェースとIPアドレスが確認できます.これにより,VPPとFRR間のインターフェース連携ができていることがわかります.

gateway_frr# show int b      
Interface       Status  VRF             Addresses
---------       ------  ---             ---------
bvi0            up      default         192.168.10.1/24
g1              up      default         10.14.167.117/27
g2              up      default         192.168.0.1/24
lo              up      default         
pimreg          up      default         

gateway_frr# 

OSPFの動作

次にOSPFについて見ていきます.

frr.confのOSPF設定

これまでのFRRにおいて,frr.confに以下の内容を追記します.これにより,OSPFを有効化します.また,/frr/daemonsでospfdを有効化しているものとします.

./frr/etc/frr/frr.conf
interface bvi0
  ip ospf area 0.0.0.0
  ip ospf passive
!
interface g1
  ip ospf area 0.0.0.0
  ip ospf passive
!
interface g2
  ip ospf area 0.0.0.0
  ip ospf mtu-ignore
!
router ospf
!
line vty
!
end

GigabitEthernet2/0/0 (g2)に接続するルータ(今回はNEC IX2215を使用)においてもOSPFを設定して,OSPFのルートが学習でき,VPPに反映されているのかを確認します.

ルートの確認

FRRにおいて,OSPFのネイバー関係が確立できていることと,ルートを学習していることを確認します.今回は192.168.1.0/24のルートをOSPFにより新しく取得しています.

gateway_frr# show ip ospf nei

Neighbor ID     Pri State           Up Time         Dead Time Address         Interface                        RXmtL RqstL DBsmL
192.168.10.129    1 Full/DR         2d19h52m          38.053s 192.168.0.2     g2:192.168.0.1                       0     0     0

gateway_frr# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR,
       f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup
       t - trapped, o - offload failure

O   10.14.167.96/27 [110/100] is directly connected, g1, weight 1, 2d19h52m
C>* 10.14.167.96/27 is directly connected, g1, 2d19h52m
O   192.168.0.0/24 [110/100] is directly connected, g2, weight 1, 2d19h52m
C>* 192.168.0.0/24 is directly connected, g2, 2d19h52m
O>* 192.168.1.0/24 [110/101] via 192.168.0.2, g2, weight 1, 2d19h52m
O   192.168.10.0/24 [110/10] is directly connected, bvi0, weight 1, 2d19h53m
C>* 192.168.10.0/24 is directly connected, bvi0, 2d19h53m
gateway_frr# 

192.168.1.0/24のルートがVPP側にも反映されていることを確認します.show ip fibにより,ルートが表示されていることから,VPPとFRRの連携ができていると確認できます.

vpp# show ip fib 192.168.1.0
ipv4-VRF:0, fib_index:0, flow hash:[src dst sport dport proto flowlabel ] epoch:0 flags:none locks:[adjacency:1, default-route:1, lcp-rt:1, nat-hi:2, ]
192.168.1.0/24 fib:0 index:24 locks:2
  lcp-rt-dynamic refs:1 src-flags:added,contributing,active,
    path-list:[41] locks:2 flags:shared, uPRF-list:37 len:1 itfs:[2, ]
      path:[51] pl-index:41 ip4 weight=1 pref=20 attached-nexthop:  oper-flags:resolved,
        192.168.0.2 GigabitEthernet2/0/0
      [@0]: ipv4 via 192.168.0.2 GigabitEthernet2/0/0: mtu:2026 next:7 flags:[] 0060b9fdff8960beb412e3610800

 forwarding:   unicast-ip4-chain
  [@0]: dpo-load-balance: [proto:ip4 index:25 buckets:1 uRPF:37 to:[146991:16828951]]
    [0] [@5]: ipv4 via 192.168.0.2 GigabitEthernet2/0/0: mtu:2026 next:7 flags:[] 0060b9fdff8960beb412e3610800
vpp# 

おわりに

まだまだ遊べそうなので,他のプロトコルとかを試したいな~.

参考文献

Discussion