13.7 C
New York
Monday, October 21, 2024

macos – Methods to add AAAA flag (IPv6) to DNS resolver configuration on Sierra?


This was a large trouble to determine, so I wrote up just a little information in hopes that others would discover it useful:

The Drawback

macOS’s area identify resolver will solely return IPv6 addresses (from AAAA data) when it thinks that you’ve a legitimate routable IPv6 tackle. For bodily interfaces like Ethernet or Wi-Fi it is sufficient to set or be assigned an IPv6 tackle, however for tunnels (equivalent to these utilizing utun interfaces) there are some additional annoying steps that have to be taken to persuade the system that sure, you certainly have an IPv6 tackle, and sure, you’d wish to get IPv6 addresses again for DNS lookups.

I take advantage of wg-quick to determine a WireGuard tunnel between my laptop computer and a Linode digital server. WireGuard makes use of a utun user-space tunnel machine to make the connection. This is how that machine will get configured:

utun1: flags=8051 mtu 1420
    inet 10.75.131.2 --> 10.75.131.2 netmask 0xffffff00
    inet6 fe80::a65e:60ff:fee1:b1bfpercentutun1 prefixlen 64 scopeid 0xc
    inet6 2600:3c03::de:d002 prefixlen 116
    nd6 choices=201

And here is a number of related traces from my routing desk:

Web:
Vacation spot        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         hyperlink#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:
Vacation spot                             Gateway                         Flags         Netif Expire
::/1                                    utun1                           USc           utun1
2600:3c03::de:d000/116                  fe80::a65e:60ff:fee1:b1bfpercentutun1 Uc            utun1
8000::/1                                utun1                           USc           utun1
  • 10.20.4/24 is my native ethernet community.
  • 10.20.4.5 is my laptop computer’s LAN IP tackle.
  • 10.20.4.4 is my gateway’s LAN IP tackle.
  • 10.75.131.2 is the IPv4 tackle of my finish of the WireGuard point-to-point tunnel.
  • 2600:3c03::de:d002 is the IPv6 tackle of my finish of the WireGuard point-to-point tunnel.
  • 50.116.51.30 is the general public tackle of my Linode server.

This must be sufficient to have IPv6 connectivity, proper? Effectively, identify decision works when host talks on to my identify server:

sam@shiny ~> host ipv6.whatismyv6.com
ipv6.whatismyv6.com has IPv6 tackle 2607:f0d0:3802:84::128

Pinging by IPv6 tackle works:

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 acquired, 0.0% packet loss
round-trip min/avg/max/std-dev = 80.991/80.991/80.991/0.000 ms

And HTTP connections by IPv6 tackle work:

sam@shiny ~> curl -s 'http://[2607:f0d0:3802:84::128]' -H 'Host: ipv6.whatismyv6.com' | html2text | head -3
                 This web page reveals your IPv6 and/or IPv4 tackle
                          You're connecting with an IPv6 Handle of:
                                             2600:3c03::de:d002

Nevertheless, HTTP connections by IPv6-only hostname do not work:

sam@shiny ~> curl 'http://ipv6.whatismyv6.com'
curl: (6) Couldn't resolve host: ipv6.whatismyv6.com

The end result is similar in wget in addition to in GUI apps like Firefox: connecting by a literal IPv6 tackle works positive, however connecting by a hostname that solely has an AAAA file (and no A file) related to it doesn’t.

Curiously, ping6 is in a position to do a DNS lookup and get an IPv6 tackle again:

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 acquired, 0.0% packet loss
round-trip min/avg/max/std-dev = 49.513/49.513/49.513/0.000 ms

Why can ping6 do that when nothing else can? It seems that when ping6 calls getaddrinfo it overwrites the default flags. One of many default flags is AI_ADDRCONFIG, which tells the resolver to solely return addresses in tackle households that the system has an IP tackle for. (That’s, do not return IPv6 addresses except the system has a (not link-local) IPv6 tackle.) Most different applications add to the default flags reasonably than clobbering them, which I suppose is wise.

In the event you run scutil --dns it can let you know how the resolver is about up. This is the output on my system (minus a bunch of mdns stuff that does not matter):

DNS configuration

resolver #1
  search area[0] : dwelling.munkynet.org
  nameserver[0] : 10.20.4.4
  if_index : 14 (en3)
  flags    : Request A data
  attain    : 0x00020002 (Reachable,Immediately Reachable Handle)

DNS configuration (for scoped queries)

resolver #1
  search area[0] : dwelling.munkynet.org
  nameserver[0] : 10.20.4.4
  if_index : 14 (en3)
  flags    : Scoped, Request A data
  attain    : 0x00020002 (Reachable,Immediately Reachable Handle)

Notice that underneath flags, it says Request A data however not Request AAAA data. So it is left to us to attempt to persuade macOS’s resolver that we do the truth is have a legitimate IPv6 tackle, despite the fact that it is on a tunnel interface.

SystemConfiguration

The “proper” manner for this to occur is for no matter program units up the tunnel to make use of the weird and largely undocumented SystemConfiguration API to register the community “service” and its IPv6 properties. The Viscosity app does this. Tunnelblick doesn’t, the official OpenVPN Consumer doesn’t, and wg-quick certain as hell would not.

The scutil Kludge

We will create the identical SystemConfiguration “service” strucures manually utilizing the scutil command:

First we create the IPv4 a part of the 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:/Community/Service/my_ipv6_tunnel_service/IPv4
> set Setup:/Community/Service/my_ipv6_tunnel_service/IPv4

After which we create the IPv6 half:

