MPTCPにUFWとnetplanとiproute2だけでポリシーベースルーティングを実装したけど途中で力尽きた話

# ポリシーベースルーティング #とは
## 序論
「ポリシーベースルーティング」とは、私なりに意訳すると**例えばLANからWANに対して「Aというアプリの接続先（プロトコル）は回線1」「Bというアプリの接続先（プロトコル）は回線2」「それ以外は回線3」を使って通信経路を分散させるルーティング技術**です。
要するに~~逸般の誤家庭のような~~特定の通信だけ経路迂回させるような技術である、という理解で良いかと思います（適当）。

## 発端

https://qiita.com/daisuke_yanase/items/011cacaf45d6d73f091f

以前上掲の記事で示したように、我が家の「マンション備え付けの無料インターネット回線」が最大1Gbpsに対して実測230Mbps程度と振るわず、**しかもなぜかVultrとの経路がえらい遠回りで帯域も細いしレイテンシも大きい**ところから話は始まります。
この対策として**OCIを契約して途中経路をショートカット**したり、実験的に**楽天モバイルの5G回線を使ってMPTCPを実現**したりしましたが、根本的な解決（Vultrに対するプロバイダ側の経路変更や、MPTCPの安定的常時接続化と高速化）には至っておりませんでした。
~~NAR01のパケ詰まりが大体悪い~~
そうこうしている内に別件でSoftBankのおうちの電話とエキサイトモバイルのSIMを解約して、新たに**VDSLしか通らないけど**NTTのひかり電話を引く必要が発生し、俄かに2回線常時接続環境が整う事になりました。
が、ここで問題が生じます。

### 問題1:全通信をMPTCPでVultrかOCI経由にするのが帯域の合計は太くなるがパケ死するしサービス利用できないことがある

はい、小見出しの通りです。
私のVultrのバンドル通信量は、月毎にVMに対し2.0TB + Vultrのアカウントに対し2.0TBの4.0TBあります。
OCIに至ってはテナントに対して10.0TBです。
VultrもOCIも回線は**大変太い**ので、MPTCPで2回線（230Mbps + 100Mbps）を束ねて330Mbpsの経路に見立ててVultrかOCIを出口にして全通信を通すのが一番帯域が太くなります。**但しパケ死したりVPN禁止なサイトが見れなくなったりする。**
我が家の通信量、バカにならないんですよ。検証でiperf回したり何台もあるPCを積極的にアップデート適用したりOSイメージダウンロードしまくったり~~YouTube観たり~~。
これをクラウドサービスに全部通すと**パケ死するしVPN接続禁止なサービス（※動画サイトやドコモメールなど。Vultrは米国のIPを日本国内に持ち込んでサービスしているので米国判定を受けます）は警告がひっきりなしに飛んできたり使用不能になったりします（実験済み）。**
私が大富豪ならそんなの気にしないんですが、~~貧乏~~へっぽこエンジニアの懐事情的にパケ死の恐怖と戦いたくはないし警告とも戦いたくないので、**全通信をクラウドに通すのは論外**です（そもそも規約的にそういう使い方を禁止しているところもある）。

### 問題2:VDSL側のIPv4がPPPoE or MAP-EなのでルーターにしているVM側の対応が~~面倒~~できない

拙宅はUbuntuServerをルーターにしています。今回引くVDSLはプロバイダが提供するIPv4通信がMAP-EかPPPoEです。
PPPoE通信ならIPが持つ全ポートを占有して使えますが動的グローバルIPで、しかも昨今問題になったように**帯域が細い**です。
MAP-E通信ならIPv4 over IPv6なのでVDSLの100Mbpsをフルスペックで使えますが、Ubuntuでアドレス帯から**自動計算して設定してくれるサービスはありません**ので、使用可能なポートレンジをアドレス帯から計算して手動で計算しなくてはなりませんし、振り出されるIPv6アドレスが変わってしまったら（※何年かに1回あるかないかレベル）設定はやり直しです。それにMAP-E対応ルーターを間に挟むとHGWとUbuntuとの間にルーターが挟まって二重NATになってしまい、設定が複雑化してしまいます。~~面倒過ぎる。~~

**では、どうネットワークを設計すべきなのか。**

## 新ネットワークの要件を定義する

### 先ず、既設ネットワークの問題点を挙げましょう

