
VPN 接続時にDNSを指定しても、結局ローカルDNSとの併用になってしまうのを嫌い、VPN 接続時に NetworkManager – dispatcher で resolv.conf を直接 書き換え る方法を試してみました。
VPN接続中もDNSはローカルのまま
仕事場のUbuntu 18.04デスクトップをOpenConnectで自宅OpenWRTルータとVPNで繋ぎ、外向きを含むすべてのトラフィックはVPNトンネルを通すようにしていたはずでした。
ところが、VPN接続中はトンネルの向こうにあるDNSを指定しており、そのDNSサーバには広告ブロックを導入しているにも関わらず、ブロックされない広告が目に付くようになりました。
そこで /etc/resolv.conf を確認すると、既存のDNSにVPN接続プロファイルに設定したDNSが追加される形になっていて、その順序は接続毎にランダムに入れ替わります。
1 2 3 4 5 6 7 8 |
$ cat /etc/resolv.conf nameserver 10.96.28.254 nameserver 192.168.51.9 . . $ cat /etc/resolv.conf nameserver 192.168.51.9 nameserver 10.96.28.254 |
VPN接続中は既存のローカルDNSを削除して、VPN接続プロファイルに設定したDNSのみにするのが今回の命題です。
Ubuntuのresolv.confを巡る考察
レガシーな時代とは異なり、近年のLinuxでは /etc/resolve.conf はシステムが適宜書き換えてしまうので、直接編集は意味がない、というところまでは理解していました。
通常 resolve.conf は、実際には次のようなシンボリックリンクになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
$ ls -l /etc/resolv.conf lrwxrwxrwx 1 root root 39 Feb 2 10:48 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf $ ls -l /run/systemd/resolve/ drwx------ 2 systemd-resolve systemd-resolve 80 May 25 09:04 netif -rw-r--r-- 1 systemd-resolve systemd-resolve 589 May 25 09:04 resolv.conf -rw-r--r-- 1 systemd-resolve systemd-resolve 715 May 25 09:04 stub-resolv.conf $ cat /run/systemd/resolve/stub-resolv.conf nameserver 127.0.0.53 options edns0 trust-ad search lan $ systemctl status systemd-resolved ● systemd-resolved.service - Network Name Resolution Loaded: loaded (/lib/systemd/system/systemd-resolved.service; enabled; vendor preset: enabled) Active: active (running) since Mon 2023-05-25 08:22:26 HKT; 6h ago Docs: man:systemd-resolved.service(8) https://www.freedesktop.org/wiki/Software/systemd/resolved https://www.freedesktop.org/wiki/Software/systemd/writing-network-configuration-managers https://www.freedesktop.org/wiki/Software/systemd/writing-resolver-clients Main PID: 1066 (systemd-resolve)<br>Status: "Processing requests..." Tasks: 1 (limit: 4915)<br>CGroup: /system.slice/systemd-resolved.service └─1066 /lib/systemd/systemd-resolve |
その中に記述されている見慣れない 127.0.0.53 は、 systemd-resolved サービスのローカルDNSスタブリスナーと呼ばれるものらしく、それはさらに /run/systemd/resolve/resolv.conf を参照しており、ここに実際のDNSアドレスが定義されていました。
1 2 3 |
$ cat /run/systemd/resolve/resolv.conf nameserver 10.96.28.254 search lan |
と、ここまでの流れをまとめると、次のようになっているということでしょう。
1 2 3 4 5 6 |
/etc/resolv.conf +--> /run/systemd/resolve/stub-resolv.conf nameserver 127.0.0.53 +--> systemd-resolved.service +--> /run/systemd/resolve/resolv.conf nameserver 10.96.28.254 #ローカルDHCPサーバから付与されたDNS |
但しこのPCでは以前、 .local の名前解決ができないことから、このローカルDNSスタブリスナーを使わないよう、手を加えたことがありました。
具体的には、 /etc/resolv.conf のリンク先を直接 /run/systemd/resolve/resolv.conf にしてしまうもので、この状態でも systemd-resolved によってダイナミックに書き換わることに違いはありません。
1 2 |
$ ls -l /etc/resolv.conf lrwxrwxrwx 1 root root 32 May 17 15:50 /etc/resolv.conf -> /run/systemd/resolve/resolv.conf |
ということで、今回編集するファイルは、 /run/systemd/resolve/resolv.conf ということになります。
NetworkManager Dispatcherサービス
ネットワークインターフェイスに関するイベントに応じて何かスクリプトを実行する手段としては、NetworkManager Dispatcherと言うサービスが用意されているそうです。
このサービスは常時稼働するのではなく、普段は待機状態にあります。
1 2 3 4 |
$ systemctl status NetworkManager-dispatcher ○ NetworkManager-dispatcher.service - Network Manager Script Dispatcher Service Loaded: loaded (/lib/systemd/system/NetworkManager-dispatcher.service; enabled; vendor preset: enabled) Active: inactive (dead) |
まずはリンク先のフォーラム回答に沿って、簡単なスクリプトを置いて変数や動作を確認してみます。
1 2 3 4 |
#!/bin/bash interface=$1 event=$2 echo "$interface received $event" | systemd-cat -p info -t dispatch_script |
1 2 3 4 5 6 7 |
$ sudo chmod +x /etc/NetworkManager/dispatcher.d/log-iface-events.sh $ ls -l /etc/NetworkManager/dispatcher.d/ -rwxr-xr-x 1 root root 2293 Dec 5 2019 01-ifupdown* -rwxr-xr-x 1 root root 116 May 18 08:28 log-iface-events.sh* drwxr-xr-x 2 root root 4096 Feb 22 2017 no-wait.d/ drwxr-xr-x 2 root root 4096 Feb 22 2017 pre-down.d/ drwxr-xr-x 2 root root 4096 Feb 22 2017 pre-up.d/ |
この状態で実際VPNを繋いだり切断したりすると、ログエントリが挙がりました。
これでインターフェイス名とイベント名が判明したので、VPN接続時にローカルDNSエントリを削除するスクリプトを書いてみます。
スクリプトの作成
NetworkManager Dispatcherのスクリプトは少し特徴的で、最初にまずスクリプトを実行させたい条件以外を全て振り落とします。
今回の場合、
- イベントが up 以外は何もせず終了
- インターフェイスが vpn* 以外は何もせず終了
でふるいにかけ、残った条件( vpn* の up )に対して、
- /etc/resolv.conf のリンク先パスを取得
- sed でその中にあるローカルDNSエントリ行を削除
を実行する流れになります。
出来上がったスクリプトがこちら。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#!/bin/bash interface=$1 event=$2 ## FILTER vpn* & up ONLY if [ "$event" != "up" ]; then exit 0 fi if [[ "$interface" != vpn* ]]; then exit 0 fi ## SCRIPT echo "$interface received $event" | systemd-cat -p info -t dispatch_script realresolv=`realpath /etc/resolv.conf` sed -i -e /'10.96.28.254'/d $realresolv exit 0 |
1 2 3 4 5 6 7 |
$ sudo chmod +x /etc/NetworkManager/dispatcher.d/99-removelocaldns.sh $ ls -l /etc/NetworkManager/dispatcher.d/ -rwxr-xr-x 1 root root 2293 Dec 5 2019 01-ifupdown* -rwxr-xr-x 1 root root 437 May 18 09:07 99-removelocaldns.sh* drwxr-xr-x 2 root root 4096 Feb 22 2017 no-wait.d/ drwxr-xr-x 2 root root 4096 Feb 22 2017 pre-down.d/ drwxr-xr-x 2 root root 4096 Feb 22 2017 pre-up.d/ |
早速VPNを繋いでみると、期待通りVPNプロファイルに指定したDNSのみになりました。
1 2 3 4 5 6 7 8 9 10 11 |
■平時 $ cat /etc/resolv.conf nameserver 10.96.28.254 ■VPN接続 $ cat /etc/resolv.conf nameserver 192.168.51.9 ■VPN切断後 $ cat /etc/resolv.conf nameserver 10.96.28.254 |
VPN切断後は systemd-resolved が元に戻してくれるので、特に何もする必要はありませんでした。
なお、接続中どうしても現在の設定が常に気になってしまう場合、以下の要領で定期的にネームサーバの参照先をモニタリングすることができます。
1 2 3 |
Every 10.0s: cat /run/systemd/resolve/resolv.conf | grep nameserver WS-0300u: Mon Oct 9 16:30:46 2023 nameserver 192.168.51.9 |