Magan

A small, secure dns server written in C. It retrieves the answers over HTTPS so your otherwise clear-text DNS traffic becomes encrypted.

Uses

- As a DOH stand-alone DNS server.
- As a DOH source for pi-hole, dnsmasq etc.
- As a DOH name server for openvpn.

Features

1. Easy to setup and use - If I don't like it, you won't either.
2. Scalable - On a Raspberry Pi, I saw it serve an impressive amount of concurrent requests as a non-root user out-of-the-box, until pthreads started complaining a little. Of course, one can extend this limit further with buffed up sysctl and ulimit settings.

Basic Usage

$ ./magan --help
Usage: ./magan [options]
  -h  --help         print this usage and exit
  -p  --port         alternate port to listen
  -d  --debug        show debug info
  -v  --version      print version information and exit

to listen on non-privileged port

You don't need root privileges if you are using port > 1024:

$ ./magan-armv7l -p 3131
Thu Apr  4 19:34:48 2019 Magan[26795]: Magan/1.2
Thu Apr  4 19:34:48 2019 Magan[26795]: Listening on port: 3131
Thu Apr  4 19:34:48 2019 Magan[26795]: Ready
..

to listen on privileged port

you'd need root privs if you want to listen in on port number less than 1024

$ sudo ./magan-armv7l 
Thu Apr  4 19:35:20 2019 Magan[26823]: Magan/1.2
Thu Apr  4 19:35:20 2019 Magan[26823]: Listening on port: 53
Thu Apr  4 19:35:20 2019 Magan[26823]: Ready
..

Or,

$ sudo ./magan-armv7l -p 53
Thu Apr  4 19:35:39 2019 Magan[26835]: Magan/1.2
Thu Apr  4 19:35:39 2019 Magan[26835]: Listening on port: 53
Thu Apr  4 19:35:39 2019 Magan[26835]: Ready
..

startup

Startup can be as simple as adding a line to your /etc/crontab:

*/5 * * * *    someuser     /usr/local/bin/magan -p 1039 1>/dev/null 2>/dev/null

If you are running as root, then:

*/5 * * * *    root     /usr/local/bin/magan -p 53 1>/dev/null 2>/dev/null

Port requirements

On your host,

  1. allow inbound UDP,TCP traffic on the port you're listening on, obviously.

  2. allow access to 8.8.8.8 udp port 53 so it can do lookup during startup.

  3. allow TCP port 443 access to dns.google.com so it can retrieve the answers.

Dependencies

We need libcurl4-openssl-dev and libjson-c3, usually installed on Debian thusly:

$ sudo apt install libjson-c3 libjson-c-dev libcurl4-openssl-dev

Download

Only Linux is supported at this time, pre-built binaries for x86_64 and armv7l can be downloaded from here.

To setup in folder ~/magan:

$ mkdir ~/magan 
$ cd ~/magan 
$ wget https://evuraan.info/evuraan/stuff/magan/magan-$(arch) -O magan 
$ chmod 755 magan 
$ ~/magan/magan -p 3399 
..

In action

Testing against Magan listening on port 1039:
$ dig @192.168.1.10 -p 1039 txt cnn.com
;; Truncated, retrying in TCP mode.

; <<>> DiG 9.10.3-P4-Ubuntu <<>> @192.168.1.10 -p 1039 txt cnn.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51737
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 38, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;cnn.com.			IN	TXT

