VPP+Linux-CP(+FRR)環境のHomeGatewayをdocker-composeで作った話
はじめに
以前にVPPのHomeGatewayを立てていたのですが,最近アップデートしました.具体的には,VPPの環境をDockerで構築して,FRRとLinux-CPを用いて連携させました.VPPとFRRを連携させるときには,Dockerのネットワークネームスペースを利用するようにしました.誰かの参考になるかもしれないので共有.(普通にネームスペース作って,そこでFRR動かすほうが楽ですが…)
Linux-CPとは?
Linuxをコントロールプレーンとして動作させるプラグインです.VPPとLinux間でインターフェースを同期したり,Linuxのルート情報をVPPに反映させたりします.
以下の資料が詳しいです.本記事では,Linux-CPを使ってFRRとVPPを連携させます.
構成
基本構成は図のようになっています.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
にルータの構築に関する設定が書かれています.重要なサービスとして,frr
,setup_ns
,vpp
を説明します.
- frr
- FRRを動作させるDockerコンテナ
-
network
をnone
として孤立したネットワークネームスペースを作成し,後述するsetup_ns
でVPPと連携
- setup_ns
- FRRのネットワークネームスペースを
/var/run/netns
に配置するコンテナ - FRRのプロセスを検索するために
docker
コマンドを使用するので/usr/bin/docker
をホストと共有
- FRRのネットワークネームスペースを
- vpp
- VPPを動作させるコンテナ
- FRRのネットワークネームスペースとLinux-CPでF連携
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/
- /dev/:/dev/
- ./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からこのネットワークネームスペースにアクセスできるようになります.
#!/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は以前と同様にインストールしているだけです.
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を起動します.
#!/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
セクションにおいて,名前空間の指定等を行っていきます.
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を設定しています.
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
また,インターフェースを見てみます.後半にあるtap1
,tap2
,tap6
は,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を有効化しているものとします.
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#
おわりに
まだまだ遊べそうなので,他のプロトコルとかを試したいな~.
参考文献
- VPP Linux CP - Part1
- Linux Control Plane Integration
- Linux-cp cli reference
- github.com/pimvanpelt/lcpng
- Wakamonog13 LT: VPPで始めるHigh Performance BGPルーター
- Learning VPP: Bypassing IKEv2 NAT-T using IPsec policy
- #plugin #vpp Linux-cp multicast issue
- ubuntu 22.04にFD.io VPP v23.02を使ってNAPT Gatewayを構築した時のメモ
- How to Access Docker Container’s Network Namespace from Host
Discussion