OVSで実現するERSPANデスティネーションの構築（冗長化もあるよ）/2.6MB GIF動画アリ

## 概要

*本ドキュメントは、公式ドキュメント上の実装例と冗長化のアイデアについて実現性を検証した結果を備忘録的に残したものとなります。*

```ascii:ERSPAN

+---------------+                               +--------------------+
| ERSPAN SOURCE +--------[L3/IP Network]------->+ ERSPAN DESTINATION |
+---------------+                               +--------------------+
```

某社のスイッチにてERSPANというパケットミラーリングのプロトコルを使用することとなった。ERPSANではミラーしたパケットをトンネリングプロトコルで包み、ネットワークを超えて転送する。

このときERSPANを終端しキャプチャ装置にパケットを転送する仕組みが何らか必要となる。

パケットキャプチャソリューションの一部や、一部のルータ等においては、ERSPANを終端する機能を有するが、いずれも高価である。

そこでOSS仮想スイッチである、Open vSwitchを用いてERPSANを終端するための実装を検討した。また実装に当たっては商用での稼働も検討し、冗長化についての検討も行った。

数秒単位での冗長化切替を実現できる、Open vSwitchでの実装が可能であることを確認した結果をここに記す。

## はじめに

### ERSPANとは

ERSPANはスイッチでキャプチャしたパケットをネットワークを超えて転送するためのCisco社が提唱・実装しているプロトコルである(Internet draftにもなっている[^1])。
[^1]: https://tools.ietf.org/html/draft-foschiano-erspan-03

ERSPANではスイッチでミラーしたパケット（Ethernetフレーム）をトンネリングプロトコルであるGREを用いてカプセリング化し、トンネルエンドポイントに指定されたリモートのIPアドレスに向けてミラーされたパケット（Ethernetフレーム）を運ぶ。

この時、GREヘッダと、ミラーされたErhternetフレームの間にERSPANヘッダを差し込む、結果プロトコルスタックとしては以下のようになる。

```ascii-art
+---------------+
| Some IP       |
|      Playload |
+---------------+
| IP(Inner)     |
+---------------+
|Ethernet(Inner)|
+---------------+
| ERSPAN        |
+---------------+
| GRE           |
+---------------+
| IP(Outer)     |
+---------------+
|Ethernet(Outer)|
+---------------+
| Physical      |
+---------------+
```

ERSPANにはいくつかバージョンが存在するが、ここではその説明は割愛する。

### ERSPANデスティネーションについて

上述の通り、ERSPANにおいてはGREによりパケットを転送する。 ERSPANのパケットはGREヘッダが付けられたIPパケットとして、ただ単純ににFIBテーブルに従い転送し、IPネットワークの中では通常のIPパケットとして処理される。

デスティネーションにおいてはパケットキャプチャ装置等に向けてこのGREおよびERSPANヘッダを取り除いたパケットを転送する。

今回このERSPANデスティネーションをOSSを用いて実装する。

### Open vSwitchのERSPAN対応

Open vSwitch（以下OVS）では ver 2.10 以上 かつ Linux kernel 4.18以上で ERPSPAN プロトコルに対応している[^2]。そのため、今回はOVSを用いた実装について検討する。

[^2]: http://docs.openvswitch.org/en/latest/faq/releases/  

## 環境について

実際の設定例に先立ち、検証に使用したハードウェアやソフトウェアの情報と最終的にFixした環境を紹介する。

### ハードウェア/VMおよび使用したソフトウェアの情報

OVSを用いる部分については2020年現在、OSのディストリビューションに付属しているOVSが2.13.0と比較的新しいリリースのUbuntu 20.04を用いている。

- Hypervisor（以後ホストOVSと呼称）
    - CPU: AMD Ryzen 5 3600 (6コア 12スレッド)
    - Memory: 16GB
    - OS: Ubuntu 20.04 LTS
        - Linux kernel 5.4.0
    - OVS: 2.13.0 (DPDK datapath)
        - 別の検証目的でDPDKのOVSをテストしていた環境のためホストOVSはDPDK版を使用

- OVS VM(2 VMs/ 以後ゲストOVSと呼称)
    - CPU: 2 Cores
    - Memory: 1 GB
    - OS: Ubuntu 20.04 LTS
        - Linux kernel 5.4.0
    - OVS: 2.13.0
    - keepalaived: 2.0.19

- Traffic VM(2 VMs)
    - CPU: 2 Cores
    - Memory: 1 GB
 
