🌐

LinuxでIn-situ OAM (IOAM)に入門してみた話

2023/11/27に公開

修士の研究中に,パケットの経路や遅延を測定するための技術としてIOAMを知り興味を持ちました.LinuxでIOAMが実装されているので,IOAMを試してみようとしたときの備忘録的なやつです.

IOAMとは?

IOAMは,In-situ Operation, Administration, and Maintenance (In-situ OAM)の略です.OAMは,障害への対処や性能測定といった運用管理技術を表します.In-situは,単語としては所定の場所に置くことを意味し,IOAMではパケット内にOAM情報を組み込んで転送することを指します.IOAMでは,パスの情報や各ノード情報(ID,タイムスタンプ,キューサイズ等)をIOAMオプションデータとしてパケット内に格納して,テレメトリ情報を収集します.

以下のように書くルータでOAM情報(NodeIDやInterfaceID等)を追加していきます.

IOAMのデータフォーマットとIOAMのデータを運ぶヘッダは分けて定義しています.IOAMが利用できるヘッダにはNetwork Service Header (NSH)や,IPv6オプションヘッダ(Hop-by-Hop Optionsや宛先オプション,SRHなど)があります.

IOAMのノード

IOAMでは,3つの種類のノードがあります.

  • IOAM Encapsulating Node
    • パケットに少なくとも1つのIOAMオプションを追加するノード
  • IOAM Decapsulating Node
    • IOAMオプションを削除するノード
  • IOAM Transit Node
    • IOAMオプションを認識でき,IOAMオプションの読み書きなどの処理を行うノード

IOAM Option-Type

IOAM Option-Typeは,RFC9197に4種類定義されています.また,RFC9326でDEXオプションが追加されています.

  • Pre-allocated Trace
    • 予めスペースが割り当てられたIOAMデータのコンテナのタイプ.
  • Incremental Trace
    • スペースは割り当てられておらず,各ノードが新たにスペースを確保してデータをPUSHするタイプ
  • Proof of Transit (POT)
    • Service Function Chaining (SFC) の検証のためのタイプ
  • Edge-to-Edge (E2E)
    • IOAM Encapsulating Nodeによって追加され,IOAM Decapsulation Nodeで処理されるタイプ.IOAM Transit Nodeでは,読み取ることはできるが,変更することはできない.
  • Direct Export (DEX)
    • IOAMのデータを直接エクスポートするためのトリガーとなるタイプ

Pre-allocated TraceとIncremental Traceのフォーマット

Pre-allocated TraceとIncremental Traceは,パケットにIOAM領域を事前に確保するかどうかの違いであり,使われるデータのフォーマットは同じです.