* Vultrとの経路が細い＆遠い（※前出）
* SoftEtherがダウンしてローカルIP接続できない状態でOCIやVultrのインスタンスにSSH接続する際、各サービスのVCNコンソールを利用するか、さくらのVPSに態々VPN接続してさくらのVPSのグローバルIPからの接続ということにしてメンテナンスを行なっていたので、これを無くしたい
* それ以外にもできればマンション共有回線の外部からUnReachableなIPアドレスではなく、さくらのVPSのIPアドレスを経由させたい（通信元をはっきりとさせておきたい）通信が幾つかある
* MPTCPの実験時にNAR01から振り出されるIP（v6アドレス）とゲートウェイが変わってしまい、通信経路がぐちゃぐちゃになってしまったことがあるので、2つあるOCIのインスタンスに対し、インスタンスAは既設回線、インスタンスBは新設回線を経由するようにしっかり設定したい
* SoftEtherが更新によりRestart/Reloadされた際、TAPデバイスに振られたIPが消失してしまい通信できなくなるのも何とかしたい

### 次に、新ネットワークの構成要件を列挙しましょう

* 既設ネットワークの構成（特に配線）を大きく変更しない
* 既設ネットワークの資産を有効活用する
* ひかり電話用にNTTのHGWは必要
* 但し、HGWのNATセッション数は4096しかないのでHGWでMAP-Eは任せられない
* また、管理機器をいたずらに増やしたり~~二重NATにしたり（これは結局別の所で二重NATになってしまいま）した~~くないので、途中にMAP-E対応ルーターを挟むのも却下
* 100Mbpsの回線速度をフルに使い切ってIPv6でVultr or OCIと接続できること（最重要）

:::note info
**-> 今回必要なのは「ひかり電話（を待ち受けているHGWとの疎通）」と「既設回線とは別に回線帯域（100Mbps）をフルに使い切ってVultrまたはOCIと通信できるIPv6アドレスの追加」と「既設ネットワークの安定性向上」であって、IPv4アドレスの使用可否はそこまで重要ではない**
:::

よって~~勿体無いですが~~**この際、VDSL側のIPv4通信は捨てることにします。**

## PBR（Policy Base Routing）を導入して接続プロトコルや接続先別に通信経路を分けて負荷分散する

はい、ここで漸く掲題した「ポリシーベースルーティング」という単語が出てきました。
要は接続プロトコルや接続先別に通信経路を変えたいわけです。
今回は以下の条件を全て満たすよう構成します。

* **SSH、メールなどの「通信元をはっきりと自分が占有しているIPにしておきたくて、通信帯域がそこまで大きくなくても良いもの」はさくらのVPSを経由させる**
* **MPTCP化したSoftEtherのOCI宛の通信は必ず接続先毎に既設/新設で経路を別にさせる**
* **その上で、自宅サーバーで待ち受けさせているサービスはMPTCP（の先にいるVultrのインスタンス）か、またはさくらのVPSを経由させる**
* **SpeedTest等の「通信元をはっきりと自分が占有しているIPにしておきたくて、通信帯域が大きい必要があるもの」はVultrを経由させる**
* **それ以外の通常のWeb閲覧やLINE等の「通常のトラフィック」は既設回線を経由させる**
* **以上をSoftEther、UFW、iproute2、及びUbuntuに備わっている標準的な機能だけで実現する（鬼）**

これを雑に図面に起こすとこんな感じでしょうか。

![自宅ネットワーク（Ver.6）図.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861875/23d56b31-2900-69d2-1cb1-1390319e84d3.jpeg)
それでは早速設定していきます。

### ルートテーブルを作成する

まずはポリシーベースルーティングをするに際し、受信したパケットを格納する場所が必要ですのでルートテーブルを作成します。
以下は拙宅の例です。

```/etc/iproute2/rt_tables.d/wan.conf
8964 ipoe
8965 vdsl
8001 wan
4212 vultr
4253 sakura
4001 lan
```

8000番台がIPv6用、4000番台がIPv4用です。適宜皆さんの実環境に合わせて読み替えるなり置き換えるなりしてください。

### UFWで受信したパケットをルーティングするルールを作成する

次にUFWで受信したパケットをルートテーブルに格納するためのフラグを付与するためのルールを作成します。
上から順に見ていって、最初の「COMMIT」の下（NATルールを追加している場合は最初のCOMMMITの下かつNATルールの上）に、例えば次のように記述します（コメントアウト部分は無視しても構いません）。