### VM配置およびそれぞれのVMの役割

少々いびつな構成ではあるが、ハイパーバイザにKVMを用いて構築した仮想マシン上のゲストOVSと物理マシンにインストールされたホストOVSを組み合わせて検証環境を構築した。

ハイパーバイザにインストールされたホストOVSがキャプチャ元スイッチ（ERSPANソース）を疑似し、VM上に構築されたゲストOVSがERSPANデスティネーションを疑似する。

Traffic VMはホストOVSによって接続されている。このTraffic VM間でPingによる通信を発生させ、その通信をホストOVS側の設定においてミラーリングし、ゲストOVSにERSPANを用いて転送する。ゲストOVSではホストOVSから送られてきたERSPANのパケットを解き、ゲストOVS内でパケットを所定のポートへミラーする。

ゲストOVSは冗長構成を検討するために２台用意し、アクティブ・スタンバイ構成でのHA構成を取る。

```ascii:一つのハイパーバイザ上で構築
  (Guest OVS)       (Guest OVS)
+--------------+  +--------------+
|ERSPAN Dest 1 |  |ERSPAN Dest 2 |
+--------------+  +--------------+  +--------------+  +--------------+
| Open vSwitch |  | Open vSwitch |  | Traffic VM 1 |  | Traffic VM 2 |
+--------------+  +--------------+  +--------------+  +--------------+
| Ubuntu 20.04 |  | Ubuntu 20.04 |  | Linux        |  | Linux        |
+--------------+  +--------------+  +--------------+  +--------------+
|Guest Machine |  |Guest Machine |  |Guest Machine |  |Guest Machine |
+-------+------+  +-------+------+  +-------+------+  +-------+------+
        |(vhost user)     |(vhost user)     |(vhost user)     |(vhost user)
+-------+-----------------+-----------------+-----------------+------+
| KVM / Open vSwitch 2.13.0 with DPDK datapath                       |  (Host OVS)
+--------------------------------------------------------------------+
| Ubuntu 20.04 LTS / Kernel 5.4.0                                    |
+--------------------------------------------------------------------+
| Host Machine AMD Ryzen 5 3600 / 16GB RAM                           |
+--------------------------------------------------------------------+
```

### ホストOVS上のネットワーク構成

ホストOVSとゲストOVSの、それぞれの間はL2で同一セグメントで接続する。

また、Traffic VM間でホストOVSを経由したPingを発生させ、ホストOVSを通過したPingパケットをERSPANでミラーリングし、ゲストOVSに転送する構成で試験を行う。

ERSPANデスティネーションとなるゲストOVSはVLAN 5に所属し、キャプチャ対象となるトラフィックを生成するTraffic VMはVLAN 3に所属する。

ERSPANのトンネルポートの送信元IPアドレスを設定するためにVLAN 5にはInternal Portを生成し、IPアドレスを割り当てる。また、ERSPANのトンネルポート自体はホストOVSのVLAN 10に作成している（理由は後述）。

ERSPANデスティネーションとなるゲストOVS同士はVRRPによってVIPを共有する。
ホストOVSはこのVIPに対してのERSPANソースセッションを作成する。

```ascii:ホストOVS内のブリッジ/VLAN構成について(最終形)
              VRRP                                             Ping
       |-----------------|                              |---------------->|
+--------------+  +--------------+               +--------------+  +--------------+
|ERSPAN Dest 1 |  |ERSPAN Dest 2 |               | Traffic VM 0 |  | Traffic VM 1 |
+------+-------+  +------+-------+               +------+-------+  +------+-------+
       | .2(VIP:.10)     | .3(VIP:.10)                  |                 |
+------+-----------------+--+----+  +----+----+  +------+-----------------+-------+
VLAN 5:192.168.50.0/24      |.1          |VLAN 10     V       VLAN 3:192.168.0.0/24
br-int             +--------+----+  +----+----+       |       br-int
                   |Internal Port|  |ERPSAN SP|<------+
                   +-------------+  +---------+  Mirroring Setting from VLAN 3 to ERSPAN SP
        [ERSPAN source session from .1 to .10]

SP: Source-port
DP: Destination-port
```
### ゲストOVS上のネットワーク構成

ゲストOVS内では１つのブリッジを作成し、vNICが所属するVLAN 4と、ミラーされたパケットを流すためのポートが所属する VLAN 2 、 ERSPANデスティネーションに設定されている VLAN 10の三つを用意した。

