Questa è stata una seccatura enorme da capire, quindi ho scritto una piccola guida nella speranza che altri lo trovino utile:
Come convincere macOS a fare ricerche DNS IPv6 quando il tuo unico indirizzo IPv6 è tramite una VPN o un tunnel di qualche tipo
Il problema
il risolutore di nomi di dominio macOS restituirà gli indirizzi IPv6 (dai record AAAA) quando ritiene di avere un indirizzo IPv6 instradabile valido. Per interfacce fisiche come Ethernet o Wi-Fi è sufficiente impostare o assegnare un indirizzo IPv6, ma per i tunnel (come quelli che usano le utun
interfacce) ci sono alcuni passaggi extra che devono essere presi per convincere il sistema che sì, tu davvero avere un indirizzo IPv6 e sì, si desidera recuperare gli indirizzi IPv6 per le ricerche DNS.
Uso wg-quick
per stabilire un tunnel WireGuard tra il mio laptop e un server virtuale Linode. WireGuard utilizza un utun
dispositivo tunnel spazio utente per effettuare la connessione. Ecco come viene configurato quel dispositivo:
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>
Ed ecco alcune righe pertinenti dalla mia tabella di routing:
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
è la mia rete ethernet locale.
10.20.4.5
è l'indirizzo IP LAN del mio laptop.
10.20.4.4
è l'indirizzo IP LAN del mio gateway.
10.75.131.2
è l'indirizzo IPv4 della mia estremità del tunnel punto-punto di WireGuard.
2600:3c03::de:d002
è l'indirizzo IPv6 della mia estremità del tunnel punto-punto WireGuard.
50.116.51.30
è l'indirizzo pubblico del mio server Linode.
Questo dovrebbe essere sufficiente per avere la connettività IPv6, giusto? Bene, la risoluzione dei nomi funziona quando host
parla direttamente al mio server dei nomi:
sam@shiny ~> host ipv6.whatismyv6.com
ipv6.whatismyv6.com has IPv6 address 2607:f0d0:3802:84::128
Il ping per indirizzo IPv6 funziona:
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
E le connessioni HTTP per indirizzo IPv6 funzionano:
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
Tuttavia, le connessioni HTTP con solo il nome host IPv6 non funzionano:
sam@shiny ~> curl 'http://ipv6.whatismyv6.com'
curl: (6) Could not resolve host: ipv6.whatismyv6.com
Il risultato è lo stesso wget
sia nelle app della GUI come Firefox: la connessione tramite un indirizzo IPv6 letterale funziona bene, ma la connessione con un nome host che ha solo un record AAAA (e nessun record A) associato non lo fa.
È interessante notare che ping6
è in grado di effettuare una ricerca DNS e ottenere un indirizzo IPv6:
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
Perché può ping6
farlo quando nient'altro può? Si scopre che quando ping6
chiama getaddrinfo
sovrascrive i flag predefiniti. Uno dei flag predefiniti è AI_ADDRCONFIG
, che indica al risolutore di restituire gli indirizzi solo nelle famiglie di indirizzi per cui il sistema dispone di un indirizzo IP. (Cioè, non restituire gli indirizzi IPv6 a meno che il sistema non disponga di un indirizzo IPv6 (non link-local). La maggior parte degli altri programmi aggiunge ai flag predefiniti anziché bloccarli, cosa che suppongo sia ragionevole.
Se lo esegui scutil --dns
ti dirà come è impostato il resolver. Ecco l'output sul mio sistema (meno un sacco di roba mdns che non importa):
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)
Si noti che sotto flags
, dice Request A records
ma non Request AAAA records
. Quindi ci resta da provare a convincere il risolutore di macOS che in realtà abbiamo un indirizzo IPv6 valido, anche se si trova su un'interfaccia tunnel.
Configurazione di sistema
Il modo "giusto" perché ciò avvenga è che qualunque programma installi il tunnel per utilizzare l' SystemConfiguration
API bizzarra e ampiamente non documentata per registrare il "servizio" di rete e le sue proprietà IPv6. L'app Viscosità fa questo. Tunnelblick no, il client OpenVPN ufficiale no, e wg-quick
sicuramente no.
Il scutil
Kludge
Possiamo creare manualmente le stesse strutture di "servizio" SystemConfiguration usando il scutil
comando:
Innanzitutto creiamo la parte IPv4 del servizio:
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
E quindi creiamo la parte 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
Fatto ciò, l'output di scutil --dns
(ancora roba modulo mdns) cambia:
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)
Ora vediamo Request AAAA records
tra le bandiere! Non sono davvero sicuro di cosa siano le "query con ambito" o perché la configurazione DNS per loro non sia cambiata, ma le cose sembrano funzionare ora quindi qualunque cosa:
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
Quando ti disconnetti dal tunnel, tutto ciò che devi fare è rimuovere le chiavi SystemConfiguration che hai aggiunto:
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
Un paio di cose da notare:
- Il nome
my_ipv6_tunnel_service
è totalmente arbitrario.
- Secondo le informazioni che ho raccolto dagli script su / giù nel
.ovpn
profilo Mullvad , devi creare sia i tasti Setup:
che i State:
tasti. Non l'ho verificato perché sono pigro.
- Non ho idea da dove
DestAddresses
provenga l'IPv6 . Li ho copiati da Viscosity perché sembravano funzionare lì. ::ffff:ffff:ffff:ffff:0:0
per l'indirizzo di collegamento locale e ::
per il pubblico
- Non so nemmeno cosa
DestAddresses
significhi o a cosa serva.
Una bella sceneggiatura
Ho scritto uno script Python che raccoglie indirizzi e lunghezze del prefisso ifconfig
dall'output. Richiede Python 3.6 o successivo, quindi assicurati di averlo nel tuo percorso. Si chiama wg-updown
e chiama il suo servizio SystemConfiguration wg-updown-utun#
, ma non è specifico per WireGuard. Potresti chiamarlo come script post-up / pre-down per qualsiasi vecchio tunnel VPN o eseguirlo manualmente. Chiamalo così:
# After tunnel comes up
wg-updown up IFACE
# Before tunnel goes down
wg-updown down IFACE
sostituire IFACE
con il nome dell'interfaccia utilizzata dal client tunnel / VPN, ad es utun1
. Stampa i comandi che sta inviando in scutil
modo da poter vedere cosa sta facendo in dettaglio.
#!/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()