OVS(Open vSwitch) 3.2でSRv6がサポートされた 1 ので使ってみる。 その使い方を端的にいうと以下のように type=srv6 としたポートを作成すれば良い。 VXLANやGeneveなど既存のトンネリングと同様のフレームワークを使って実装されているので、 options:remote_ipoptions: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_0ovs-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_ipremote_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程度。