このOVSのブリッジに対してERSPANデスティネーションセッションを作成し、ERSPANデスティネーショポートからモニターポート向けへのミラー設定を投入する[^3]。

[^3]: 実環境においてはこの モニターポートが物理NICになる、もしくはこの先でキャプチャプログラムがリッスンする形となる。

```ascii:ERSPANデスティネーションVM内のOVSのブリッジ/VLAN構成について(最終形)
                                      Mirroring setting from DP to MP
                                      +----------------+ 
[ERSPAN dest session from .10 to .1]  |                V
    +-------------+              +----+----+     +------------+
    |Internal Port|              |ERSPAN DP|     |Monitor Port|
    +------+------+              +----+----+     +-----+------+
           | .2(VIP:.10)              |                |
+--+-------+------+              +----+----+     +-----+------+
   |     VLAN 4:192.168.50.0/24  VLAN 10         VLAN 2
+--+--+  br0                     br0             br0
|vNIC |
+-----+


DP: Destination-port
MP: Monitor-port
```

## 設定例の記載にあたって

本ドキュメントはERSPANの設定についてのナレッジを残すことを目的としているため、OVSのインストールやKVMでのVMの作成、OVSとVMの接続などはアウトオブスコープとし、ここではホストOVSのブリッジの作成、ポートの作成、VMの作成、VLANの設定等は終わっている前提で進めます。
必要に応じて、公式ドキュメントや、各種チュートリアル等を参照してください。

## ゲストOVS/ERSPANデスティネーションの設定

### 前提条件
```ascii:ERSPANデスティネーションVM内のOVSのブリッジ/VLAN構成について(初期状態)
(Empty)
```

- OVSのインストールは済んでいる
- ERSPANパケットを受信するVMのvNICは``ens4``としてOSに認識されている
- ``ens4``とは別にOSにリモートログインでき、かつインターネットアクセスできるvNICが付与されている（この記載例では``ens3``がそのvNICとなっており、linux bridgeからDHCPでIPアドレスを振られてます）
- 設定についてはほぼ重複するため、二つあるゲストOVSのうち、片側のVMのみ記載しています。適宜読み替えてもう一方のVMにも設定ください。

### ブリッジの追加とブリッジへのvNICの追加

```shell-session
# ovs-vsctl add-br br0 #ブリッジに追加
# ovs-vsctl add-port br0 ens4 tag=4 #ブリッジへの受信ポートの追加
# ovs-vsctl set port br0 tag=4 #内部ポートをVLAN 4所属に変更
# ip a add 192.168.50.2/24 dev br0 #VMによりIPアドレスを変更
# ip link set up br0
# ip link set up ens4
```

またbr0へ付与したIPアドレスの永続化と、ens4の有効化のためにnetplanにより設定を永続化する。

```yaml:/etc/netplan/60-erspan-dest.yaml
network:
    ethernets:
        ens4:
            dhcp4: no
            dhcp6: no
        br0:
            dhcp4: no
            dhcp6: no
            addresses:
            - 192.168.50.2/24 #VMによりIPアドレスを変更
    version: 2
```

### keepalivedによるVRRPの形成

VRRPによる冗長化を組むために``keepalived``をインストールし、設定する。

```shell-session
# apt-get install keepalived 
```

ホストOVSとL2接続している関係からか、ホストOVS側でのトンネルエンドポイントに対するARPテーブルがGarpで消えない事象が発生したため、vMAC方式を使用している。L3接続の場合は不要だと思いますが、どこかでARPテーブルをほじられるのことを考えるとvMACにしておいたほうが安全かもしれません。

```config:/etc/keepalived/keepalived.conf
global_defs {
    router_id 192.168.50.2 #VMによりIPアドレスを変更
    vrrp_skip_check_adv_addr
    vrrp_garp_interval 1
    vrrp_garp_master_refresh  10
    vrrp_gna_interval 10
}

vrrp_instance VI_1 {
    state BACKUP
    accept
    interface br0
    virtual_router_id 51
    use_vmac vrrp51
    advert_int 1
    priority 100
    virtual_ipaddress {
        192.168.50.10/24
    }
}
```

設定を反映。

```shell-session
# systemctl resatrt keepalived
```

2VMとも設定し、うまく行けばVRRPが組めているので確認する。