;; ANSWER SECTION:
cnn.com.		219	IN	TXT	"\"126953328-4422040\"" ""
cnn.com.		219	IN	TXT	"\"133461244-4422058\"" ""
cnn.com.		219	IN	TXT	"\"178953534-4422001\"" ""
cnn.com.		219	IN	TXT	"\"186844776-4422028\"" ""
cnn.com.		219	IN	TXT	"\"228426766-4422034\"" ""
cnn.com.		219	IN	TXT	"\"267933795-4422004\"" ""
cnn.com.		219	IN	TXT	"\"287893558-4422013\"" ""
cnn.com.		219	IN	TXT	"\"294913881-4422049\"" ""
cnn.com.		219	IN	TXT	"\"299762315-4422055\"" ""
cnn.com.		219	IN	TXT	"\"2baPGrmeo+RwsWdIdq/gIVSEWNb4tC9mLGQu0j4l/mduqhm06T+V9vNLXsauLyH9FwMZJSRHvj/YHGKOVWRylw==\"" ""
cnn.com.		219	IN	TXT	"\"321159687-4422031\"" ""
cnn.com.		219	IN	TXT	"\"349997471-4422043\"" ""
cnn.com.		219	IN	TXT	"\"353665828-4422052\"" ""
cnn.com.		219	IN	TXT	"\"528183251-4422019\"" ""
cnn.com.		219	IN	TXT	"\"553992719-4400647\"" ""
cnn.com.		219	IN	TXT	"\"598362927-4422061\"" ""
cnn.com.		219	IN	TXT	"\"667921863-4422007\"" ""
cnn.com.		219	IN	TXT	"\"688162515-4422037\"" ""
cnn.com.		219	IN	TXT	"\"691244352-4422022\"" ""
cnn.com.		219	IN	TXT	"\"714321471-4421998\"" ""
cnn.com.		219	IN	TXT	"\"754516718-4422064\"" ""
cnn.com.		219	IN	TXT	"\"755973593-4422016\"" ""
cnn.com.		219	IN	TXT	"\"764482256-4422025\"" ""
cnn.com.		219	IN	TXT	"\"782989862-4417942\"" ""
cnn.com.		219	IN	TXT	"\"826218936-4422046\"" ""
cnn.com.		219	IN	TXT	"\"882269757-4422010\"" ""
cnn.com.		219	IN	TXT	"\"MS=ms66433104\"" ""
cnn.com.		219	IN	TXT	"\"_globalsign-domain-verification=5ckEJ4VIhQ6weCdCfmfzQPVP6ED1LtCX9jw1OKX5Mv\"" ""
cnn.com.		219	IN	TXT	"\"_globalsign-domain-verification=MK_ZKmss4D_DdzGOsssHxxBOK6hJc6LGycFvNOESdZ\"" ""
cnn.com.		219	IN	TXT	"\"_globalsign-domain-verification=yTw3T3KnyIyTB1xG2GvVhl1zWJlFp-WqmNskdVI_65\"" ""
cnn.com.		219	IN	TXT	"\"adobe-idp-site-verification=279ead95-3581-42b7-82f4-73c97f8cebfa\"" ""
cnn.com.		219	IN	TXT	"\"d1xTs9+kADZZSz3bPphLpkMXXxBGjqn5vsQHhi2M6lo0r8AdIbm6j8LfQXPujsywVgeGSP+AXWX0vO9Iep5cUg==\"" ""
cnn.com.		219	IN	TXT	"\"facebook-domain-verification=xszi21kow2trmw3xt3ph6s631zyu3i\"" ""
cnn.com.		219	IN	TXT	"\"globalsign-domain-verification=-Q7umwx2mj164XwLa0PsoUaWe2HBhta50GjggsT98f\"" ""
cnn.com.		219	IN	TXT	"\"globalsign-domain-verification=2lI5pahhCu_jg_2RC5GEdolQmAa4K7rhP7_OA-lZBK\"" ""
cnn.com.		219	IN	TXT	"\"google-site-verification=_QivaXNjhXy-V1y_YqrycXdAWZi2mVrcwbXerX6THeY\"" ""
cnn.com.		219	IN	TXT	"\"ms=ms97284866\"" ""
cnn.com.		219	IN	TXT	"\"v=spf1 include:cnn" "com" "_nspf" "vali" "email include:%{i}" "_ip" "%{h}" "_ehlo" "%{d}" "_spf" "vali" "email ~all\"" ""

;; Query time: 29 msec
;; SERVER: 192.168.1.10#1039(192.168.1.10)
;; WHEN: Thu Apr 04 22:51:51 PDT 2019
;; MSG SIZE  rcvd: 2178

Go big or..

