C'était un énorme J'ai donc rédigé un petit guide dans l'espoir que d'autres personnes le trouvent utile :
Comment convaincre MacOS d'effectuer des recherches DNS IPv6 lorsque votre seule adresse IPv6 est obtenue via un VPN ou un tunnel quelconque ?
Le problème
Le résolveur de nom de domaine de MacOS ne renvoie les adresses IPv6 (à partir des enregistrements AAAA) que lorsqu'il pense que vous avez une adresse IPv6 routable valide. Pour les interfaces physiques telles que l'Ethernet ou le Wi-Fi, il suffit de définir ou de se voir attribuer une adresse IPv6, mais pour les tunnels (tels que ceux utilisant le protocole utun
interfaces) il y a des étapes supplémentaires ennuyeuses qui doivent être prises pour convaincre le système que oui, vous avez effectivement une adresse IPv6, et oui, vous aimeriez récupérer les adresses IPv6 pour les recherches DNS.
J'utilise wg-quick
pour établir un tunnel WireGuard entre mon ordinateur portable et un serveur virtuel Linode. WireGuard utilise un utun
périphérique de tunnel en espace utilisateur pour établir la connexion. Voici comment ce périphérique est configuré :
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>
Et voici quelques lignes pertinentes de ma table de routage :
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
est mon réseau local ethernet.
-
10.20.4.5
est l'adresse IP du réseau local de mon ordinateur portable.
-
10.20.4.4
est l'adresse IP du réseau local de ma passerelle.
-
10.75.131.2
est l'adresse IPv4 de mon extrémité du tunnel point à point WireGuard.
-
2600:3c03::de:d002
est l'adresse IPv6 de mon extrémité du tunnel point à point WireGuard.
-
50.116.51.30
est l'adresse publique de mon serveur Linode.
Cela devrait être suffisant pour avoir une connectivité IPv6, non ? Eh bien, la résolution de nom fonctionne lorsque host
communique directement avec mon serveur de noms :
sam@shiny ~> host ipv6.whatismyv6.com
ipv6.whatismyv6.com has IPv6 address 2607:f0d0:3802:84::128
Le ping par adresse IPv6 fonctionne :
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
Et les connexions HTTP par adresse IPv6 fonctionnent :
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
Cependant, les connexions HTTP par nom d'hôte IPv6 uniquement ne fonctionnent pas :
sam@shiny ~> curl 'http://ipv6.whatismyv6.com'
curl: (6) Could not resolve host: ipv6.whatismyv6.com
Le résultat est le même en wget
ainsi que dans les applications graphiques comme Firefox : la connexion par une adresse IPv6 littérale fonctionne bien, mais la connexion par un nom d'hôte auquel n'est associé qu'un enregistrement AAAA (et aucun enregistrement A) ne fonctionne pas.
C'est intéressant, ping6
est capable de faire une recherche DNS et d'obtenir une adresse IPv6 en retour :
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
Pourquoi ping6
faire ça quand rien d'autre ne le peut ? Il s'avère que lorsque ping6
appelle getaddrinfo
il écrase les drapeaux par défaut. L'un des drapeaux par défaut est AI_ADDRCONFIG
qui indique au résolveur de ne renvoyer que les adresses des familles d'adresses pour lesquelles le système possède une adresse IP. (C'est-à-dire ne pas renvoyer les adresses IPv6 à moins que le système ne possède une adresse IPv6 (non locale au lien)). La plupart des autres programmes ajouter aux drapeaux par défaut plutôt que de les écraser, ce qui, je suppose, est raisonnable.
Si vous exécutez scutil --dns
il vous indiquera comment le résolveur est configuré. Voici la sortie sur mon système (sans un tas de trucs mdns qui n'ont pas d'importance) :
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)
Notez que sous flags
il est dit Request A records
mais pas Request AAAA records
. Il nous reste donc à essayer de convaincre le résolveur de MacOS que nous avons bien une adresse IPv6 valide, même si elle se trouve sur une interface tunnel.
Configuration du système
La "bonne" façon pour que cela se produise est que le programme qui met en place le tunnel utilise la méthode bizarre et largement non documentée suivante SystemConfiguration
API pour enregistrer le "service" réseau et ses propriétés IPv6. L'application Viscosity le fait. Tunnelblick ne le fait pas, le Client OpenVPN officiel ne le fait pas, et le Client OpenVPN officiel ne le fait pas. wg-quick
c'est sûr que non.
Le site scutil
Kludge
Nous pouvons créer manuellement les mêmes structures de "service" SystemConfiguration à l'aide de la commande scutil
commandement :
Tout d'abord, nous créons la partie IPv4 du service :
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
Et ensuite on crée la partie 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
Une fois que cela est fait, la sortie de scutil --dns
(encore une fois modulo mdns stuff) changements :
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)
Maintenant, nous voyons Request AAAA records
dans les drapeaux ! Je ne suis pas vraiment sûr de ce que sont les "scoped queries" ou pourquoi la configuration DNS pour celles-ci n'a pas changé, mais les choses semblent fonctionner maintenant, alors peu importe :
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
Lors de la déconnexion du tunnel, il suffit de supprimer les clés SystemConfiguration que vous avez ajoutées :
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
Quelques points à noter :
- Le nom
my_ipv6_tunnel_service
est totalement arbitraire.
- D'après les informations que j'ai glanées dans les scripts de haut en bas du Mullvad.
.ovpn
vous devez créer à la fois le profil Setup:
y State:
les clés. Je ne l'ai pas vérifié parce que je suis paresseux.
- Je n'ai aucune idée de l'endroit où l'IPv6
DestAddresses
viennent de. Je les ai copiés de Viscosity parce qu'ils semblaient y fonctionner. ::ffff:ffff:ffff:ffff:0:0
pour l'adresse locale de liaison et ::
pour le public
- Je ne sais même pas vraiment ce que
DestAddresses
signifie ou à quoi il sert.
Un joli script
J'ai écrit un script en Python qui récupère les adresses et les longueurs de préfixe à partir des données suivantes ifconfig
sortie. Il nécessite Python 3.6 ou plus, donc assurez-vous que vous l'avez dans votre chemin. Il s'appelle wg-updown
et appelle son service SystemConfiguration wg-updown-utun#
mais ce n'est pas vraiment spécifique à WireGuard. Vous pourriez l'appeler comme un script post-up/pre-down pour n'importe quel vieux tunnel VPN ou l'exécuter manuellement. Appelez-le comme ceci :
# After tunnel comes up
wg-updown up IFACE
# Before tunnel goes down
wg-updown down IFACE
remplacer IFACE
avec le nom de l'interface que votre client tunnel/VPN utilise, par ex. utun1
. Il imprimera les commandes qu'il envoie à scutil
pour que vous puissiez voir ce qu'il fait en détail.
#!/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()