Đây là một rắc rối lớn để tìm ra, vì vậy tôi đã viết ra một hướng dẫn nhỏ với hy vọng rằng những người khác sẽ thấy nó hữu ích:
Cách thuyết phục macOS thực hiện tra cứu DNS IPv6 khi địa chỉ IPv6 duy nhất của bạn thông qua VPN hoặc đường hầm nào đó
Vấn đề
Trình phân giải tên miền của macOS sẽ chỉ trả về địa chỉ IPv6 (từ bản ghi AAAA) khi họ nghĩ rằng bạn có địa chỉ IPv6 có thể định tuyến hợp lệ. Đối với các giao diện vật lý như Ethernet hoặc Wi-Fi, đủ để đặt hoặc được gán địa chỉ IPv6, nhưng đối với các đường hầm (chẳng hạn như các utun
giao diện sử dụng giao diện), có một số bước khó chịu cần phải được thực hiện để thuyết phục hệ thống rằng có, thực sự bạn có địa chỉ IPv6 và vâng, bạn muốn lấy lại địa chỉ IPv6 để tra cứu DNS.
Tôi sử dụng wg-quick
để thiết lập một đường hầm WireGuard giữa máy tính xách tay của tôi và máy chủ ảo Linode. WireGuard sử dụng utun
thiết bị đường hầm không gian người dùng để thực hiện kết nối. Đây là cách thiết bị được cấu hình:
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>
Và đây là một vài dòng có liên quan từ bảng định tuyến của tôi:
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
là mạng ethernet cục bộ của tôi.
10.20.4.5
là địa chỉ IP LAN của máy tính xách tay của tôi.
10.20.4.4
là địa chỉ IP LAN của cổng của tôi.
10.75.131.2
là địa chỉ IPv4 ở cuối đường hầm điểm-điểm của WireGuard.
2600:3c03::de:d002
là địa chỉ IPv6 ở cuối đường hầm điểm-điểm của WireGuard.
50.116.51.30
là địa chỉ công cộng của máy chủ Linode của tôi.
Điều này là đủ để có kết nối IPv6, phải không? Vâng, độ phân giải tên hoạt động khi host
nói chuyện trực tiếp với máy chủ tên của tôi:
sam@shiny ~> host ipv6.whatismyv6.com
ipv6.whatismyv6.com has IPv6 address 2607:f0d0:3802:84::128
Ping theo địa chỉ IPv6 hoạt động:
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
Và các kết nối HTTP theo địa chỉ IPv6 hoạt động:
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
Tuy nhiên, các kết nối HTTP bằng tên máy chủ chỉ IPv6 không hoạt động:
sam@shiny ~> curl 'http://ipv6.whatismyv6.com'
curl: (6) Could not resolve host: ipv6.whatismyv6.com
Kết quả wget
cũng giống như trong các ứng dụng GUI như Firefox: kết nối bằng địa chỉ IPv6 theo nghĩa đen hoạt động tốt, nhưng kết nối bằng tên máy chủ chỉ có bản ghi AAAA (và không có bản ghi A) được liên kết với nó.
Điều thú vị là, ping6
là có thể làm một tra cứu DNS và nhận được một địa chỉ IPv6 trở lại:
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
Tại sao có thể ping6
làm điều này khi không có gì khác có thể? Nó chỉ ra rằng khi ping6
gọi getaddrinfo
nó sẽ ghi đè các cờ mặc định. Một trong những cờ mặc định là AI_ADDRCONFIG
, cho biết trình phân giải chỉ trả về địa chỉ trong các họ địa chỉ mà hệ thống có địa chỉ IP cho. (Nghĩa là, không trả lại địa chỉ IPv6 trừ khi hệ thống có địa chỉ IPv6 (không phải liên kết cục bộ).) Hầu hết các chương trình khác thêm vào các cờ mặc định thay vì ghi đè chúng, mà tôi cho là hợp lý.
Nếu bạn chạy scutil --dns
nó sẽ cho bạn biết cách thiết lập trình phân giải. Đây là đầu ra trên hệ thống của tôi (trừ một loạt các công cụ mdns không quan trọng):
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)
Lưu ý rằng dưới flags
, nó nói Request A records
nhưng không Request AAAA records
. Vì vậy, chúng tôi còn cố gắng thuyết phục trình phân giải của macOS rằng trên thực tế chúng tôi có địa chỉ IPv6 hợp lệ, mặc dù trên giao diện đường hầm.
Cấu hình hệ thông
Cách "đúng" cho điều này xảy ra là cho bất kỳ chương trình nào thiết lập đường hầm sử dụng SystemConfiguration
API kỳ lạ và phần lớn không có giấy tờ để đăng ký "dịch vụ" mạng và các thuộc tính IPv6 của nó. Ứng dụng Độ nhớt thực hiện điều này. Đường hầm không có, Máy khách OpenVPN chính thức thì không, và wg-quick
chắc chắn là không có địa ngục.
các scutil
kludge
Chúng ta có thể tạo các cấu trúc "dịch vụ" SystemConfiguration tương tự bằng cách sử dụng scutil
lệnh:
Đầu tiên chúng tôi tạo phần IPv4 của dịch vụ:
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
Và sau đó chúng tôi tạo phần 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
Khi điều này được thực hiện, đầu ra của scutil --dns
(một lần nữa công cụ modulo mdns) thay đổi:
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)
Bây giờ chúng ta thấy Request AAAA records
trong những lá cờ! Tôi không thực sự chắc chắn "truy vấn phạm vi" là gì hoặc tại sao cấu hình DNS cho chúng không thay đổi, nhưng mọi thứ dường như hoạt động ngay bây giờ.
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
Khi ngắt kết nối khỏi đường hầm, tất cả những gì bạn phải làm là xóa các khóa SystemConfiguration mà bạn đã thêm:
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
Một vài điều cần lưu ý:
- Cái tên
my_ipv6_tunnel_service
này hoàn toàn tùy ý.
- Theo thông tin tôi lượm lặt được từ các kịch bản lên / xuống trong
.ovpn
hồ sơ Mullvad , bạn phải tạo cả phím Setup:
và State:
phím. Tôi đã không xác minh điều này bởi vì tôi lười biếng.
- Tôi không biết IPv6
DestAddresses
đến từ đâu. Tôi đã sao chép chúng từ Viscosity vì chúng dường như hoạt động ở đó. ::ffff:ffff:ffff:ffff:0:0
cho địa chỉ liên kết cục bộ và ::
cho công chúng
- Tôi thậm chí không thực sự biết nó
DestAddresses
có nghĩa là gì hoặc dùng để làm gì.
Một kịch bản hay
Tôi đã viết một kịch bản python lướt qua địa chỉ và độ dài tiền tố từ ifconfig
đầu ra. Nó yêu cầu Python 3.6 trở lên, vì vậy hãy chắc chắn rằng bạn đã có nó trong đường dẫn của mình. Nó được gọi wg-updown
và gọi dịch vụ SystemConfiguration của nó wg-updown-utun#
, nhưng nó không thực sự đặc trưng cho WireGuard. Bạn có thể gọi nó như một tập lệnh đăng lên / xuống trước cho bất kỳ đường hầm VPN cũ nào hoặc chạy thủ công. Gọi nó như thế này:
# After tunnel comes up
wg-updown up IFACE
# Before tunnel goes down
wg-updown down IFACE
thay thế IFACE
bằng tên của giao diện mà máy khách đường hầm / VPN của bạn đang sử dụng, vd utun1
. Nó sẽ in các lệnh mà nó gửi đến scutil
để bạn có thể thấy những gì nó đang làm chi tiết.
#!/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()