OVS(Open vSwitch) 3.2でSRv6がサポートされた 1 ので使ってみる。
その使い方を端的にいうと以下のように type=srv6
としたポートを作成すれば良い。
VXLANやGeneveなど既存のトンネリングと同様のフレームワークを使って実装されているので、
options:remote_ip
やoptions:local_ip
でトンネルの両端(SRv6では双方のSIDに相当)を指定する。
それらに加えてSRv6では中継するルータをSegment Listとして設定できるよう特別なオプション options:srv6_segs
が存在する。
ちなみにInner PacketとしてはIPv4、IPv6両方をサポートしている。
ovs-vsctl add-br br0
ovs-vsctl add-port br0 srv6_0 -- \
set int srv6_0 type=srv6 \
options:remote_ip=fc00:100::1 \
options:srv6_segs="fc00:100::1,fc00:200::1,fc00:300::1"
OVSでは主にkernelspaceとuserspaceの2種類のデータパスが存在しているが、 SRv6はuserspaceのみ対応している 2。 つまりDPDKやAFXDP 3 のような仕組みを使ってデプロイする必要がある。
Feature | Linux upstream | Linux OVS tree | Userspace |
---|---|---|---|
GRE | 3.11 | 1.0 | 2.4 |
VXLAN | 3.12 | 1.10 | 2.4 |
Geneve | 3.18 | 2.4 | 2.4 |
… | … | … | … |
SRv6 | NO | NO | 3.2 |
さてどうやって動かすのか。 mininetのスクリプト 4 をベースとしてそれをシンプルなコマンド列に落とし込んだ。 以下のような構成で動かしてみる。
まずOVS 3.2以上が動作していることを確認する。
またこの記事ではAFXDPを使っているので、コンパイル時に ./configure --enable-afxdp
としておく。
ovs-vswitchd -V
# ovs-vswitchd (Open vSwitch) 3.2.0
# DPDK 22.11.1
続いてvethペアを2つ作る。 片方がunderlay network、もう一方がoverlay networkに相当する。
ip link add p1 type veth peer name ovs-p1
ip link add p2 type veth peer name ovs-p2
続いて OVS にブリッジを2つ作る。
こちらも同じく片方がunderlay network、もう一方がoverlay networkに相当する。
userspaceでデータパスを動かすためにdatapath_type=netdev
とする。
今回はScapyでハードコードされたパケットを使って実験するため、MACアドレスを固定しておく。
ovs-vsctl -- add-br br1 -- set Bridge br1 protocols=OpenFlow10 \
fail-mode=secure datapath_type=netdev
ovs-vsctl -- add-br br2 -- set Bridge br2 protocols=OpenFlow10 \
fail-mode=secure datapath_type=netdev
ovs-vsctl set bridge br2 other_config:hwaddr=aa:55:aa:55:00:00
それぞれのブリッジにポートを割り当てていく。 今回はafxdpでパケットをuserpsaceに引き上げている。もちろんDPDKを使っても良い。
ovs-vsctl add-port br1 ovs-p1 -- set interface ovs-p1 type="afxdp"
ovs-vsctl add-port br2 ovs-p2 -- set interface ovs-p2 type="afxdp"
ブリッジ br1
にはtype=srv6
なポートを作成しておく。
ovs-vsctl add-port br1 srv6_0 -- set interface srv6_0 type=srv6 \
options:local_ip=fc00:100::100 options:remote_ip=fc00:100::1
ポートの設定はここで終わり。以下のような状態になっているはず。
ovs-vsctl show
# a1fee738-93fe-42ee-8420-a283807c3d87
# Bridge br1
# fail_mode: secure
# datapath_type: netdev
# Port br1
# Interface br1
# type: internal
# Port ovs-p1
# Interface ovs-p1
# type: afxdp
# Port srv6_0
# Interface srv6_0
# type: srv6
# options: {local_ip="fc00:100::100", remote_ip="fc00:100::1"}
# Bridge br2
# fail_mode: secure
# datapath_type: netdev
# Port br2
# Interface br2
# type: internal
# Port ovs-p2
# Interface ovs-p2
# type: afxdp
# ovs_version: "3.2.0"
続いてフロールールを投入する。
単純に ovs-p1 — srv6_0
と ovs-p2 - LOCAL
の間でトラフィックが流れるよう設定した。
ovs-ofctl add-flow br1 in_port=ovs-p1,actions=output:srv6_0
ovs-ofctl add-flow br1 in_port=srv6_0,actions=output:ovs-p1
ovs-ofctl add-flow br2 in_port=LOCAL,actions=output:ovs-p2
ovs-ofctl add-flow br2 in_port=ovs-p2,actions=output:LOCAL
全てのポートをup状態にしておく。
for iface in ovs-p1 ovs-p2 br1 br2 p1 p2; do
ip link set dev $iface up
done
最後にlocal_ip
をブリッジbr2
に割り当て、さらにremote_ip
用のMACアドレスを静的に設定しておく。
もちろんlocal_ip
とremote_ip
が異なるサブネットに所属する場合にはその経路を設定する。
ip -6 addr add fc00:100::100/64 dev br2
ovs-appctl tnl/arp/set br2 fc00:100::1 aa:55:aa:55:00:01
準備ができたので、パケットを投げてみる。 p1 -> p2 の方向でEncapされることを確認できる。 期待通りSRHが挿入されている。(注目したいフィールドだけ抜粋)
python3 -c "from scapy.all import *; \
pkt=Ether(dst='aa:55:aa:55:00:ff',src='aa:55:aa:55:00:ee') \
/IP(dst='192.168.1.1',src='192.168.3.3')/ICMP(); \
sendp(pkt, iface='p1')"
tcpdump -i p2 -w p2.pcap
tshark -V -r p2.pcap
# Frame 1: 106 bytes on wire (848 bits), 106 bytes captured (848 bits)
# Ethernet II, Src: aa:55:aa:55:00:00 (aa:55:aa:55:00:00), Dst: aa:55:aa:55:00:01 (aa:55:aa:55:00:01)
# Internet Protocol Version 6, Src: fc00:100::100, Dst: fc00:100::1
# Routing Header for IPv6 (Segment Routing)
# Next Header: IPIP (4)
# Length: 2
# [Length: 24 bytes]
# Type: Segment Routing (4)
# Segments Left: 0
# Last Entry: 0
# Flags: 0x00
# Tag: 0000
# Address[0]: fc00:100::1
# Internet Protocol Version 4, Src: 192.168.3.3, Dst: 192.168.1.1
# Internet Control Message Protocol
逆方向の場合にはDecapされることを確認できる。
python3 -c "from scapy.all import *; \
pkt=Ether(src='aa:55:aa:55:00:01',dst='aa:55:aa:55:00:00') \
/IPv6(src='fc00:100::1', dst='fc00:100::100') \
/IPv6ExtHdrSegmentRouting(addresses=['fc00:100::100']) \
/IP(dst='192.168.5.5',src='192.168.5.6')/ICMP(); \
sendp(pkt, iface='p2')"
tcpdump -i p1 -w p1.pcap
tshark -V -r p1.pcap
# Frame 1: 42 bytes on wire (336 bits), 42 bytes captured (336 bits)
# Ethernet II, Src: 00:00:00_00:00:00 (00:00:00:00:00:00), Dst: 00:00:00_00:00:00 (00:00:00:00:00:00)
# Internet Protocol Version 4, Src: 192.168.5.6, Dst: 192.168.5.5
# Internet Control Message Protocol
おしまい。 ちなみにカーネルデータパスについては未実装である。 マイクロベンチマークについては動画 5 で触れている。 ざっくりとマルチフローの場合に20Mpps、シングルフローの場合に4~7Mpps程度。