OpenBSD で ND Proxy 的な動作を有効にした際のメモ。

自宅ネットワークでグローバルなIPv6アドレスを利用するために ND Proxy 機 能が必要だった。ルータとして利用している OpenBSD で利用できる ND Proxy 実装がなかったため自作した。ソースコードは https://github.com/toru-mano/nd-reflector で公開している。

ND Proxy がなぜ必要か

次のような一般的な家庭ネットワークを想定する。

1
2
3
4
5
             +------------+                 +------------+        +------+
             |            | IPv4        Wan |            | Lan    |      |
  Internet --+ ISP Router +-----------------+ Home Route +--------+ Host |
             |            | IPv6            |            |        |      |
             +------------+  (RA with /64)  +------------+        +------+

IPv4 ネットワークでは LAN 内の端末はプライベートアドレスが割り当てられ、 自宅のルータで NAT してインターネットと通信する。一方で、 IPv6 ネットワー クでは LAN 内であってもグローバルアドレスを割り当て、 NAT せずに通信する。 もちろん NAT することもできるがアドレス空間が広大な IPv6 ではその必要がな い。

(回線契約にも依存するが) ISP ルータから割り当てられる IPv6 ネットワー クが /64 の場合がある。このとき LAN 内の端末が、インターネットと通信す るには ISP Router が LAN 内の端末の MAC アドレスを Neighbor Discovery Protocol (NDP)で知る必要がある( NDP は IPv6 の ARP に相当する)。こ の NDP パケットはマルチキャストされるためルータを越えらず、LAN 内の端 末に届かない。これを届くようにするのが ND Proxy である(IPv4 では同様の Proxy ARP が存在する)。

OpenBSD で動作する ND Proxy がない問題

ND Proxy は基本的な機能であるが、少し調べた限り、残念ながら OpenBSD で 動作する実装がないようであった。

コマンド ndp(8) は NDP テーブルを操作することができる。オプション -s でプロキシするアドレスを登録することができる。しかし、 IPv6 では端末が 使うアドレスは時間ともに変化し事前に分からないのでこのコマンドを使い ND Proxy を実装することは難しい。

オープンソースで ND Proxy を実装したものは存在するが、いずれも Linux もしくは FreeBSD 向けに実装してあり OpenBSD では動作しない。

ND Proxy を実現する ND Reflector の実装

そこで ND Proxy 的な動作をする ND Reflector https://github.com/toru-mano/nd-reflector を実装した。

このプログラムはWANインターフェイスで受信した Neighbor Solicitation (NS) メッセージを受信して、LAN 向けだった場合、 Neighbor Advertisement (NA) メッセージをWANインターフェイスから返信する。

RFC的にはプロキシは NS メッセージを LAN 内にプロキシして、LANから NAメッ セージを受信したときに限り、WANインターフェイスから NA メッセージを返 信すべきである(参照、RFC4390 Neighbor Discovery Proxies (ND Proxy))。 これには状態管理が必要であり実装が複雑になる。一方で、WAN インターフェ イスで LAN 向けの NS メッセージを受信したら即座に WAN インターフェイス から返信するシンプルなプログラムでも目的が達成できると考えた。動作的に はNSメッセージをNAメッセージとして反射するため ND Reflector と名付けて 実装した。

ビルド、インストールと動作

ソースコードをクローンして次のようにビルドする。

1
2
3
4
5
  mkdir build
  cd build
  cmake -DCMAKE_BUILD_TYPE=Release ..
  make
  doas make install

次のようにしてWAN インターフェイスを em0, LAN インターフェイスを em1 に指定して ND Reflector ndrd を実行できる。

1
2
3
4
  doas rcctl enable ndrd
  doas rcctl set ndrd flags em0 em1
  rcctl get ndrd
  doas rcctl start ndrd

設定例

次のように WAN インターフェイスを em0, LAN インターフェイスを em1 とするネットワークでは次のようにOpenBSDを設定する。

1
2
3
4
5
             +------------+                 +------------+        +------+
             |            | IPv4        em0 |            | em1    |      |
  Internet --+ ISP Router +-----------------+  OpenBSD   +--------+ Host |
             |            | IPv6            |    ndrd    |        |      |
             +------------+  (RA with /64)  +------------+        +------+

ISP ルータから割り当てられる IPv6 プレフィックスを 2001:db8::/64, デ フォルトゲートウェイを fe80::1 とする。

WANインターフェイスの設定 /etc/hostname.em0.

1
2
  inet6 eui64
  !route add -inet6 default fe80::1%em0

LANインターフェイスの設定 /etc/hostname.e10.

1
  inet6 2001:db8::1 64

LAN 内の端末に IPv6 アドレスを配布するための Router advertisement daemon (rad) の設定 /etc/rad.conf.

1
  interface em1

次で IPv6 パケットのルーティングを有効にし、 ND Reflector ndrd を実 行する。

1
2
  doas rcctl set ndrd flags em0 em1
  doas sysctl net.inet6.ip6.forwarding=1

理由は後述するが "cannot forward" というメッセージを抑制するために次を pf.conf に追加しておく。

1
  block in on em0 inet6 proto ipv6-icmp from fe80::/10 to 2000::/3 icmp6-type neighbrsol

"cannot forward" message

何もしないと dmesg/var/log/messages に次のようなメッセージが複 数記録される。

cannot forward src fe80::1, dst 2001:db8::1, nxt 58, rcvif 1, outif 2

これは ISP Router が NDP テーブルを更新するために NS メッセージをユニ キャスト宛に(マルチキャスト宛ではなく)送信するため。ND Reflector は これらのメッセージを受信すると、通常通りNAメッセージを返信する。これと 同時に OpenBSD カーネルは NS メッセージを LAN インターフェイスに転送し ようとする。この NS メッセージはリンクローカルな送信元とグローバルな宛 先アドレスをもっており、送信元のアドレススコープは宛先のスコープよりも 小さいため、カーネルはこれらのパケットの転送を拒否して上のようなメッセー ジを記録する。

また、カーネルは ICMP6 destination unreachable メッセージをコード 2(beyond the scope of source address)で ISP ルータに通知する。結果と して ISP ルータは NA メッセージと unreachable エラーメッセージを同時に 受け取るため混乱する可能性がある。

回避するにはこれらのメッセージをカーネルが受信しないような次のルールを pf.conf(5) に設定すれば良い。

1
  block in on em0 inet6 proto ipv6-icmp from fe80::/10 to 2000::/3 icmp6-type neighbrsol

ND Reflector ndrdbpf(4) を使いパケットを受信し、 bpf(4)pf(4) よりも先に動作するため、上のルールの影響を受けない。

実装について簡単にメモ

  • 自分宛ではないパケットを受信するため bpf を使う

    • NS メッセージがマルチキャストアドレス宛に送信されるため受信インター フェイスをプロミスキャスモードにする
  • NS メッセージに NA メッセージを返信すべきか否かはルーティングテーブルを見る

    • NS のターゲットアドレスのルーティング先が LAN インターフェイスであった場合 NA メッセージを返信する
    • ルーティングテーブルはソケット AF_ROUTE を利用する(参考 route(4)
    • ソケットを読むときは timeout を設定しないと、時々プログラムが停止する