Basically – If you are using OpenVPN with IPv6 support, MacOS will not recognize it and only allow IPv4 DNS resolution.
References:
https://openvpn.net/vpn-server-resources/limited-ipv6-support-built-into-the-access-server/
https://apple.stackexchange.com/questions/304215/how-to-add-aaaa-flag-ipv6-to-dns-resolver-configuration-on-sierra
https://www.tunnelblick.net/cUsingScripts.html
https://tunnelblick.net/cConfigT.html#creating-and-installing-a-tunnelblick-vpn-configuration
First, you have to configure IPv6 in your OpenVPN Server. For OpenVPN AS, there is a guide linked above.
Once IPv6 is working in the tunnel, you will probably recognize that some services are working, but in some applications (like Safari), IPv6 only sites won’t work. This is because they use the systems DNS resolver, which doesn’t allow AAAA requests, if it thinks that it has no IPv6 internet access.
You can check this using:
scutil --dns DNS configuration resolver #1 search domain[0] : openvpn nameserver[0] : 8.8.8.8 nameserver[1] : 8.8.4.4 flags : Request A records reach : 0x00000002 (Reachable)
As you can see above, the resolver is only used for A records.
On Stackexchange, user smammy created a nice writeup for Wireguard, that utilizes a script to convince the MacOS resolver to resolve AAAA records using the given DNS servers. This can easily be adapted for Tunnelblick.
You need to create a Tunnelblick configuration package by creating a folder, adding the necessary files to it and adding the .tblk ending to it. MacOS will recognize it as a Tunnelblick package then and allow you to install the configuration including the necessary scripts.
The folder should contain 4 files:
- profile.ovpn
- up-suffix.sh
- down-suffix.sh
- wg-updown.sh
profile.ovpn
This is your OpenVPN profile, in my case the auto login profile exported from OpenvpnAS.
up-suffix.sh
This is executed when establishing the connection.
./wg-updown.sh up utun3
down-suffix.sh
This is executed when terminating the connection.
./wg-updown.sh down utun3
wg-updown.sh
This is the script from stack exchange
#!/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()
When the folder/package is complete, it can be imported to Tunnelblick. You will get a lot of warnings.
After importing, the connection can be started. After it is established, the output of scutils should look something like this:
scutil --dns DNS configuration resolver #1 search domain[0] : openvpn nameserver[0] : 8.8.8.8 nameserver[1] : 8.8.4.4 flags : Request A records, Request AAAA records reach : 0x00000002 (Reachable)
Success! AAAA DNS resolution should now work in all applications.
What I haven’t gotten to work yet, is that MacOS recognizes IPv6 DNS servers pushed trough the VPN config. I could only get it to resolve IPv6 addresses trough IPv4 DNS servers. If you have any success with this, please let me know.