naoki86star

インターネットの片隅でなにかしら書いてみる

domain-nameベースのルーティング(インタフェース選択) on Linux

やったこと

 ドメイン名によって経路(インタフェース)を選択的にすることができるのかどうか試してみた。家の環境で、通常のインターネットにつなぐ経路とVPNのトンネル経由の経路が利用できるときに、特定のドメイン名の場合には希望するインタフェースからパケットを流す、ということが目標。webで調べて試してみて、1)ipset+dnsmasq 2)iptablesを使ったパケットの印付け 3) ip ruleの設定を組み合わせることでできた。

環境
  • raspberry pi3(B+, "Raspbian GNU/Linux 10 (buster)")

有線/無線ともにstaticでアドレス振ってる
無線はアクセスポイントとして設定してる
無線側PCには、dnsmasqがdhcp機能でPCにアドレス供給し、DNS問い合わせにも応答する
pi3ではインターネット向けに、通常のhome-GWを抜けるインタフェースeth0と、OpenVPN,OpenConnectでそれぞれインタフェースtun0,tun1のトンネルでhome-GWを通る経路を設定している

+-----+     +--------------+   +-------+
| PC  |/////|pi3(vpnclient)|---|home-GW|====(internet) 
+-----+     +------------- +   +-------+
      wifi区間            イーサ
step1 : ipsetの仕掛け

 dnsによる名前解決が走った時に、そのdnsが設定した条件にマッチしていたら、ipsetのメンバーに追加されるようにする。dnsmasqを使うことで、実際に解決に走ったドメイン名に対しipsetできるとのこと。
 先にipsetを作成することが必要で、これはiptablesで参照されるSETNAMEのついた入れ物といえる。

$ sudo ipset create GOOGLE hash:ip

/etc/dnsmasq.confに

ipset=/*.google.com/google.com/GOOGLE

の一行*1追加して、dnsmasqを再起動する。ping dns.google.comとかping map.google.comとかするとsudo ipset list GOOGLEでみたときMembers:にIPアドレスが追加されていくのがわかる。*2

step 2 : iptablesの仕掛け

iptablesのmangle tableを使うと、パケットに特殊な印付けをできるとのこと。パケットがカーネルの中を通過している間だけ有効な印らしい。ここで付けた値201は後のルーティングテーブル番号に合わせただけで、この数字にする必然はない。

sudo iptables -A PREROUTING -t mangle -j MARK -m set --match-set GOOGLE dst --set-mark 201
sudo iptables -A OUTPUT -t mangle -j MARK -m set --match-set GOOGLE dst --set-mark 201

1行目は、フォワードパケット用。2行目はpi3上から発信する場合用。

step 3 : ip ruleの設定

 Linuxのルーティングテーブルは、テーブル自体を複数持たせることができるとのこと。今まで知らなかったけれども、経路テーブルを切り替える潜在的な能力がカーネル2.4以来備わっているとのこと。
 VPNを通したいときのルーティングテーブルを追加してあげて、MARKが付いているパケットの場合はそのルーティングテーブルを参照するということができれば、目的を達成できる。

 今回vpn_tableという名前で追加してみる。/etc/iproute2/rt_tablesを編集

#
# reserved values
#
255     local
254     main
253     default
0       unspec
#
# local
#
#1      inr.ruhep
201 vpn_table

vpn_tableに経路を追加してあげる。このテーブルを使う場合はすべて同じinterface tun1にしたいので

sudo ip route add default dev tun1 table vpn_table

とすることになる。

最後、

sudo ip rule add fwmark 201 lookup vpn_table

というコマンドで、MARK=201のパケットの場合vpn_tableテーブルを参照する、ということが実現できることになる。

PCからtracert dns.google.comすると、ip rule add前後で経路が変わってくれた。

参考にさせていただいたもの

networking - Linux: routing based on domain names - Super User
やや長くいろいろ書かれていて全体通して手法を理解するのに参考になった。
iptables - Forwarding packets from loopback interface with policy-based routing - Unix & Linux Stack Exchange
pi3発信のケースでMARKされずにいたときに、これで解決できた。
GNU/Linux host name resolution - /dev/posts/
名前解決周辺のデーモン・設定を俯瞰して見ることができて大変参考になった。

*1:この記述に注意しておく、今の自分の環境条件だとipset=/*.google.com/GOOGLEとした場合にはなぜかうまく機能しなかった

*2:自分として特にgoogle.comを別経路にしたいわけでなく、試行するにはわかりやすいであろうドメイン例ということで、これで試していた。