🥅

TunnelblickでVPN接続をするときにChatGPTのサイトだけVPNを通したくない

2023/05/01に公開

ChatGPTのサイトはVPN接続をしているとアクセスできない場合があります。ChatGPTとお話するときだけVPNを切らないといけないのは不便だなということで、ChatGPTのサイトへのアクセスはVPNを通さない設定をTunnelblickでやってみました。尚、この調べ物はChatGPTに相談しながら解決にたどり着きました。TunnelblickはMac用アプリケーションなので、Mac環境を前提としています。

以下ではChatGPTに相談しながら進めた試行錯誤の過程も残していますが、最終的な設定だけ見たい人は最後の見出しに飛んでください。

VPNの接続時と切断時にルーティングテーブルを編集

まずChatGPTに提案されたのはTunnelblickの設定ファイル[1]でルーティングテーブルを変更する設定でした。下記のような設定を .opvn ファイルに書きます。

route-up route add -host <ipaddress> -gateway <gateway>
route-pre-down route delete -host <ipaddress> -gateway <gateway>

<ipaddress> にはChatGPTのサイトのIPアドレス、 <gateway> はデフォルトゲートウェイのIPアドレスを設定します。

route-up はVPN接続時に任意のコマンドを実行できます。ここではrouteコマンドを使って指定したIPアドレス<ipaddress>への通信を指定したゲートウェイ<gateway>を経由して行うような設定をルーティングテーブルに追加しています。この設定によりVPN接続時に特定のサイトにVPN接続をせずにアクセスすることができます。

route-pre-down はVPN切断前に任意のコマンドを実行できます。ここではrouteコマンドを使い、route-upで行ったルーティングテーブルの変更を元に戻しています。この2つの設定によってVPN接続時だけルーティングテーブルの設定を変えるようなことが実現できます。

VPN接続を通したくないサイトのIPアドレスは下記のコマンドで確認できます。

dig +short A example.com

macの場合、デフォルトゲートウェイのIPv4アドレスは下記のコマンドで確認できます。

netstat -nr -f inet | grep default | awk '{print $2}'

この方法の場合、予め<ipaddress><gateway>を調べてから.opvnファイルに設定する必要があり、あまり汎用的ではないです。ということでこれらをVPN接続、切断時に取得してルーティングテーブルを変更するようなスクリプトを作ることにします。

ルーティングテーブル変更スクリプトを作成

route-upで実行するスクリプトは下記のようになります。ChatGPTのサイトのIPアドレスとデフォルトゲートウェイのIPアドレスを取得してrouteコマンドでルーティングテーブルに追加しています。

add_route.sh
#!/bin/bash

# Domain to bypass VPN
domain="chat.openai.com"

# Get the IP address for the domain
ip_address=$(dig +short "$domain")

# Get the default gateway
gateway=$(netstat -nr | grep default | awk '{print $2}')

# Add route for the domain's IP address
route add -host "$ip_address" -gateway "$gateway"

route-pre-downで実行するスクリプトは下記です。

delete_route.sh
#!/bin/bash

# Domain to bypass VPN
domain="chat.openai.com"

# Get the IP address for the domain
ip_address=$(dig +short "$domain")

# Get the default gateway
gateway=$(netstat -nr | grep default | awk '{print $2}')

# Delete route for the domain's IP address
route delete -host "$ip_address" -gateway "$gateway"

このスクリプトには二点問題がありました。一点目は、digした結果が複数出力される可能性があるが、スクリプトは一つだけ出力されることを前提にしていることです。chat.openai.comに対してdigした結果は下記です。cloudflareのCNAMEも出てきてしまっています。

$ dig +short A chat.openai.com
chat.openai.com.cdn.cloudflare.net.
104.18.2.161
104.18.3.161

複数の出力結果に対応できるようにし、かつIPアドレスだけを取得できるようにしたいです。

二点目の問題は、あまりないかもしれないですが、VPN接続時に取得したIPアドレスが、VPN切断時に改めて取得したIPアドレスと異なっており、ルーティングテーブルからの削除が正常に行えない事象が発生する可能性があることでした。これらをChatGPTに相談し、下記のようなスクリプトになりました。

add_route.sh
#!/bin/bash

# Domain to bypass VPN
domain="chat.openai.com"

# Get the default gateway for IPv4
gateway=$(netstat -nr -f inet | grep default | awk '{print $2}')

# Get the IP address and CNAME for the domain
ip_addresses_and_cnames=$(dig +short "$domain")

