Update

It is not recommend of using recursive DNS server for public service. Dnsmasq, however, only support recursive. Using dnsmasq in public can make your server vulnerable to DDOS attack.

Why Dnsmasq

After adding too many DNS records into my cloudflare account, I find it necessary to find a way to reduce the record. However, using wildcard is only avaliable for pro plan user. So I decided to deploy my own dns server.

Then I choosed 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, using docker-compose up to start the server. Which may get 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 stoped the server and retried.

sudo systemctl stop systemd-resolved

This time, dnsmasq was successfully started

Systemd-resolved

However, stop 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 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 systemd create 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 request are successfully.

Adding NS record to DNS provider

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

Firstly, adding a A record pointed to the server.

Second, adding a 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 maching.

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