Here's how you can stress test and make your pick. If your current DOH setup is listening on port 1039, send it some love thusly, and see how soon it conks out:
$ for i in `seq 1 900`; do   echo "Trying $i"; dig @192.168.1.10 -p 1039  $RANDOM-blog.blogspot.com  &  echo "Done with $i"; done 
or, if 192.168.1.10 were listening on port 53:
$ for i in `seq 1 700`; do   echo "Trying $i"; host  $RANDOM-blog.blogspot.com 192.168.1.10 &  echo "Done with $i"; done  
Look for ;; connection timed out; no servers could be reached messages.

The point here is, send a large number of concurrent DNS requests (not sequential) to see who goes tits up first.

Security and Privacy

Question: Is there a nefarious side to this free-ware?

Short answer: No.

Long Answer: No. There are no hidden shenanigans, period. Use it if you like. Don't use it if you don't like it. I am not releasing the Source Code.

History

It all started with Pi-hole - I started using Pi-hole after a friend recommended it. The DOH piece I had setup at that time soon started to show its weakness - the trouble manifested as random DNS errors. Imagine explaining intermittent DNS failures to children whose Netflix shows have stopped playing all of a sudden! I started this project to be used with my pihole and openvpn setup. I first wrote a Secure DNS server in Python3 (Scapy), and later decided to give C a try. This was a great learning experience, and RFC1035 eventually started to make sense!

At this time, I am pleased with the way it works, and now would like to give it away in hopes that others find it useful.

How Magan works

Suppose your incoming query is looking for the "A" record for www.cnn.com. Magan processes your incoming query and replies with DNS records based on the response we retrieve from "https://dns.google.com/".

During the first few queries (five, currently), it sets up re-usable sockets to dns.google.com:443. Subsequent answers from Magan thus are a lot faster, because we are able to re-use these sockets. (Although this also depends on network conditions between you and dns.google.com.)

After the first 5 queries, this is how the socket structure looks like:

$ ./magan-x86_64 -p 1039 & disown
../snip/..
$ netstat -pantu |grep magan
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 0.0.0.0:1039            0.0.0.0:*               LISTEN      730/magan  		// <- tcp listening on port 1039         
tcp        0      0 192.168.1.6:39846       74.125.21.100:443       ESTABLISHED 730/magan           	// <- reusable socket to dns.google.com
tcp        0      0 192.168.1.6:40614       74.125.21.100:443       ESTABLISHED 730/magan           	// <- reusable socket to dns.google.com
tcp        0      0 192.168.1.6:36654       74.125.21.100:443       ESTABLISHED 730/magan           	// <- reusable socket to dns.google.com
tcp        0      0 192.168.1.6:39832       74.125.21.100:443       ESTABLISHED 730/magan           	// <- reusable socket to dns.google.com
tcp        0      0 192.168.1.6:38228       74.125.21.100:443       ESTABLISHED 730/magan           	// <- reusable socket to dns.google.com
udp        0      0 0.0.0.0:1039            0.0.0.0:*                           730/magan           	// <- udp listening on port 1039
udp        0      0 0.0.0.0:35033           0.0.0.0:*                           730/magan      		// <- see notes below
As said earlier when Megan starts, it does one dns lookup to 8.8.8.8 to determine the address for dns.google.com. In this example, 35033 is the originating udp port of that request. Note, this will be a random port chosen by the kernel. The address we find from this lookup is important, as it is used to connect the 5 re-usable sockets it then goes onto establish.

74.125.21.100 was the address we got from the initial lookup, and all the 5 sockets are set to this destination.

This will be the socket "structure" of Magan when it runs, except you'd also have your local clients connecting to ports 1039.

Bugs/Issues

If you encounter any, please do tell!

License

Copyright (c) 2019, evuraan. All rights reserved.

Redistribution and use in binary form without modification is permitted provided that the following conditions are met:

Redistributions in binary form must reproduce the copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

Neither the name of the author nor the names of other contributors if any, may be used to endorse or promote products derived from this software without specific prior written permission

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.