SRv6 (Segment Routing over IPv6) をMininetで実行してみた



SRv6の実行についての記事が少ないので，自分で書いてみた．Mininetであれば，100行程度で，そこそこのトポロジが作れて簡単にSRv6が試せる！！！おすすめ！！！！

全てのコードはGithubにあります．

https://github.com/shu1r0/try_SRv6_with_mininet

## SRv6とは？
SRv6はSegmentRoutingをIPv6データプレーンで実装したものです．SegmentRoutingは，ソースルーティングを活用したルーティング技術である．SRv6では，IPv6の拡張ヘッダであるSRH(Segment Routing Header)を用いる．SRHは，SRv6ポリシーを表すSegmentListを持つ．このSegmentListにより，パケットの転送方法などを指定できる．

## Mininetとは？
Linuxのネームスペースなどの機能を用いて，ネットワークをエミュレートするためのツール．OpenFlowなどのSDNネットワークをテストするときによく使われる．pythonで簡単にネットワークを構築できる．

## 実行環境
SRv6の基本的な動作はLinuxにも実装されている．Linuxの実装状況は[このSegmentRoutingのサイト](https://www.segment-routing.net/open-software/linux/)で確認できる．
- 環境構築: Vagrant + VirtualBox
- OS: Ubuntu20.04 LTS

Mininetを使用するのでMininetのインストールが必要です．Mininetのインストールなどは[Mininet公式ページ](http://mininet.org/download/#option-2-native-installation-from-source)とか様々なブログに記載されている．

詳しくはGithubのVagrantfileを参照してください．


## SRv6ノードの設定
Mininetのノードクラスを継承したSRv6ノードを作成する．SRv6を実行するための設定を`config`メソッドに書いておけば，Mininetのスタート時に実行してくれる．`config`メソッドで親のメソッドを呼び出していないのは，勝手にIPアドレスなどが割り当てられないようにするためである．

```python
from mininet.node import Node

class SRv6Node(Node):

    def __init__(self, name, **params):
        super().__init__(name, **params)

    def config(self, **params):
        self.cmd("ifconfig lo up")
        self.cmd("sysctl -w net.ipv4.ip_forward=1")
        self.cmd("sysctl -w net.ipv6.conf.all.forwarding=1")
        self.cmd("sysctl -w net.ipv6.conf.all.seg6_enabled=1")
        self.cmd("sysctl -w net.ipv6.conf.all.seg6_require_hmac=0")

        for i in self.nameToIntf.keys():
            self.cmd("sysctl -w net.ipv6.conf.{}.seg6_enabled=1".format(i))
```
`net.ipv4.ip_forward`などのフォワーディングの設定とSRv6を有効化する設定が必要である．また，各インターフェースでもSRv6を有効化する必要があるため，`nameToIntf`のキーを使ってFor文でそれぞれ有効化している．

このクラスは以下のように`addHost`メソッドを使って追加する．
```python
r1 = net.addHost("r1", cls=SRv6Node)
```

## 実行
テスト用のネットワーク構成を以下に示す．ルータはSRv6を有効にしたノードである．今回はSRv6を試すだけなので，ルーティングプロトコルは使用しない．
![topo.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2661127/90f12ef8-34ea-1924-a5bc-141db6fd1530.png)
上のネットワークの設定を反映した最終的なコードは以下に示す．
```python
from mininet.cli import CLI
from mininet.net import Mininet
from mininet.log import setLogLevel
from mininet.node import Node


class SRv6Node(Node):

    def __init__(self, name, **params):
        super().__init__(name, **params)

    def config(self, **params):
        self.cmd("ifconfig lo up")
        self.cmd("sysctl -w net.ipv4.ip_forward=1")
        self.cmd("sysctl -w net.ipv6.conf.all.forwarding=1")
        self.cmd("sysctl -w net.ipv6.conf.all.seg6_enabled=1")
        self.cmd("sysctl -w net.ipv6.conf.all.seg6_require_hmac=0")

        for i in self.nameToIntf.keys():
            self.cmd("sysctl -w net.ipv6.conf.{}.seg6_enabled=1".format(i))


def main():
    setLogLevel("info")
    net = Mininet()

    r1 = net.addHost("r1", cls=SRv6Node)
    r2 = net.addHost("r2", cls=SRv6Node)
    r3 = net.addHost("r3", cls=SRv6Node)
    r4 = net.addHost("r4", cls=SRv6Node)
    r5 = net.addHost("r5", cls=SRv6Node)
    r6 = net.addHost("r6", cls=SRv6Node)

    h1 = net.addHost("h1", ip=None)
    h2 = net.addHost("h2", ip=None)
    h3 = net.addHost("h3", ip=None)
    h4 = net.addHost("h4", ip=None)

    net.addLink(r1, h1,
                intfName1="r1_h1", params1={"ip": "192.168.1.1/24"},
                intfName2="h1_r1", params2={"ip": "192.168.1.2/24"})
    h1.cmd("ip route add default dev h1_r1 via 192.168.1.1")
    
    net.addLink(r1, h2,
                intfName1="r1_h2", params1={"ip": "192.168.2.1/24"},
                intfName2="h2_r1", params2={"ip": "192.168.2.2/24"})
    h2.cmd("ip route add default dev h2_r1 via 192.168.2.1")
    
    net.addLink(r6, h3,
                intfName1="r6_h3", params1={"ip": "192.168.3.1/24"},
                intfName2="h3_r6", params2={"ip": "192.168.3.2/24"})
    h3.cmd("ip route add default dev h3_r6 via 192.168.3.1")
    
    net.addLink(r6, h4,
                intfName1="r6_h4", params1={"ip": "192.168.4.1/24"},
                intfName2="h4_r6", params2={"ip": "192.168.4.2/24"})
    h4.cmd("ip route add default dev h4_r6 via 192.168.4.1")

    net.addLink(r1, r2, intfName1="r1_r2", intfName2="r2_r1")
    net.addLink(r1, r3, intfName1="r1_r3", intfName2="r3_r1")

    net.addLink(r2, r4, intfName1="r2_r4", intfName2="r4_r2")
    net.addLink(r2, r3, intfName1="r2_r3", intfName2="r3_r2")

    net.addLink(r3, r5, intfName1="r3_r5", intfName2="r5_r3")

    net.addLink(r4, r6, intfName1="r4_r6", intfName2="r6_r4")
    net.addLink(r4, r5, intfName1="r4_r5", intfName2="r5_r4")

    net.addLink(r5, r6, intfName1="r5_r6", intfName2="r6_r5")

    h1.cmd("ip -6 addr add fc00:1::2/64 dev h1_r1")
    h1.cmd("ip -6 route add default dev h1_r1 via fc00:1::1")
    r1.cmd("ip -6 addr add fc00:1::1/64 dev r1_h1")
    
    h2.cmd("ip -6 addr add fc00:2::2/64 dev h2_r1")
    h2.cmd("ip -6 route add default dev h2_r1 via fc00:2::1")
    r1.cmd("ip -6 addr add fc00:2::1/64 dev r1_h2")

    r1.cmd("ip -6 addr add fc00:a::1/64 dev r1_r2")
    r2.cmd("ip -6 addr add fc00:a::2/64 dev r2_r1")

    r1.cmd("ip -6 addr add fc00:b::1/64 dev r1_r3")
    r3.cmd("ip -6 addr add fc00:b::2/64 dev r3_r1")

    r2.cmd("ip -6 addr add fc00:c::1/64 dev r2_r3")
    r3.cmd("ip -6 addr add fc00:c::2/64 dev r3_r2")

    r2.cmd("ip -6 addr add fc00:d::1/64 dev r2_r4")
    r4.cmd("ip -6 addr add fc00:d::2/64 dev r4_r2")

    r3.cmd("ip -6 addr add fc00:e::1/64 dev r3_r5")
    r5.cmd("ip -6 addr add fc00:e::2/64 dev r5_r3")

    r4.cmd("ip -6 addr add fc00:f::1/64 dev r4_r5")
    r5.cmd("ip -6 addr add fc00:f::2/64 dev r5_r4")

    r4.cmd("ip -6 addr add fc00:aa::1/64 dev r4_r6")
    r6.cmd("ip -6 addr add fc00:aa::2/64 dev r6_r4")

    r5.cmd("ip -6 addr add fc00:ab::1/64 dev r5_r6")
    r6.cmd("ip -6 addr add fc00:ab::2/64 dev r6_r5")
    
    h3.cmd("ip -6 addr add fc00:3::2/64 dev h3_r6")
    h3.cmd("ip -6 route add default dev h3_r6 via fc00:3::1")
    r6.cmd("ip -6 addr add fc00:3::1/64 dev r6_h3")
    
    h4.cmd("ip -6 addr add fc00:4::2/64 dev h4_r6")
    h4.cmd("ip -6 route add default dev h4_r6 via fc00:4::1")
    r6.cmd("ip -6 addr add fc00:4::1/64 dev r6_h4")
    
    r1.cmd("ip -6 route add fc00:3::/64 encap seg6 mode encap segs fc00:a::2,fc00:d::2,fc00:aa::2 dev r1_r2")
    r6.cmd("ip -6 route add fc00:1::/64 encap seg6 mode encap segs fc00:ab::1,fc00:e::1,fc00:b::1 dev r6_r5")

    net.start()

    CLI(net)

    net.stop()


if __name__ == "__main__":
    main()
```

### SRv6のルートの設定
今回は，H1とH3で疎通できるようなルーティングを行う．ルーティングの設定方法はencapでそれぞれのSIDを指定する．
![flow.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2661127/e1a4c585-4624-f98f-8b78-f9b4080fb8e9.png)

Mininetのノードには，`cmd`メソッドがあり，シェルのコマンドをノード内で実行できる．このメソッドで，ルーティングを以下のように設定する．
```python
r1.cmd("ip -6 route add fc00:3::/64 encap seg6 mode encap segs fc00:a::2,fc00:d::2,fc00:aa::2 dev r1_r2")
r6.cmd("ip -6 route add fc00:1::/64 encap seg6 mode encap segs fc00:ab::1,fc00:e::1,fc00:b::1 dev r6_r5")
```
h1 => R1 => R2 => R4 => R6 => H3 となるように，通過するインターフェースを全て指定しておく．


### 実行手順
Mininetを実行する．その後，`xterm h1`でh1のコンソールを立ち上げる．
```bash
$ sudo python3 net.py
*** Configuring hosts
r1 r2 r3 r4 r5 r6 h1 h2 h3 h4 
*** Starting controller

*** Starting 0 switches

*** Starting CLI:
mininet> xterm h1
```

h1のコンソールでh3に対してpingする．
```bash
$ ping -6 fc00:3::2
```

### 実行結果
ping実行画面を下に示す．

![screenshot000111.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2661127/467add75-cd2e-2205-9597-44a71a89071f.jpeg)

r1_r2でキャプチャしたパケットを以下に示す．
![screenshot000112.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2661127/80081bcb-9502-dacd-810a-e3d9a15cd149.jpeg)
ちゃんとパケットが通ってることは確認できた．

## 終わりに
今回は，MininetでSRv6を実行する方法についてまとめた．時間がある時に，他のBehaivorであるEnd.DX4とかEnd.DT4を実行する記事とかを書きたい．