```shell-session:VRRPでActiveとなているVMでのipコマンドでの確認結果
$ ip a show dev vrrp51
12: vrrp51@br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:00:5e:00:01:33 brd ff:ff:ff:ff:ff:ff
    inet 192.168.50.10/24 scope global vrrp51
       valid_lft forever preferred_lft forever
```

```shell-session:VRRPでStandbyとなているVMでのipコマンドでの確認結果
$ ip a show dev vrrp51
12: vrrp51@br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:00:5e:00:01:33 brd ff:ff:ff:ff:ff:ff
```

### ERSPANデスティネーションの設定

ERSPANのデスティネーションセッションの設定を入れます。

```shell-session
# ovs-vsctl add-port br0 erspan_dp tag=10 -- \
            set Interface erspan_dp type=erspan \
                options:key=1 options:remote_ip=192.168.50.1 \
                options:erspan_ver=2 options:erspan_dir=1 \
                options:erspan_hwid=4  
```

ここで作成したトンネルポートをVLANタグを4でも2でもなく、10としているのは``erspan_dp``が所属するVLANを隔離するためとなる。
VLANを隔離しない（たとえばVLAN 4を設定した）場合、キャプチャされたパケットがERSPANポートから飛び出し、ens4などを経由して外部に流出してしまう。
さらに、;逆にVLAN 4内で流通するブロードキャストやマルチキャストパケット（たとえば、ARPリクエストやDHCPリクエストや、今回内部で使用しているVRRPのアドバタイズなど）がERSPANポートに流入する。その結果今回のERSPAN送信元である、ホストOVS向けにそれらのパケットが流出してしまう。

そのため、ここではVLANを隔離する目的でERSPANポートである ``erspan_dp``をそのほかのVLANとは違うVLANに設定している。

もし他に良い手があればご教授ください…。

### ERSPANセッションのミラールールの設定

ERSPANデスティネーションセッションでカプセル化を解かれたパケットをモニターポートへ吐き出す設定を追加する。

```shell-session
# ovs-vsctl add-port br0 mport tag=2 -- set Interface mport type=internal # モニターポートを追加
# ovs-vsctl --\
           --id=@srcp get port erspan_dp --\
           --id=@dstp get port mport --\
           --id=@m create mirror name=m0 output-port=@dstp select-src-port=@srcp --\
           set bridge br0 mirrors=@m #トンネルポートからモニターポートへのミラー設定を追加
# ip link set up mport
```

必要に応じてモニターポートの永続化設定をnetplanに投入しておく。

```diff:/etc/netplan/60-erspan-dest.yaml
--- a/60-erspan-dest.yaml 2020-07-06 17:03:20.839471511 +0000
+++ b/60-erspan-dest.yaml 2020-07-06 17:02:03.370304770 +0000
@@ -8,5 +8,8 @@
             dhcp6: no
             addresses:
             - 192.168.50.2/24
+        mport:
+            dhcp4: no
+            dhcp6: no
     version: 2
```

以上で、デスティネーション側の設定は終了です。

## ホストOVS/ERSPANソースの設定

### 前提条件
```ascii:ホストOVS内のブリッジ/VLAN構成について（初期状態）
+--------------+  +--------------+             +--------------+  +--------------+
|ERSPAN Dest 1 |  |ERSPAN Dest 2 |             | Traffic VM 1 |  | Traffic VM 2 |
+------+-------+  +------+-------+             +------+-------+  +------+-------+
       |                 |                            |                 |
+------+-----------------+-------+             +------+-----------------+-------+
VLAN 5                                         VLAN 3
br-int                                         br-int
```
- ホストOVSはインストール済み
- ゲストVM類はOVSに接続済み
- OVSのブリッジが作成済みでかつ各VM向けポートにはTag VLANの設定が投入済み

この状態からERSPANに必要な設定を投入する。

### ゲストOVSとホストOVS間のVLAN 5でのネットワーク導通の確保

まず、VLAN 5のブリッジに対してERSPANのソースIPアドレスとなるためのIPアドレスを付与する。
ソースIPを付与するポートの名前は``vlan5``とし、``Internal Port``で作成し、IPアドレスの付与、リンクアップを実施する。

```shell-session
# ovs-vsctl add-port br-int vlan5 tag=5 -- set Interface vlan5 type=internal
# ip a add 192.168.50.1/24 dev vlan5
# ip link set up vlan5
```

この状態で問題なく疎通できていればホスト側からVIPに対してPingが飛ぶはずです。