# Filter only IP addresses
ip_addresses=$(echo "$ip_addresses_and_cnames" | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b")

# Remove the previous temporary file if it exists
rm -f /tmp/added_route_ips.txt

# Add route for each IP address and save to a temporary file
for ip_address in $ip_addresses; do
  route add -host "$ip_address" -gateway "$gateway"
  echo "$ip_address" >> /tmp/added_route_ips.txt
done
delete_route.sh
#!/bin/bash

# Check if the temporary file with the added IP addresses exists
if [ -f /tmp/added_route_ips.txt ]; then
  # Get the default gateway for IPv4
  gateway=$(netstat -nr -f inet | grep default | awk '{print $2}')

  # Read the IP addresses from the temporary file and delete the route for each IP address
  while read ip_address; do
    route delete -host "$ip_address" -gateway "$gateway"
  done < /tmp/added_route_ips.txt

  # Remove the temporary file
  rm /tmp/added_route_ips.txt
fi

route addroute deleteを取得したIPアドレスの数だけ行うようになりました。また、route-up時に取得したIPアドレスを一時ファイルに出力し、切断時にそのファイルから参照することで、VPN接続中に対象サイトのIPアドレスが変わったとしてもルーティングテーブルから正常に削除できるようになりました。

この2つのスクリプトを任意の場所に置き、実行可能にします。

chmod +x /path/to/add_route.sh
chmod +x /path/to/delete_route.sh

.opvnファイルに下記を追記します。

script-security 2
route-up /path/to/add_route.sh
route-pre-down /path/to/delete_route.sh

script-security 2 は、OpenVPN設定ファイル(.ovpn)において、スクリプトの実行レベルを設定するオプションです。この設定がないとセキュリティ上の制約からスクリプトが実行されません。値を2にするとスクリプトの実行(管理者権限が必要なスクリプト含む)が許可されます。routeコマンドの実行に管理者権限が必要なため、設定する必要があります。

接続の確認

さて、ここまで出来たらVPN接続を確認してみます。接続してみると下記のようなエラーがログに出ており、接続ができませんでした。

Multiple --route-up scripts defined.  The previously configured script is overridden.

route-upは複数定義すると上書きされてしまうということで、どこかですでに一回実行されてしまっているようです。調べると、TunnelblickでVPN接続時に--route-upオプションをつけてopenvpnコマンドを実行しているようでした。これはどうしようもないため、手詰まりかと思っていましたが、Tunnelblickのドキュメントを見ていると、スクリプトを特定のイベントで実行できるしくみがありました。routeコマンドのような管理者権限が必要なコマンドの実行もできそうです。

Using Scripts - Tunnelblick | Free open source OpenVPN VPN client server software GUI for Mac OS X.

スクリプトを特定のタイミングで走らせる

下記を見ながら設定をしていきます。

Setting up Tunnelblick - Tunnelblick | Free open source OpenVPN VPN client server software GUI for Mac OS X.

まず任意の場所に適当な名前でディレクトリを作成します。ディレクトリ名のサフィックスは.tblkとする必要があります。

$ mkdir /path/to/config.tblk

.opvnの設定ファイルをそこに移動します。

$ mv config.opvn /path/to/config.tblk/

上の方で作っていたadd_route.shdelete_route.shconnected.shdown-prefix.shにリネームします。ファイル名によってどのタイミングで実行するかが決まるため、ドキュメントに記載のファイル名をそのまま使います。connected.shは接続後に実行され、down-prefix.shはTunnelblickが用意しているdownスクリプトが走る前に実行されます。[2]

ファイル名だけを変えたファイルの内容を再掲します。

connected.sh
#!/bin/bash

# Domain to bypass VPN
domain="chat.openai.com"

# Get the default gateway for IPv4
gateway=$(netstat -nr -f inet | grep default | awk '{print $2}')

# Get the IP address and CNAME for the domain
ip_addresses_and_cnames=$(dig +short "$domain")

# Filter only IP addresses
ip_addresses=$(echo "$ip_addresses_and_cnames" | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b")

# Remove the previous temporary file if it exists
rm -f /tmp/added_route_ips.txt

# Add route for each IP address and save to a temporary file
for ip_address in $ip_addresses; do
  route add -host "$ip_address" -gateway "$gateway"
  echo "$ip_address" >> /tmp/added_route_ips.txt
done
down-prefix.sh
#!/bin/bash

# Check if the temporary file with the added IP addresses exists
if [ -f /tmp/added_route_ips.txt ]; then
  # Get the default gateway for IPv4
  gateway=$(netstat -nr -f inet | grep default | awk '{print $2}')

  # Read the IP addresses from the temporary file and delete the route for each IP address
  while read ip_address; do
    route delete -host "$ip_address" -gateway "$gateway"
  done < /tmp/added_route_ips.txt

  # Remove the temporary file
  rm /tmp/added_route_ips.txt
fi

作成したディレクトリに移動します。

$ mv connected.sh /path/to/config.tblk/
$ mv down-prefix.sh /path/to/config.tblk/

実行権限も忘れずに。

chmod +x /path/to/config.tblk/connected.sh
chmod +x /path/to/config.tblk/down-prefix.sh

出来ているファイルを確認します。

$ ls /path/to/config.tblk 
config.ovpn     connected.sh    down-prefix.sh

config.tblkをTunnelblickの設定画面左カラムの接続先の場所あたりにドラッグアンドドロップします。また、すべてのトラフィックをVPN経由で接続するのチェックを外しておきます。

D&Dすると下記のモーダルが出てきます。

めちゃくちゃ怖い警告です。自分が完全に理解したスクリプト以外は.tblkディレクトリに置かないようにしましょう。インストールをクリックすると

念押しの確認がでてきます。かなり躊躇します。ここまで言われるとインストールする気も失せてきます。管理者権限で実行できるスクリプトが含まれるので何度も確認したい理由もわかります。何度も書きますが自分が完全に理解しているスクリプトだけを置くようにします。

インストールをクリックすると設定が追加されます。この設定を使うとChatGPT以外のサイトではVPNを通した接続となり、ChatGPTのサイトを開いたときはVPNを通さない設定になります。目的が達成できました。

脚注
  1. TunnelblickはOpenVPNクライアントなので、OpenVPNの設定 ↩︎

  2. route-pre-down-prefix.shにすると接続と切断を繰り返してうまく動かないので、down-prefix.shにしています。 ↩︎

Discussion