OpenBSD で IPv4 PPPoE と IPv4 IPoE (MAP-E) の両方をポリシーベースルーティング(Policy-Based Routing)とVRF(Virtual Routing and Forwarding)相当の技術で実現したときのメモ。

ポリシーベースルーティングは pf(4) を利用し、VRF は rdomain(4) を利用する。

PPPoE と IPoE を並用する理由

OpenBSD カーネルにパッチをあてて IPoE (MAP-E) を動作させたため IPv4 の通信路として IPoE 方式と PPPoE 方式の両方が使えるようになった。 各方式の特徴は次の表のようにまとめられることが多く(ただし、私の環境では通信速度に顕著な差はない)、一長一短となっている。

方式 通信速度 ポート範囲
IPoE 速い 制限あり
PPPoE 遅い 制限なし

例えば

  • Web アクセスや動画視聴、データダウンロードする際は IPoE を使い、
  • SSH などのサービスを公開する際は PPPoE を使う、

とすれば両者の良いとこどりができる。

概念的には次のようなネットワークトポロジでトラフィックに応じて使用するリンク(PPPoE もしくは IPoE)を選択する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    +------------------+
    |                  |
    |    Internet      |
    |                  |
    +----+--------+----+
         |        |
         |        |
  PPPoE  |        | IPoE (MAP-e)
         |        |
         |        |
         |        |
    +----+--------+----+
    |                  |
    |  OpenBSD Router  |
    |                  |
    +--------+---------+
             |
             |
             | LAN
     --------+----------

しかし通常のIPルーティングでは宛先IPアドレスのみでルーティング先が決まるため、上記の実現は困難である。 またルーティング先はデフォルトゲートウェイで決まり、デフォルトゲートウェイは一つだけしか指定できないので複数リンクを使いわけることは困難である。

そこで、送信元IPやプロトコル情報などを含めてルーティングするポリシーベースルーティングと複数のルーティングテーブルを利用できる VRF 相当の技術を利用する。 それぞれ pf(4)rdomain(4) を利用する。

最終的には次のような構成により PPPoE と IPoE を並用する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
          +---------------------------------------------------------------+
          |                                                               |
          |                          Internet                             |
          |                                                               |
          +-----------+--------------------------------------+------------+
                      |                                      |
                      |                                      |
                      |                                      |
  +-------------------+--------------------------------------+---------------------+
  |                   |              OpenBSD                 |                     |
  |    +--------------+----------------+       +-------------+-----------------+   |
  |    | rdomain1     |                |       | rdomain0    |                 |   |
  |    |         +----+---+            |       |         +---+----+            |   |
  |    |         | pppoe0 |            |       |         |  gif0  |            |   |
  |    |         +--------+            |       |         +--------+            |   |
  |    |                               |       |                               |   |
  |    |    +--------+  +---------+    |       |    +--------+  +---------+    |   |
  |    |    |  sshd  |  | unwind  |    +-------+    |  sshd  |  | unbound |    |   |
  |    |    +--------+  +---------+    |       |    +--------+  +---------+    |   |
  |    |                               |  pf   |                               |   |
  |    |                               |       |     +--------------------+    |   |
  |    +-------------------------------+-------+     |                    |    |   |
  |                                            |     |    +---------+     |    |   |
  |                                            |     |    | vport0  |     |    |   |
  |                                            |     |    +---------+     |    |   |
  |                                            |     |                    |    |   |
  |                                            |     |       veb0         |    |   |
  |                                            |     |                    |    |   |
  |                                            |     |  +-----+  +-----+  |    |   |
  |                                            |     |  | em1 |  | em2 |  |    |   |
  |                                            |     |  +--+--+  +--+--+  |    |   |
  |                                            |     |     |        |     |    |   |
  |                                            +-----+-----+--------+-----+----+   |
  |                                                        |        |              |
  +--------------------------------------------------------+--------+--------------+
                                                           |        |
                                                           |        | LAN
                                                        ---+--------+-------------------

Routing domain による複数ルーティングテーブル

OpenBSD の rdomain(4) は linux の network namespace 相当の機能をもち、複数のルーティングテーブルと routing domain をもつことができる。 Routing domain はルーティングテーブルとインターフェイスをもつ。 インターフェイスに到着したパケットはインターフェイスが所属する routing domain のルーティングテーブルに従いルーティングされる。

デフォルトの routing domain である rdomain 0 は常に存在している。この rdomain 0 に IPoE インターフェイス gif0 が存在するとして、別の rdomain に PPPoE インターフェイス pppoe0 を作成し、名前が解決できるように設定する。

Routing domain 1 に PPPoE インターフェイスを追加する

通常と同じように /etc/hostname.pppoe0 で設定する。このとき rdomain 1 を追加してインターフェイスが所属する routing domain を指定する。

1
2
3
4
5
6
7
rdomain 1
inet 0.0.0.0 255.255.255.255 NONE mtu 1454 \
        pppoedev em0 authproto chap \
        authname username authkey secret up
dest 0.0.0.1
!/sbin/ifconfig lo1 inet 127.0.0.1/8
!/sbin/route -T 1 add default -ifp pppoe0 0.0.0.1

コマンド doas sh /etc/netstart pppoe0 などでインターフェイスを作成したあとに netstat -R で各 routing domain に所属するインターフェイスと routing table を確認できる。

1
2
3
4
5
6
7
8
> netstat -R
Rdomain 0
  Interfaces: lo0 em0 em1 em2 em3 em4 enc0 gif0 pflow0 veb0 vport0 wg0 pflog0
  Routing table: 0