```/etc/ufw/before.rules
*mangle
-F

# ローカルエリア同士のIPアドレスの通信はデフォルトのルートテーブルに格納する
-A PREROUTING -d 127.0.0.1/8,192.168.0.0/22,192.168.4.0/22,192.168.8.0/22,192.168.12.0/22 -j MARK --set-mask 0

# ローカルエリア側のインターフェイス（eth2）からインターネット側宛のSSHやメールソフト、リモートデスクトップの通信はさくらのVPSのルートテーブルに格納する
-A PREROUTING -i eth2 -p tcp -m multiport --dports 22,110,465,587,993,995,3389,5900 -j MARK --set-mark 4253
-A OUTPUT -p tcp -m multiport --dports 22,110,465,587,993,995,3389,5900 -j MARK --set-mark 4253
-A PREROUTING -i eth2 -p udp -m multiport --dports 3389 -j MARK --set-mark 4253
-A OUTPUT -p udp -m multiport --dports 3389 -j MARK --set-mark 4253

# ローカルエリア側のインターフェイス（eth2）からインターネット側宛のWeb関連のTCP通信は既設回線のルートテーブルに格納する
-A PREROUTING -i eth2 -p tcp -m multiport --dports 80,443 -j MARK --set-mark 4001
-A OUTPUT -p tcp -m multiport --dports 80,443 -j MARK --set-mark 4001

# ローカルエリア側のインターフェイス（eth2）からインターネット側宛のWeb関連のUDP通信は既設回線のルートテーブルに格納する
-A PREROUTING -i eth2 -p udp -m multiport --dports 53,123,443 -j MARK --set-mark 4001
-A OUTPUT -p udp -m multiport --dports 53,123,443 -j MARK --set-mark 4001

COMMIT
```

### SoftEtherのサービスファイル側に記述していたMPTCP関連のExecStartPostをスタートアップスクリプトへ移設する

SoftEtherのサービスファイル側に記述していたMPTCP関連のExecStartPostをスタートアップスクリプトへ移動します。

```/etc/network/if-up.d/static-routes
#!/bin/sh
/sbin/ip link set dev eth2 promisc on
/sbin/ip link set dev eth3 promisc on
/sbin/ip link set dev eth4 promisc on
/sbin/ip link set dev eth5 promisc on
/sbin/ip link set dev eth6 promisc on
/sbin/ip link set dev eth7 promisc on
/sbin/ip rule add fwmark 4001 table lan
/sbin/ip rule add fwmark 4253 table sakura
/sbin/ip rule add fwmark 4212 table vultr
/sbin/ip -6 rule add fwmark 8001 table wan
/sbin/ip -6 rule add fwmark 8964 table ipoe
/sbin/ip -6 rule add fwmark 8965 table vdsl
/sbin/ip mptcp limits set subflow 2 add_addr_accepted 2
/sbin/ip mptcp endpoint del id 1
/sbin/ip mptcp endpoint del id 2
/sbin/ip mptcp endpoint del id 3
/sbin/ip mptcp endpoint add 192.168.8.235 dev eth2 subflow
/sbin/ip mptcp endpoint add 192.168.4.112 dev eth6 signal
```

### スタートアップスクリプトはそのままでは発火しない（いつからか参照しなくなったらしい）のでnetworkd-dispatcherから叩くようにスクリプトを書く

/etc/network/if-up.d/に配置したスタートアップスクリプトはいつからかOS起動時に参照されなくなったらしいので、networkd-dispatcherから叩くようにスクリプトを書きます。

```/etc/networkd-dispatcher/routable.d/990ifup-hooks
#!/bin/bash

if [ "$IFACE" == "eth2" ]; then
        /bin/sh /etc/network/if-up.d/static-routes
fi
```

### NetplanでOSがルーティングポリシーを参照してゲートウェイを設定するように書き換えます

NetplanでOSがルーティングポリシーを参照してゲートウェイを設定するように書き換えます。
下掲の例ではネームサーバーはルーター自身のBINDが名前解決を行なっているので空欄または自分自身を参照するように設定しています。

