Configure DNS server using docker to handle requests from sub-domain

DevOps Sep 24, 2019

Update

It is not recommended if using a recursive DNS server for public service. Dnsmasq, however, only supports recursive. Using dnsmasq in public can make your server vulnerable to DDOS attacks.

Why Dnsmasq

After adding too many DNS records to my Cloudflare account, I find it necessary to find a way to reduce the record. However, using wildcards is only available for pro-plan users. So I decided to deploy my own DNS server.

Then I chose to deploy dnsmasq, a simple DNS server, on docker.

Configure dnsmasq

Docker Compose

The docker-compose.yml looks like this:

version: '3.6'
services:
  dnsmasq:
    image: andyshinn/dnsmasq
    restart: always
    container_name: 'dnsmasq'
    network_mode: host
    cap_add:
      - NET_ADMIN
    volumes:
      - ./data/dnsmasq.conf:/etc/dnsmasq.conf
      - ./data/dnsmasqhosts:/etc/dnsmasqhosts
      - ./data/resolv.dnsmasq.conf:/etc/resolv.dnsmasq.conf

Together with this file, we create a folder called data, which contains the configuration file of dnsmasq.

dnsmasq. conf

# setting upstream dns server
resolv-file=/etc/resolv.dnsmasq.conf
addn-hosts=/etc/dnsmasqhosts

strict-order
# both local address and public address
listen-address=127.0.0.1,202.182.*.*

# upstream dns server
server=108.61.*.*

# prevent dns hijacking
bogus-nxdomain=108.61.*.*

address=/*.*.example.com/202.182.*.*

resolv.dnsmasq.conf

nameserver 108.61.*.*

After setting up, use docker-compose up to start the server. Which may get an error like this

docker: Error response from daemon: failed to create endpoint DNS-server on network bridge: Error starting userland proxy: listen tcp 0.0.0.0:53: bind: address already in use.

Using netstat -ltnp, I found the port was used by systemd-resolved, so I stopped the server and retried.

sudo systemctl stop systemd-resolved

This time, dnsmasq was successfully started

Systemd-resolved

However, stopping systemd-resolved may also stop your network. So it is necessary to configure systemd-resolved not occupying the port.

Editing the following files and replace them with this:

/etc/systemd/resolved.conf

#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.
#
# Entries in this file show the compile time defaults.
# You can change settings by editing this file.
# Defaults can be restored by simply deleting this file.
#
# See resolved.conf(5) for details

[Resolve]
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.
#
# Entries in this file show the compile time defaults.
# You can change settings by editing this file.
# Defaults can be restored by simply deleting this file.
#
# See resolved.conf(5) for details

[Resolve]
#DNS=
#FallbackDNS=
#Domains=
#LLMNR=no
#MulticastDNS=no
#DNSSEC=no
#DNSOverTLS=no
#Cache=yes
DNSStubListener=no
#ReadEtcHosts=yes

/etc/resolve.conf
Before editing this file, you need to remove and create it again to disable the systemd to create a symlink on it.

nameserver 127.0.0.1
nameserver 108.61.*.*

Testing DNS server on local

Now you may use dig to test the server

$ dig google.com

; <<>> DiG 9.11.5-P1-1ubuntu2.3-Ubuntu <<>> google.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44353
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

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

;; ANSWER SECTION:
google.com.             161     IN      A       172.217.31.174

;; Query time: 0 msec
;; SERVER: 108.61.*.*#53(108.61.*.*)
;; WHEN: Tue Sep 24 17:33:49 UTC 2019
;; MSG SIZE  rcvd: 55
$ dig *.*.example.com

; <<>> DiG 9.11.5-P1-1ubuntu2.3-Ubuntu <<>> *.*.example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26535
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

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

;; ANSWER SECTION:
*.*.example.com. 30 IN A  202.182.*.*

;; Query time: 9 msec
;; SERVER: 108.61.*.*#53(108.61.*.*)
;; WHEN: Tue Sep 24 17:35:10 UTC 2019
;; MSG SIZE  rcvd: 82

All the requests are successful.

Adding NS record to DNS provider

However, on another machine, the second query request cannot be processed and will return SERVFAIL. To let other machines be able to use our newly established DNS server, we need to add an NS record to it. In this example, I will use Cloudflare.

Firstly, adding an A record pointed to the server.

Second, add an NS record of your sub-domain which you want your custom DNS server to handle.

Now the server will be able to handle requests from other machines.

References

[0] https://blog.csainty.com/2016/09/running-dnsmasq-in-docker.html
[1] https://www.digitalocean.com/community/questions/dnsmasq-and-local-docker-container
[2] https://www.coderli.com/config-dnsmasq-using-docker/
[3] https://medium.com/@niktrix/getting-rid-of-systemd-resolved-consuming-port-53-605f0234f32f
[4] https://qiita.com/bmj0114/items/9c24d863bcab1a634503
[5] https://support.rackspace.com/how-to/using-dig-to-query-nameservers/
[6] https://www.ilanni.com/?p=10624

Tags

Sophie Cao

(she/they, elle/iel, 她/佢)