以下に,Trace Optionのヘッダーを示します.IOAM Trace-Typeには,ヘッダの後に続くデータの種類を表します.

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        Namespace-ID           |NodeLen  | Flags | RemainingLen|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               IOAM Trace-Type                 |  Reserved     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
(出典: https://datatracker.ietf.org/doc/html/rfc9197)

ヘッダの後に続くデータは,以下のようにリストで追加されていきます.このリストに,ノードIDやインターフェースID,タイムスタンプをスタックします.

`+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+<-+
|                                                               |  |
|                        node data list [0]                     |  |
|                                                               |  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  D
|                                                               |  a
|                        node data list [1]                     |  t
|                                                               |  a
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~                             ...                               ~  S
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  p
|                                                               |  a
|                        node data list [n-1]                   |  c
|                                                               |  e
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  |
|                                                               |  |
|                        node data list [n]                     |  |
|                                                               |  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+<-+`
(出典: https://datatracker.ietf.org/doc/html/rfc9197)

data listにどのようなデータを格納するかはIOAM Trace Typeで指定します.

IOAM Trace Type

IOAM Trace Typeは,ノードリストに格納するデータのタイプを指定するための識別子です.各ビットがフラグとなっており,ビットフィールドとして定義されています.

bit 意味
0 Hop_Lim and node_id (short format)
1 ingress_if and egress_if (short format)
2 timestamp (秒)
3 timestamp (小数)
4 Transit delay
5 namespace-specific data (short format)
6 queue depth
7 checksum complement
8 Hop_Lim and node_id (wide format)
9 ingress_if_id and egress_if_id (wide format)
10 namespace-specific data (wide format)
11 buffer occupancy
22 ~ 21 未定義
22 可変長Opaque State Snapshot
23 予約済み

各ノードでは,TraceTypeで指定されたデータをdata listに追加していきます.

例えば,ingress_if and egress (short)では,以下の情報をdata listに追加します.

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     ingress_if_id             |         egress_if_id          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
(出典: https://datatracker.ietf.org/doc/html/rfc9197)

NodeIDとInterfaceIDといった項目では,Short format と Wide formatがあり,それぞれ設定する必要があります.

LinuxでのIPv6 IOAM実装

Linuxでは,IPv6 IOAMが実装されています.この実装では,IPv6 Hop-by-Hop Optionsヘッダを使用して,IOAMデータを格納します.

IOAMの有効化

まず,IOAMによるカプセル化に対応しているかを確認します.

$ grep -i CONFIG_IPV6_IOAM6_LWTUNNEL /boot/config-$(uname -r)
CONFIG_IPV6_IOAM6_LWTUNNEL=y

次に必要なインターフェースでIOAMを有効化します.

# sysctl -w net.ipv6.conf.<interface>.ioam6_enabled=1
sysctl -w net.ipv6.conf.eth1.ioam6_enabled=1

Node IDとInterface IDの設定

NodeIDの設定は,sysctlで以下のように設定します.

sysctl -w net.ipv6.ioam6_id=1
sysctl -w net.ipv6.ioam6_id_wide=0x11111111

InterfaceIDの設定では,以下のように各インターフェースに設定します.

sysctl -w net.ipv6.conf.r1_r2.ioam6_id=0x0102
sysctl -w net.ipv6.conf.r1_r2.ioam6_id_wide=0x01020102

トンネリングの設定

IOAMでトンネリングを有効化するためには以下の設定が必要です.ip6_tunnelモジュールを読み込み,リンクを立ち上げます.

modprobe ip6_tunnel
ip link set ip6tnl0 up

Namespaceの設定

Namespaceは,オプションヘッダの処理する必要があるかどうかや,オペレーションの操作範囲を識別するために使用します.

新しいNamespace(ID=123)を追加する場合は,以下のコマンドを実行します.

# ip ioam namespace add <NamespaceID> [data <data(short)>] [wide <data(wide)>]
ip ioam namespace add 123

IPv6 IOAM のルート設定

カプセル化する場合,IOAM Encapsulating Nodeで以下のようにルートを設定します.

$ ip -6 route add <prefix> encap ioam6 mode encap tundst <distination> trace prealloc type <IOAM-Trace-Type> ns <NamespaceID> size <IOAM Data size> dev <link>

Linuxで試してみる

では,実際に以下のネットワークでIOAMの動作を確認します.これは,LinuxのNamespaceを用いて作成します.
以下のネットワークにおいて,R1 => R2 => R4の経路でIOAMがどのように動作するかを見ます.
なお,試したLinuxのバージョンは6.2です.

Namespaceの設定全文は,以下のGithubを参照してください.
https://github.com/shu1r0/netns_network_examples/tree/main/ioam

ここで,R1では以下のような設定を行っています.ip netns exec r1を省いていますが,r1のネームスペース上で実行しています.

sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1
ip -6 addr add fd00:10::1/48 dev r1_h1
ip -6 addr add fd00:12::1/48 dev r1_r2
ip -6 addr add fd00:13::1/48 dev r1_r3

# Enable ioam
sysctl -w net.ipv6.conf.r1_h1.ioam6_enabled=1
sysctl -w net.ipv6.conf.r1_r2.ioam6_enabled=1
sysctl -w net.ipv6.conf.r1_r3.ioam6_enabled=1

# Enable ioam ip tunnel
modprobe ip6_tunnel
ip link set ip6tnl0 up

# Set Node id
sysctl -w net.ipv6.ioam6_id=1
sysctl -w net.ipv6.ioam6_id_wide=0x11111111

# Set Interface id
sysctl -w net.ipv6.conf.r1_r2.ioam6_id=0x0102
sysctl -w net.ipv6.conf.r1_r2.ioam6_id_wide=0x01020102
sysctl -w net.ipv6.conf.r1_r3.ioam6_id=0x0103
sysctl -w net.ipv6.conf.r1_r3.ioam6_id_wide=0x01030103

# Set Namespase id
ip netns exec r1 ip ioam namespace add 100 data 0xbeefcafe wide 0xbeefbeefcafecafe

以上のネットワークで,TraceTypeを変えてpingを実行してみます.

TraceType 0x800000

このタイプは,前から1ビット目を1とするため,Hop_Lim and node_id (short format)を指定します.以下のようにルートを設定して,pingを送信します.ノードは2個しか経由しないので,4bytesのデータ×2でsizeを8としています.

$ ip netns exec r1 ip -6 route replace fd00:20::/48 encap ioam6 mode encap tundst fd00:24::2 trace prealloc type 0x800000 ns 100 size 8 dev r1_r2
$ ip netns exec r4 ip -6 route replace fd00:10::/48 encap ioam6 mode encap tundst fd00:12::1 trace prealloc type 0x800000 ns 100 size 8 dev r4_r2
$ ip netns exec h1 ping -c 2 fd00:20::2
PING fd00:20::2(fd00:20::2) 56 data bytes
64000 bytes from fd00:20::2: icmp_seq=1 ttl=62 time=0.173 ms
64 bytes from fd00:20::2: icmp_seq=2 ttl=62 time=0.084 ms

--- fd00:20::2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1017ms

r4のインターフェースr4_r2でパケットキャプチャすると,以下のようなパケットを取得できます.

TraceType 0x440000

このタイプは,前から2ビット目と6ビット目を1とするため,ingress_if and egress_if (short format)とnamespace-specific data (short format)を指定します.以下のようにルートを設定して,pingを送信します.

$ ip netns exec r1 ip -6 route replace fd00:20::/48 encap ioam6 mode encap tundst fd00:24::2 trace prealloc type 0x440000 ns 100 size 24 dev r1_r2
$ ip netns exec r4 ip -6 route replace fd00:10::/48 encap ioam6 mode encap tundst fd00:12::1 trace prealloc type 0x440000 ns 100 size 24 dev r4_r2
$ ip netns exec h1 ping -c 2 fd00:20::2
PING fd00:20::2(fd00:20::2) 56 data bytes
64 bytes from fd00:20::2: icmp_seq=1 ttl=62 time=0.068 ms
64 bytes from fd00:20::2: icmp_seq=2 ttl=62 time=0.121 ms

--- fd00:20::2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1016ms
rtt min/avg/max/mdev = 0.068/0.094/0.121/0.026 ms

r4のインターフェースr4_r2でパケットキャプチャすると,以下のようなパケットを取得できます.

TraceType 0x8000002

このタイプは,前から1ビット目と後ろから2ビット目を1とするため,hop_lim and node_id (short format)と可変長Opaque State Snapshotを指定します.

可変長Opaque State Snapshotのデータは以下のように設定します.

ip netns exec r1 ip ioam schema add 0x111 oam1
ip netns exec r1 ip ioam namespace set 100 schema 0x111
ip netns exec r2 ip ioam schema add 0x222 oam2
ip netns exec r2 ip ioam namespace set 100 schema 0x222

r2で設定されたデータを確認します.

$ ip netns exec r2 ip ioam schema show
schema 546 [namespace 100], data: 6f 61 6d 32

以下のようにルートを設定して,pingを送信します.

$ ip netns exec r1 ip -6 route replace fd00:20::/48 encap ioam6 mode encap tundst fd00:24::2 trace prealloc type 0x800002 ns 100 size 24 dev r1_r2
$ ip netns exec r4 ip -6 route replace fd00:10::/48 encap ioam6 mode encap tundst fd00:12::1 trace prealloc type 0x800002 ns 100 size 24 dev r4_r2
$ ip netns exec h1 ping -c 2 fd00:20::2
PING fd00:20::2(fd00:20::2) 56 data bytes
64 bytes from fd00:20::2: icmp_seq=1 ttl=62 time=0.067 ms
64 bytes from fd00:20::2: icmp_seq=2 ttl=62 time=0.068 ms

--- fd00:20::2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1013ms

r4のインターフェースr4_r2でパケットキャプチャすると,以下のようなパケットを取得できます.

おわりに

今回はIOAM情報を付加しているだけで収集してないので,今後は,IOAM情報を収集するシステムを調べていきたいです.
また,IOAMは面白い技術ではあると思うのですが,利用例まだまだ少ないと思うので,今後に注目していきたいです.

参考文献

Discussion