How to make LXD's dnsmasq serve only IPv6 addresses for a DNS zone?

Hello!

Is there a way to:

  • Assign public IPv6 addresses to containers via DHCPv6 from a specific range;
  • Assign private (RFC1918) IPv4 addresses to the very same containers;
  • Configure dnsmasq to both serve as:
    • a DHCP(v6) server to containers, and
    • a public DNS server that manages a zone assigned to the containers (foo.example.com in my example), and responds to AAAA requests for container public IPv6 addresses but not to A requests (hiding private IPv4 addresses from clients)

Basically what I want is to have a bunch of LXC containers which would be resolveable and accessible via IPv6 only. How do I do that?

Here’s a relevant part of lxd init's preseed configuration:

networks:
- config:
    dns.domain: foo.example.com
    ipv4.address: auto
    ipv4.nat: "true"

    # IPv6 address of lxdbr0
    ipv6.address: 2001:1234:5678:9abc::1

    # Containers get assigned IPv6 addresses from this range
    ipv6.dhcp: "true"
    ipv6.dhcp.ranges: 2001:1234:5678:9abc::0002-2001:1234:5678:9abc::ffff
    ipv6.dhcp.stateful: "true"
    ipv6.nat: "false"

    raw.dnsmasq: |
      auth-zone=foo.example.com
      dns-loop-detect

  description: ""
  name: lxdbr0
  type: "bridge"
  project: default

This almost works, in that the containers get assigned both IPv4 and IPv6 addresses:

$ lxc list

+------+---------+----------------------+----------------------------------+-----------+-----------+
| NAME |  STATE  |         IPV4         |              IPV6                |   TYPE    | SNAPSHOTS |
+------+---------+----------------------+----------------------------------+-----------+-----------+
| test | RUNNING | 10.33.156.141 (eth0) | 2001:1234:5678:9abc::768e (eth0) | CONTAINER | 0         |
+------+---------+----------------------+----------------------------------+-----------+-----------+

…and dnsmasq listening on lxdbr0 responds to AAAA queries for the containers:

$ dig AAAA test.foo.example.com @2001:1234:5678:9abc::1

; <<>> DiG 9.16.1-Ubuntu <<>> AAAA test.foo.example.com @2001:1234:5678:9abc::1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 63597
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;test.foo.example.com.	IN	AAAA

;; ANSWER SECTION:
test.foo.example.com. 600 IN	AAAA	2001:1234:5678:9abc::768e

;; Query time: 0 msec
;; SERVER: 2001:1234:5678:9abc::1#53(2001:1234:5678:9abc::1)
;; WHEN: Sat Nov 14 14:32:45 EST 2020
;; MSG SIZE  rcvd: 84

…but it seems to be happy to report back private IPv4 addresses of said containers too:

$ dig A test.foo.example.com @2001:1234:5678:9abc::1

; <<>> DiG 9.16.1-Ubuntu <<>> A test.foo.example.com @2001:1234:5678:9abc::1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8897
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;test.foo.example.com.	IN	A

;; ANSWER SECTION:
test.foo.example.com. 600 IN	A	10.33.156.141

;; Query time: 0 msec
;; SERVER: 2001:1234:5678:9abc::1#53(2001:1234:5678:9abc::1)
;; WHEN: Sat Nov 14 14:36:12 EST 2020
;; MSG SIZE  rcvd: 72

…which is something that I don’t want.

As per dnsmasq’s documentation, I’ve tried setting some more raw.dnsmasq parameters to configure the dnsmasq to serve a zone and limit its responses to IPv6 addresses as follows:

# * NS record for foo.example.com points to foo.ns.example.com
# * AAAA record for foo.ns.example.com points to 2001:1234:5678:9abc::1
#   (address for lxdbr0)
raw.dnsmasq: |
  auth-server=foo.ns.example.com,lxdbr0/6
  auth-zone=foo.example.com,2001:1234:5678:9abc::/64,lxdbr0/6
  dns-loop-detect

With the configuration above, dnsmasq no longer responds to A queries (as intended) but then DHCPv6 stops working, i.e. upon container restart, it no longer acquires an IPv6 address from dnsmasq:

$ lxc list

+------+---------+----------------------+------+-----------+-----------+
| NAME |  STATE  |         IPV4         | IPV6 |   TYPE    | SNAPSHOTS |
+------+---------+----------------------+------+-----------+-----------+
| test | RUNNING | 10.33.156.141 (eth0) |      | CONTAINER | 0         |
+------+---------+----------------------+------+-----------+-----------+

What should I try next?

(I’m on Ubuntu 20.04 and running LXD 4.7 (18251) from Snap.)

I don’t think that is possible with with dnsmasq (although if it is then using the raw.dnsmasq setting that you’re already using would be the way to go).

One possible approach that does come to mind is to use that combined with the --dhcp-script option in dnsmasq to run a custom script on each lease allocation and then perhaps maintain a database combined with generation of an external dns server’s config containing your external IPv6 IPs.

-6 --dhcp-script=<path>

Whenever a new DHCP lease is created, or an old one destroyed, or a TFTP file transfer completes, the executable specified by this option is run. must be an absolute pathname, no PATH search occurs. The arguments to the process are “add”, “old” or “del”, the MAC address of the host (or DUID for IPv6) , the IP address, and the hostname, if known. “add” means a lease has been created, “del” means it has been destroyed, “old” is a notification of an existing lease when dnsmasq starts or a change to MAC address or hostname of an existing lease (also, lease length or expiry and client-id, if –leasefile-ro is set and lease expiry if –script-on-renewal is set). If the MAC address is from a network type other than ethernet, it will have the network type prepended, eg “06-01:23:45:67:89:ab” for token ring. The process is run as root (assuming that dnsmasq was originally run as root) even if dnsmasq is configured to change UID to an unprivileged user.

--dhcp-scriptuser

Specify the user as which to run the lease-change script or Lua script. This defaults to root, but can be changed to another user using this flag.

See http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html