```shell-session
$ ping 192.168.50.10 -c 3
PING 192.168.50.10 (192.168.50.10) 56(84) バイトのデータ
64 バイト応答 送信元 192.168.50.10: icmp_seq=1 ttl=64 時間=1.55ミリ秒
64 バイト応答 送信元 192.168.50.10: icmp_seq=2 ttl=64 時間=0.196ミリ秒
64 バイト応答 送信元 192.168.50.10: icmp_seq=3 ttl=64 時間=0.215ミリ秒

--- 192.168.50.10 ping 統計 ---
送信パケット数 3, 受信パケット数 3, パケット損失 0%, 時間 2009ミリ秒
rtt 最小/平均/最大/mdev = 0.196/0.654/1.553/0.635ミリ秒
$ ip nei show 192.168.50.10
192.168.50.10 dev vlan5 lladdr 00:00:5e:00:01:33 DELAY
```

netplanで上記設定したVLAN 5へのIPアドレスを永続化する。

```yaml:/etc/netplan/60-network-for-ovs.yam
network:
  ethernets:
    vlan5:
      dhcp4: no
      dhcp6: no
      addresses:
      - 192.168.50.1/24
  version: 2
```

### ERSPANソースセッションの作成

次に、ERSPANソースセッションを作成する。

```shell-session
# ovs-vsctl add-port br-int erspan_sp tag=10 -- \
            set In-terface erspan_sp type=erspan \
            options:key=1 options:remote_ip=192.168.50.10 \
            options:erspan_ver=2 options:erspan_dir=1 \
            options:erspan_hwid=4
```

### ミラリング設定を追加

最後にVLAN 3からerspan_spに向けたキャプチャの設定を追加する[^4]。

[^4]: このキャプチャ設定はVLAN単位でキャプチャする場合の例です。

```shell-session
ovs-vsctl --\
          --id=@p get port erspan_sp --\
          --id=@m create mirror name=m0 select-all=true select-vlan=3 output-port=@p --\
          set bridge br-int mirrors=@m
```

これでキャプチャの設定が完了し、VLAN 3上のトラフィックがすべてERSPANを通じてデスティネーションに届けられているはずです。


## 試験

実際にゲストOVSにて、ホストOVSで流れるパケットがキャプチャできていることおよび、KVMでゲストOVSのVMを強制シャットダウンし、ゲストOVSでキャプチャセッションが切り替わっていることを確認する。

### 動画
今回無駄にGIF動画で実際にキャプチャができている様子、切り替わる様子をまとめた。

- １番目と２番目の窓が、ERSPANデスティネーションVM1台目、2台目それぞれのモニターポートでtcpdumpの実行結果
- ３番目の窓ががLAN 3上でPingを打っているVMのpingの実行結果
- 一番下がホストOVSでの各種操作（状態確認、VMの停止等）

初期状態おいては上から２つ目の窓である、ゲストOVS2においてキャプチャされている。
この上阿智でゲストOVS2で障害を擬似的に発生させ、ゲストOVS1にスイッチオーバされることを確認する。

![試験状況(2.6MB GIFアニメ)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207353/6d265694-e33c-b995-6f07-f85b9643b490.gif)

### MAC学習の確認
ホストOVSでのMAC学習の状況を確認。現在のVIPはポート6の先にいることがわかる。

### キャプチャされたパケットの確認
上から3つ目の窓がVLAN 3上でPingを打っているサーバであり、こちらに出ているシーケンス番号と上段2番目の窓のActive系ゲストOVSのERSPANデスティネーションサーバでキャプチャされた結果を比較している。

### 障害の疑似・デスティネーションサーバの障害
現在Active系であるになっているゲストOVSのVMを``virsh destroy``で殺し、最上段のStandby系ゲストOVSにERSPANのパケットが吸いこまれ、キャプチャを継続できていることが確認できる。

*パケットドロップはPingのカウントにして4パケット、約4～5秒程度の切り替わり時間となりました*

## 最後にMAC学習の確認

ホストOVSでのMAC学習の状況を確認し、VIPはポート5の先に移動したことがわかる。


## まとめ

OVSを用いることでERSPANのデスティネーションセッションを構築でき、パケットキャプチャを実現できることがわかった。また、Keepalivedと組み合わせることで、ERSPANセションを冗長化することも確認できた。

今回の構成においては負荷試験は実施していませんので、どれぐらいのHWスペックでどれぐらいの処理性能を出すことができるかは今後の課題となる。

以上