これは理解するのが非常に面倒だったので、他の人が役に立つと期待して少しガイドを書きました。
唯一のIPv6アドレスがVPNまたはある種のトンネル経由である場合に、macOSにIPv6 DNSルックアップを実行させる方法
問題
macOSのドメイン名リゾルバーは、有効なルーティング可能なIPv6アドレスがあると判断した場合にのみ(AAAAレコードから)IPv6アドレスを返します。イーサネットやWi-Fiのような物理インターフェースの場合、IPv6アドレスを設定または割り当てるだけで十分ですが、トンネル(utun
インターフェースを使用するものなど)の場合、システムに「はい、本当に」 IPv6アドレスがあり、はい、DNSルックアップのためにIPv6アドレスを取得したいです。
wg-quick
ラップトップとLinode仮想サーバーの間にWireGuardトンネルを確立するために使用します。WireGuardは、utun
ユーザー空間トンネルデバイスを使用して接続を確立します。そのデバイスの設定方法は次のとおりです。
utun1: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1420
inet 10.75.131.2 --> 10.75.131.2 netmask 0xffffff00
inet6 fe80::a65e:60ff:fee1:b1bf%utun1 prefixlen 64 scopeid 0xc
inet6 2600:3c03::de:d002 prefixlen 116
nd6 options=201<PERFORMNUD,DAD>
そして、ここに私のルーティングテーブルからのいくつかの関連する行があります:
Internet:
Destination Gateway Flags Refs Use Netif Expire
0/1 utun1 USc 0 0 utun1
default 10.20.4.4 UGSc 0 0 en3
10.20.4/24 link#14 UCS 3 0 en3 !
10.75.131.2 10.75.131.2 UH 0 0 utun1
50.116.51.30 10.20.4.4 UGHS 7 2629464 en3
128.0/1 utun1 USc 5 0 utun1
Internet6:
Destination Gateway Flags Netif Expire
::/1 utun1 USc utun1
2600:3c03::de:d000/116 fe80::a65e:60ff:fee1:b1bf%utun1 Uc utun1
8000::/1 utun1 USc utun1
10.20.4/24
私のローカルイーサネットネットワークです。
10.20.4.5
私のラップトップのLAN IPアドレスです。
10.20.4.4
ゲートウェイのLAN IPアドレスです。
10.75.131.2
WireGuardポイントツーポイントトンネルの終端のIPv4アドレスです。
2600:3c03::de:d002
WireGuardポイントツーポイントトンネルの終端のIPv6アドレスです。
50.116.51.30
Linodeサーバーのパブリックアドレスです。
これで、IPv6接続が可能になりますか?さて、名前解決は、host
私のネームサーバーと直接対話するときに機能します。
sam@shiny ~> host ipv6.whatismyv6.com
ipv6.whatismyv6.com has IPv6 address 2607:f0d0:3802:84::128
IPv6アドレスによるPingは機能します。
sam@shiny ~> ping6 -c1 2607:f0d0:3802:84::128
PING6(56=40+8+8 bytes) 2600:3c03::de:d002 --> 2607:f0d0:3802:84::128
16 bytes from 2607:f0d0:3802:84::128, icmp_seq=0 hlim=55 time=80.991 ms
--- 2607:f0d0:3802:84::128 ping6 statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 80.991/80.991/80.991/0.000 ms
また、IPv6アドレスによるHTTP接続は機能します。
sam@shiny ~> curl -s 'http://[2607:f0d0:3802:84::128]' -H 'Host: ipv6.whatismyv6.com' | html2text | head -3
This page shows your IPv6 and/or IPv4 address
You are connecting with an IPv6 Address of:
2600:3c03::de:d002
ただし、IPv6のみのホスト名によるHTTP接続は機能しません。
sam@shiny ~> curl 'http://ipv6.whatismyv6.com'
curl: (6) Could not resolve host: ipv6.whatismyv6.com
結果は、wget
FirefoxのようなGUIアプリでも同じです。リテラルIPv6アドレスによる接続は正常に機能しますが、AAAAレコードのみが関連付けられている(Aレコードがない)ホスト名による接続はうまくいきません。
興味深いことに、DNSルックアップを実行してIPv6アドレスを取得することping6
ができます。
sam@shiny ~ [6]> ping6 -c1 ipv6.whatismyv6.com
PING6(56=40+8+8 bytes) 2600:3c03::de:d002 --> 2607:f0d0:3802:84::128
16 bytes from 2607:f0d0:3802:84::128, icmp_seq=0 hlim=55 time=49.513 ms
--- ipv6.whatismyv6.com ping6 statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 49.513/49.513/49.513/0.000 ms
ping6
他に何もできないのになぜこれができるのでしょうか?それは時にあることが判明ping6
呼び出し、getaddrinfo
それがデフォルトのフラグを上書きします。デフォルトフラグの1つは、AI_ADDRCONFIG
システムにIPアドレスがあるアドレスファミリのアドレスのみを返すようにリゾルバに指示します。(つまり、システムに(リンクローカルではない)IPv6アドレスがない限り、IPv6アドレスを返さないでください。)他のほとんどのプログラムは、デフォルトのフラグを上書きするのではなく追加します。
実行scutil --dns
すると、リゾルバの設定方法が表示されます。これが私のシステムの出力です(重要なことはありませんが、mdnsのものはたくさんありません):
DNS configuration
resolver #1
search domain[0] : home.munkynet.org
nameserver[0] : 10.20.4.4
if_index : 14 (en3)
flags : Request A records
reach : 0x00020002 (Reachable,Directly Reachable Address)
DNS configuration (for scoped queries)
resolver #1
search domain[0] : home.munkynet.org
nameserver[0] : 10.20.4.4
if_index : 14 (en3)
flags : Scoped, Request A records
reach : 0x00020002 (Reachable,Directly Reachable Address)
その下に注意してくださいflags
、それが言うRequest A records
はありませんRequest AAAA records
。そのため、トンネルインターフェイス上であっても、実際には有効なIPv6アドレスを持っていることをmacOSのリゾルバーに納得させることは私たちに任されています。
システム構成
これが起こる「正しい」方法は、トンネルをセットアップするプログラムが、奇妙で大部分が文書化されていないSystemConfiguration
APIを使用してネットワークの「サービス」とそのIPv6プロパティを登録することです。Viscosityアプリがこれを行います。Tunnelblickはサポートしていませんが、公式のOpenVPNクライアントはサポートしていwg-quick
ません。
scutil
その場しのぎ
scutil
次のコマンドを使用して、同じSystemConfiguration "サービス"構造を手動で作成できます。
最初に、サービスのIPv4部分を作成します。
sam@shiny ~> sudo scutil
> d.init
> d.add Addresses * 10.75.131.2
> d.add DestAddresses * 10.75.131.2
> d.add InterfaceName utun1
> set State:/Network/Service/my_ipv6_tunnel_service/IPv4
> set Setup:/Network/Service/my_ipv6_tunnel_service/IPv4
次に、IPv6部分を作成します。
> d.init
> d.add Addresses * fe80::a65e:60ff:fee1:b1bf 2600:3c03::de:d002
> d.add DestAddresses * ::ffff:ffff:ffff:ffff:0:0 ::
> d.add Flags * 0 0
> d.add InterfaceName utun1
> d.add PrefixLength * 64 116
> set State:/Network/Service/my_ipv6_tunnel_service/IPv6
> set Setup:/Network/Service/my_ipv6_tunnel_service/IPv6
> quit
これが完了すると、scutil --dns
(再びmdnsを法として)出力が変わります:
DNS configuration
resolver #1
search domain[0] : home.munkynet.org
nameserver[0] : 10.20.4.4
if_index : 14 (en3)
flags : Request A records, Request AAAA records
reach : 0x00020002 (Reachable,Directly Reachable Address)
DNS configuration (for scoped queries)
resolver #1
search domain[0] : home.munkynet.org
nameserver[0] : 10.20.4.4
if_index : 14 (en3)
flags : Scoped, Request A records
reach : 0x00020002 (Reachable,Directly Reachable Address)
Request AAAA records
フラグに表示されます!「スコープクエリ」とは何か、またはそれらのDNS構成が変更されなかった理由はよくわかりませんが、今はうまくいくようです。
sam@shiny ~> curl -s 'http://ipv6.whatismyv6.com' | html2text | head -3
This page shows your IPv6 and/or IPv4 address
You are connecting with an IPv6 Address of:
2600:3c03::de:d002
トンネルから切断するときは、追加したSystemConfigurationキーを削除するだけです。
sam@shiny ~> sudo scutil
> remove State:/Network/Service/my_ipv6_tunnel_service/IPv4
> remove Setup:/Network/Service/my_ipv6_tunnel_service/IPv4
> remove State:/Network/Service/my_ipv6_tunnel_service/IPv6
> remove Setup:/Network/Service/my_ipv6_tunnel_service/IPv6
> quit
注意点がいくつかあります:
- 名前
my_ipv6_tunnel_service
は完全に任意です。
- Mullvad
.ovpn
プロファイルのアップ/ダウンスクリプトから収集した情報によるSetup:
と、State:
キーとキーの両方を作成する必要があります。私は怠け者なので、これを確認しませんでした。
- IPv6
DestAddresses
がどこから来たのかはわかりません。そこから機能しているように見えたので、これらをViscosityからコピーしました。::ffff:ffff:ffff:ffff:0:0
リンクローカルアドレスおよび::
一般向け
- 私は本当に何を
DestAddresses
意味するのか、それが何のために使われるのかさえ知りません。
素敵なスクリプト
ifconfig
出力からアドレスとプレフィックス長を収集するPythonスクリプトを作成しました。Python 3.6以降が必要なので、パスにそれがあることを確認してください。それは呼ばれていますwg-updown
し、そのSystemConfigurationサービスを呼び出しwg-updown-utun#
、それは本当にWireGuard固有ではありません。古いVPNトンネルのポストアップ/プレダウンスクリプトとして呼び出すことも、手動で実行することもできます。次のように呼び出します。
# After tunnel comes up
wg-updown up IFACE
# Before tunnel goes down
wg-updown down IFACE
IFACE
トンネル/ VPNクライアントが使用しているインターフェイスの名前、たとえばに置き換えますutun1
。送信先のコマンドを出力するscutil
ので、何をしているのかを詳細に確認できます。
#!/usr/bin/env python3
import re
import subprocess
import sys
def service_name_for_interface(interface):
return 'wg-updown-' + interface
v4pat = re.compile(r'^\s*inet\s+(\S+)\s+-->\s+(\S+)\s+netmask\s+\S+')
v6pat = re.compile(r'^\s*inet6\s+(\S+?)(?:%\S+)?\s+prefixlen\s+(\S+)')
def get_tunnel_info(interface):
ipv4s = dict(Addresses=[], DestAddresses=[])
ipv6s = dict(Addresses=[], DestAddresses=[], Flags=[], PrefixLength=[])
ifconfig = subprocess.run(["ifconfig", interface], capture_output=True,
check=True, text=True)
for line in ifconfig.stdout.splitlines():
v6match = v6pat.match(line)
if v6match:
ipv6s['Addresses'].append(v6match[1])
# This is cribbed from Viscosity and probably wrong.
if v6match[1].startswith('fe80'):
ipv6s['DestAddresses'].append('::ffff:ffff:ffff:ffff:0:0')
else:
ipv6s['DestAddresses'].append('::')
ipv6s['Flags'].append('0')
ipv6s['PrefixLength'].append(v6match[2])
continue
v4match = v4pat.match(line)
if v4match:
ipv4s['Addresses'].append(v4match[1])
ipv4s['DestAddresses'].append(v4match[2])
continue
return (ipv4s, ipv6s)
def run_scutil(commands):
print(commands)
subprocess.run(['scutil'], input=commands, check=True, text=True)
def up(interface):
service_name = service_name_for_interface(interface)
(ipv4s, ipv6s) = get_tunnel_info(interface)
run_scutil('\n'.join([
f"d.init",
f"d.add Addresses * {' '.join(ipv4s['Addresses'])}",
f"d.add DestAddresses * {' '.join(ipv4s['DestAddresses'])}",
f"d.add InterfaceName {interface}",
f"set State:/Network/Service/{service_name}/IPv4",
f"set Setup:/Network/Service/{service_name}/IPv4",
f"d.init",
f"d.add Addresses * {' '.join(ipv6s['Addresses'])}",
f"d.add DestAddresses * {' '.join(ipv6s['DestAddresses'])}",
f"d.add Flags * {' '.join(ipv6s['Flags'])}",
f"d.add InterfaceName {interface}",
f"d.add PrefixLength * {' '.join(ipv6s['PrefixLength'])}",
f"set State:/Network/Service/{service_name}/IPv6",
f"set Setup:/Network/Service/{service_name}/IPv6",
]))
def down(interface):
service_name = service_name_for_interface(interface)
run_scutil('\n'.join([
f"remove State:/Network/Service/{service_name}/IPv4",
f"remove Setup:/Network/Service/{service_name}/IPv4",
f"remove State:/Network/Service/{service_name}/IPv6",
f"remove Setup:/Network/Service/{service_name}/IPv6",
]))
def main():
operation = sys.argv[1]
interface = sys.argv[2]
if operation == 'up':
up(interface)
elif operation == 'down':
down(interface)
else:
raise NotImplementedError()
if __name__ == "__main__":
main()