hnwの日記

特定ホスト名の通信だけVPN経由にするルータ設定(OpenWrt編)

自宅のルーターの設定で、普段の通信はデフォルトゲートウェイを使いたいけど、一部のホスト名の通信だけはVPNトンネルインターフェースを使いたい、という状況がまれにあります。一般的なニーズではないと思いますが、少なくとも私にはそういうニーズがありました。

IPアドレスごとに外向きインターフェースを切り替えたいのであればiptablesの設定だけで実現できます。一方で、ホスト名によってインターフェースを切り替えるのは一般的には困難です(ホスト名の解決はアプリケーション層で行われるのに対し、iptablesネットワーク層トランスポート層での処理になるため)。このような場合に、IPset経由でdnsmasqとiptablesを連携してルーティングを切り替える方法があります。本稿ではこのやり方を説明します。

OpenWrtとは

OpenWrtは組み込み用途のLinuxディストリビューションで、家庭用の有線LANルータ・Wi-Fiルータファームウェアを置き換えることで機能追加をしたりカスタマイズ性を高めたりしようというプロジェクトです。ルータの持つ基本的な機能(PPPoEやファイアウォールの設定)に加え、DDNSVPN・VLAN・QoSなどの設定がGUIから可能になります。また、ルータにsshでログインできるようになるので、トラブルの切り分けがやりやすくなるメリットもあります。自宅のネットワークで色々遊びたい人にはお勧めの選択肢です。

必要パッケージの追加

ではOpenWrtでの設定を見ていきましょう。まずは必要パッケージをインストールします。

# opkg update
# opkg install ipset kmod-ipt-ipset dnsmasq-full luci-app-mwan3

dnsmasqは最初からインストールされているものでは機能不足で、full版に差し替える必要があります。

DNS正引きのログ出力

今回のような実験的な取り組みを行う場合、動作確認やトラブル対応のためにDNSクエリをログに出しておくと便利です。

Web管理画面(LuCI)から「Network」「DHCP and DNS」「General Settings」「Log queries」をチェックするとログに全DNSクエリがログに残ります。ログは logread コマンドで確認できます。

# logread
(略)
Mon Feb 22 10:09:02 2021 daemon.info dnsmasq[2794]: 173859 192.168.2.101/24828 query[A] android.googleapis.com from 192.168.2.101
Mon Feb 22 10:09:02 2021 daemon.info dnsmasq[2794]: 173859 192.168.2.101/24828 forwarded android.googleapis.com to 192.168.1.1
Mon Feb 22 10:09:02 2021 daemon.info dnsmasq[2794]: 173859 192.168.2.101/24828 reply android.googleapis.com is 172.217.24.138

IPsetのセット作成・動作確認

IPsetとは、IPアドレスやその他のネットワーク情報を高速に検索できるLinux上のオンメモリデータベースです。iptablesと組み合わせて使う前提の仕組みで、iptablesで大量のルールを扱うのに利用したり、今回のように別ツールと組み合わせて使ったりできます。

IPsetでは1つのデータベースをセットとよび、セットに対してIPアドレスやネットワーク範囲を追加・削除することができます。今回はforce_usというセットを作ります。

# ipset create force_us hash:ip

次に、/etc/config/dhcp を直接変更して、特定ホスト名をDNS正引きするとそのIPアドレスがforce_usセットに追加されるようにします。

config dnsmasq
    (略)
    list ipset '/api.example.com/api2.example.com/force_us'

このように、スラッシュ区切りで複数のホスト名を指定できます。また、ホスト名の後方一致でドメイン名を指定することもできます。

最後に dnsmasq を再起動して動作確認をします。DNS正引きした結果がセットに追加されていれば成功です。

# kdig a api.example.com +short
192.0.2.32
# ipset list force_us
Name: force_us
Type: hash:ip
Revision: 6
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 368
References: 1
Number of entries: 1
Members:
192.0.2.32

mwan3の設定

mwan3は外向きロードバランシング用のOpenWrt独自パッケージで、iptablesのラッパーです。今回のように外向きインターフェースを使い分けたいだけの場合にはオーバースペックですが、Web管理画面から設定できて楽なので利用しています。

mwan3の設定で、先ほど作ったforce_usセットにマッチするIPアドレス宛てなら別のインターフェースを使うよう設定していきましょう。

まずWeb管理画面「Network」「Load Balancing」「Interfaces」の「Add」からvpnusインターフェース(OpenVPNで設定したVPNトンネルインターフェース)を設定します。これで既存インターフェースがmwan3の管理下に置かれて死活監視が行われるようになります。

次に、「Network」「Load Balancing」「Members」からvpnus_m1_w3というメンバーを追加します。ここでインターフェースとして先ほど設定したvpnusインターフェースを指定します。

f:id:hnw:20210222230837p:plain
mwan3のmember設定

さらに「Network」「Load Balancing」「Policies」からvpnus_onlyポリシーを追加します。ここで先ほどのvpnus_m1_w3メンバーを指定します。

f:id:hnw:20210222231019p:plain
mwan3のpolicy設定

最後に「Network」「Load Balancing」「Rules」でforce_us_v4というルールを作ります。IPsetについては先ほど作成した「force_us」を指定し、「Policy assigned」は先ほど作成した「vpnus_only」ポリシーを指定します。

f:id:hnw:20210222231137p:plain
mwan3のrule設定

以上の設定で、特定ホスト宛の通信をVPN経由にすることができました。

ipsetの保存

ルータを再起動しても同じ設定を維持するため、IPsetのforce_usセットをルータ起動時に作るようにします。

/etc/config/firewallを直接変更しましょう。

config ipset
    option enabled '1'
    option name 'force_us'
    option storage 'hash'
    option family 'ipv4'
    option match 'dest_ip'

制限事項

今回の仕組みが期待通り動作するためには、通信を行う前にDNSの正引きが行われる必要があります。逆に言うと、IPアドレスでアクセスするようなサービスでは使えません。ストリーミング系サービスの中にはAPIサーバからストリーミングサーバのIPアドレスが返ってくるようなものがありますが、こうした場合には適用できません。また、端末がルータのDNSを利用していないような場合も無力です。

まとめ

OpenWrtルータ上でdnsmasqとIPsetを連携させて特定ドメイン・特定ホスト宛の通信だけをVPN経由にすることができました。1ヶ月ほど使っていますが、今のところ期待通りに動作しています。

参考URL