```/etc/netplan/99-network-config.yaml
# This is the network config written by 'subiquity'
network:
  ethernets:
    eth0: # <-既設回線のインターフェイス
      accept-ra: true
      dhcp4: true
      dhcp6: true
      mtu: 1440
      nameservers:
        addresses: []
      optional: true
      routes:
      - to: 0.0.0.0/0
        via: 192.168.254.254 # <-対向するルーターのDHCPで通知されるデフォルトGW
        on-link: true
        table: 4001 # <-追加
      - to: 2001:db8::8964:xxxx:xxxx:xxxx # <-OCIのインスタンス1のIPアドレス
        via: fe80::xxxx:xxxx:xxxx:xxxx # <-対向するルーターのRAで配信されるデフォルトGW
        metric: 2 # <-追加
    eth1: # <-新設回線（VDSL）のインターフェイス
      accept-ra: true
      addresses:
      - 192.168.12.2/22 # <-HGWとの接続に使うIPアドレス
      dhcp4: false
      dhcp6: false
      mtu: 1440
      nameservers:
        addresses: []
      optional: true
      routes:
      - to: 2001:db8::8964:yyyy:yyyy:yyyy # <-OCIのインスタンス2のIPアドレス
        via: fe80::yyyy:yyyy:yyyy:yyyy # <-対向するHGWのRAで配信されるデフォルトGW
        metric: 2 # <-追加
    eth2: # <-ローカルエリアのインターフェイス
      accept-ra: false
      addresses:
      - 192.168.0.1/22
      - fdfc:xxxx:xxxx:xxxx::1/64
      dhcp4: false
      dhcp6: false
      macaddress: XX:XX:XX:XX:XX:XX
      nameservers:
        addresses: [ fdfc:xxxx:xxxx:xxxx::1, 192.168.0.1 ]
      mtu: 1500
      optional: true
    eth3: # <-DMZのインターフェイス
      accept-ra: false
      addresses:
      - 192.168.8.235/22
      dhcp4: false
      dhcp6: false
      macaddress: ZZ:ZZ:ZZ:ZZ:ZZ:ZZ
      mtu: 1500
      optional: true
      routes:
      - to: 0.0.0.0/0
        via: 192.168.8.253 # <-さくらのVPSのTAPデバイスのアドレス
        on-link: true
        table: 4253 # <-追加
      - to: 0.0.0.0/0
        via: 192.168.8.1
        on-link: true
        table: 4212 # <-追加
# eth4-5はSoftEtherのブリッジなので記述例では記載省略
    eth6: # <-MPTCPのサブフロー用インターフェイス
      accept-ra: false
      addresses:
      - 192.168.5.112/22
      dhcp4: false
      dhcp6: false
      macaddress: AA:BB:CC:DD:EE:FF
      mtu: 1500
      optional: true
# eth7はSoftEtherのブリッジなので記述例では記載省略
  version: 2

```

以上を設定したら、

```
sudo systemctl ufw try
```

してufwがきちんと設定されるのを確認し、

```
sudo systemctl daemon-reload
sudo shutdown -r now
```

再起動後、**SoftEtherがゾンビプロセス大増殖せず（重要）**、途中経路（この場合はOCIの2つのインスタンス）に対しさくらのVPS経由で接続できること、更に各々のインスタンス上で
```
iftop
```
を実行して回線負荷を監視しながら、ルーターからMPTCPの対向側のDMZ側IPに対しiperf3などで負荷を仕掛けます。


途中経路のインスタンスが両方ともトラフィックが増加して、**対向側との帯域が従前より増加**していれば成功なのですが……。

## 残念ながらVultrを経由すると100Mbps前後で頭打ちになってしまいました

一応iftop等で帯域の増加を確認しながら色々テストしたんですが、**通信は安定しているものの帯域は50Mbps×2＝100Mbps前後で頭打ちになってしまいました。**
色々分からないなりに原因を追いかけてみたんですが、MPTCP自体は成立していて既設・新設共にほぼ均等に負荷は掛かってるらしい、というところまではわかったんですが、じゃあどこがボトルネックなのよ？　というところまで突き詰められませんでした。
でも多分、VDSL側の速度（上り下り合計100Mbps）に引き摺られてるんじゃないかなあ？
無念。
でもまあひかり電話が主目的であって、MPTCPやPBRは副目的だったし（震え声

## まとめ

:::note info
**MPTCPで2本のWANを使ってMPTCPを再現出来た**
:::

:::note info
**PBRにより、任意のゲートウェイに特定の通信を向けることが出来る様になった**
:::

:::note warn
**MPTCPにより固定回線を2本束ねたが、速度が有意に向上はしなかった**
:::

**お疲れ様でした！**