Rdomain 1
  Interfaces: pppoe0 lo1
  Routing table: 1

動作確認

OpenBSD の ping(8) は使用するルーティングテーブルをオプション -V で指定できる。さきほど作成した PPPoE にひもづくルーティングテーブル 1 を使用するには次のようなコマンドを実行する。

1
  ping -V 1 8.8.8.8

Linux の ip netns exec 相当は route(8) のオプション -T で実現でき、指定したルーティングテーブルでコマンドを実行できる。よって ping のオプション -V を使用せずにルーティングテーブル 1 から ping を実行するには次のようなコマンドを実行する。

1
route -T 1 exec ping 8.8.8.8

Routing domain 1 で名前が解決できるように unwind を起動する

新しい routing domain を作成した場合、 dig などで名前が解決できないことがある。例えば /etc/resolv.confnameserver 127.0.0.1 など routing domain 0 で起動している unbound が指定されている場合である。このとき routing domain 0 と 1 は分離しているため routing domain 1 から routing domain 0 の unbound へ通信ができないため名前解決ができない。

そこで routing domain 1 で名前解決ができるように unwind を起動する。

設定ファイル /etc/unwind.conf を例えば次のように編集して public DNS サーバを指定する。

1
forwarder { 8.8.8.8 1.1.1.1 }

次に unwind が routing domain 1 で動作するように設定する。

1
2
  rcctl set unwind rtable 1
  rcctl start unwind

これで routing domain 1 の 127.0.0.1unwind がDNSをリッスンするので名前が解決できるようになる。

動作確認

次のようなコマンドで実際に PPPoE と IPoE でそれぞれ通信が確認できる。

1
2
route -T 1 exec curl -4 ifconfig.io
route -T 0 exec curl -4 ifconfig.io

サイト http://ifconfig.io はアクセス元のIPアドレス情報を表示する。よって上のコマンドではそれぞれ PPPoE と IPoE のIPアドレスが表示される。

両方の routing domain で SSH を起動する

SSH も unwind 同様に rcctl でも routing domain を指定できるが、 設定ファイル /etc/ssh/ssd_config で指定できる。設定の ListenAddress で listen する routing domain を次のように指定できる。

1
2
3
ListenAddress 0.0.0.0
ListenAddress 0.0.0.0 rdomain 1
ListenAddress ::

上の設定で sshd はデフォルトの routing domain 0 で全ての IPv4 と IPv6 アドレスでリッスンし、 routing domain 1 の全ての IPv4 アドレスでリッスンする。

PF によるポリシーベースルーティング

Routing domain とコマンド routercctl により OpenBSD から送信されるパケットが PPPoE か IPoE どちらを通るか制御できる。一方で LAN 内のホストから送信されたパケットは routing domain 0 のデフォルトゲートウェイである IPoE を経由してインターネットへ抜ける。

これは LAN 内からのパケットは virtual ethernet bridge である veb0 に到着し、このインターフェイスは routing domain 0 に所属するためである。

そこで PF を利用して LAN 内から到着した特定のパケットを routing domain 0 から 1 に移動して、 PPPoE を経由してインターネットへ抜けるように設定する。

PF ではなく pair(4) でふたつの routing domain を接続し、特定のパケットだけ pair インターフェイスを通し、別の routing domain に流す方法もある。この場合はルーティングテーブルによりパケットが routing domain を移動するので宛先のみでどの routing domain を使用するか決める必要がある。一方で PF の場合は送信元IPアドレスやポート番号も含めてどの routing domain を使用するか決められるため柔軟性が高いため PF を使用する。

経路選択に使用されるルーティングテーブルの変更

PFの設定 pf.conf でキーワード rtable を使うことで使用されるルーティングテーブルを変更することができる。

例えば、テーブル via_pppoe で指定された宛先をもつパケットの経路選択にルーティングテーブル 1 を使用するには次のように設定する。

1
pass in on vport0 inet from vport0:network to <via_pppoe> rtable 1

特定の LAN 内ホストからのパケットの経路選択にルーティングテーブル 1 を使用するには次のように設定する。

1
pass in on vport0 inet from $pppoe_hosts to !<martians> rtable 1

戻りのパケットのルーティングテーブルを変更するルールは不要である。これは行きのルールマッチ時に作成される state が行きと戻りのパケット両方にマッチするためである。

具体的な pf.conf の設定例

ポリシーベースルーティングと PPPoE インターフェイスに関連する設定だけ抜粋すると例えば次のような設定となる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
  table <via_pppoe> const { 8.8.8.8/32, 1.0.0.1/32 }
  block in on pppoe0
  match on pppoe0 inet all scrub (no-df random-id max-mss 1414)

  # NAT with PPPoE
  match out on pppoe0 inet from vport0:network to !<martians> nat-to (pppoe0:0)
  pass out on pppoe0 inet modulate state (pflow)
  # Allow ssh remote access
  pass in on pppoe0 inet proto tcp from any to (pppoe0) port $ssh_port

  # Policy based routing via rtable
  # packet to specific hosts route via rtable 1
  pass in on vport0 inet from vport0:network to <via_pppoe> rtable 1
  # packet from specific hosts route via rtable 1
  pass in on vport0 inet from $pppoe_hosts to !<martians> rtable 1
  # route all outgoint packet from LAN via PPPoE
  # pass in on vport0 inet from vport0:network to !<martians> rtable 1

前半で NAT、後半でポリシーベースルーティングを設定している。

おわりに

PF と routing domain を利用して PPPoE と MAP-E を並用できるようにしたが、通信品質計測してみたところ、私の環境ではどちらの経路も通信品質に大差ないようで残念であった。