> 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:/Community/Service/my_ipv6_tunnel_service/IPv6
> set Setup:/Community/Service/my_ipv6_tunnel_service/IPv6
> give up

As soon as that is carried out, the output of scutil --dns (once more modulo mdns stuff) adjustments:

DNS configuration

resolver #1
  search area[0] : dwelling.munkynet.org
  nameserver[0] : 10.20.4.4
  if_index : 14 (en3)
  flags    : Request A data, Request AAAA data
  attain    : 0x00020002 (Reachable,Immediately Reachable Handle)

DNS configuration (for scoped queries)

resolver #1
  search area[0] : dwelling.munkynet.org
  nameserver[0] : 10.20.4.4
  if_index : 14 (en3)
  flags    : Scoped, Request A data
  attain    : 0x00020002 (Reachable,Immediately Reachable Handle)

Now we see Request AAAA data within the flags! I am not likely certain what “scoped queries” are or why the DNS configuration for them did not change, however issues appear to work now so no matter:

sam@shiny ~> curl -s 'http://ipv6.whatismyv6.com' | html2text | head -3
                 This web page reveals your IPv6 and/or IPv4 tackle
                          You're connecting with an IPv6 Handle of:
                                             2600:3c03::de:d002

When disconnecting from the tunnel, all you need to do is take away the SystemConfiguration keys you added:

sam@shiny ~> sudo scutil
> take away State:/Community/Service/my_ipv6_tunnel_service/IPv4
> take away Setup:/Community/Service/my_ipv6_tunnel_service/IPv4
> take away State:/Community/Service/my_ipv6_tunnel_service/IPv6
> take away Setup:/Community/Service/my_ipv6_tunnel_service/IPv6
> give up

A pair issues to notice:

  • The identify my_ipv6_tunnel_service is completely arbitrary.
  • Based on info I gleaned from the up/down scripts within the Mullvad .ovpn profile, you need to create each the Setup: and State: keys. I did not confirm this as a result of I’m lazy.
  • I’ve no clue the place the IPv6 DestAddresses come from. I copied these from Viscosity as a result of they appeared to work there. ::ffff:ffff:ffff:ffff:0:0 for the link-local tackle and :: for the general public
  • I do not even actually know what DestAddresses means or what it is used for.

A pleasant script

I wrote a python script that gleans addresses and prefix lengths from ifconfig output. It requires Python 3.6 or later so be sure to’ve bought that in your path. It is known as wg-updown and calls its SystemConfiguration service wg-updown-utun#, nevertheless it’s not likely WireGuard-specific. You may name it as a post-up/pre-down script for any previous VPN tunnel or run it manually. Name it like this:

# After tunnel comes up
wg-updown up IFACE

# Earlier than tunnel goes down
wg-updown down IFACE

change IFACE with the identify of the interface that your tunnel/VPN shopper is utilizing, e.g. utun1. It would print the instructions that it is sending to scutil so you’ll be able to see what it is doing intimately.

#!/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*inets+(S+)s+-->s+(S+)s+netmasks+S+')
v6pat = re.compile(r'^s*inet6s+(S+?)(?:%S+)?s+prefixlens+(S+)')
def get_tunnel_info(interface):
    ipv4s = dict(Addresses=[], DestAddresses=[])
    ipv6s = dict(Addresses=[], DestAddresses=[], Flags=[], PrefixLength=[])
    ifconfig = subprocess.run(["ifconfig", interface], capture_output=True,
                              verify=True, textual content=True)
    for line in ifconfig.stdout.splitlines():
        v6match = v6pat.match(line)
        if v6match:
            ipv6s['Addresses'].append(v6match[1])
            # That is cribbed from Viscosity and possibly fallacious.
            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])
            proceed
        v4match = v4pat.match(line)
        if v4match:
            ipv4s['Addresses'].append(v4match[1])
            ipv4s['DestAddresses'].append(v4match[2])
            proceed
    return (ipv4s, ipv6s)

def run_scutil(instructions):
    print(instructions)
    subprocess.run(['scutil'], enter=instructions, verify=True, textual content=True)

def up(interface):
    service_name = service_name_for_interface(interface)
    (ipv4s, ipv6s) = get_tunnel_info(interface)
    run_scutil('n'.be part of([
        f"d.init",
        f"d.add Addresses * {' '.join(ipv4s['Addresses'])}",
        f"d.add DestAddresses * {' '.be part of(ipv4s['DestAddresses'])}",
        f"d.add InterfaceName {interface}",
        f"set State:/Community/Service/{service_name}/IPv4",
        f"set Setup:/Community/Service/{service_name}/IPv4",
        f"d.init",
        f"d.add Addresses * {' '.be part of(ipv6s['Addresses'])}",
        f"d.add DestAddresses * {' '.be part of(ipv6s['DestAddresses'])}",
        f"d.add Flags * {' '.be part of(ipv6s['Flags'])}",
        f"d.add InterfaceName {interface}",
        f"d.add PrefixLength * {' '.be part of(ipv6s['PrefixLength'])}",
        f"set State:/Community/Service/{service_name}/IPv6",
        f"set Setup:/Community/Service/{service_name}/IPv6",
    ]))

def down(interface):
    service_name = service_name_for_interface(interface)
    run_scutil('n'.be part of([
        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 important():
    operation = sys.argv[1]
    interface = sys.argv[2]
    if operation == 'up':
        up(interface)
    elif operation == 'down':
        down(interface)
    else:
        increase NotImplementedError()

if __name__ == "__main__":
    important()

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles