Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#3.5

DNS Server is the best tool in the box

4.95/5 (20 votes)
30 Apr 2015CPOL14 min read 33.6K   3.5K  
Your first line of defence agsint viruses and spyware

Introduction

If like me you now have ten or more computers hooked up to your wifi network and would like to retain some control over what data is transmitted over the internet then you might like to consider hosting your own DNS-Server on a dedicated machine and sharing the server across the Local Area Network (LAN).

DNS request from a web-browser use UDP packets on port 53 to communicate across the internet with servers to resolve domain names to an Ipv4 or IPv6 address and the returned IP address is then used to directly communicate with a web-site usually using TCP packets over HTTP on port 80 or 443 for HTTPS.

Maintaining and running your own DNS server has many advantages with a one drawback that I will list below.

Spyware / Virus protection

Blocking all traffic in your routers firewall from going out on UDP port 53 apart from the computer that is hosting a local DNS-Server is a fantastic way to kill 95% of spyware dead because no IP means no calling home without asking the DNS-Server first. In practise it should be 100% dead but such is the need to call home that Microsoft and others will often circumvents the need for a DNS-Server by hard coding IP address into the code.

Most routers will be provided with a pair of DNS-Servers addresses by their ISP and the router will pass the addresses of these servers down to each device that is connected to the routers LAN which on face value would seem beneficial because your ISP likes to keep upstream costs down by caching data from sites like eBay and then sending you a cached copy of the data at the highest possible speed.

I have no trouble with this business arrangement but you should be warned that ISP's can and do sell marketing data to 3rd parties and that you need to be careful if you are using a secure Tor Network because some IP-Addreses used for caching as unique to your ISP and country.

DNS Hijacking by ISP's

Where I do have trouble is when ISPs like mine start to hijack DNS-Lookups when customers have decided to opt out and use a 3rd party provider such as OpenDNS or Google on 8.8.8.8  because ISPs are acting just like hackers and the only way to stop this practice is to switch over to DNS servers using HTTPS on port 443 or to use a VPN connected to someone like Cyber-Ghost.

Trouble is most network devices will only work with DNS using UDP port 53 so you will need to host your own DNS-Server to service request and connect the server to a upstream DNS-Server on port 443 to stop these hijacks.

The best way to know if anyone is hijacking your DNS-Lookups is to link the DNS-Server to a Whois table (Whois is a big mess, took me months to collect, compress and analyze 4.2 billion IP's) but you can download the XML data in the Whois.zip file at the top of the page and then use the code below to get you started if you would like to add "Whois" to your projects.

C#
public static long IPtoLonger(string IP)
      {//The XML table uses longs so we need to convert Ipv4 to longs
          string[] Data = IP.ChopOffAfter(":").Split('.');
          if (Data.Length != 4 || IP.EndsWith(".")) return 0;
          long w = long.Parse(Data[0]);
          long x = long.Parse(Data[1]);
          long y = long.Parse(Data[2]);
          long z = long.Parse(Data[3]);
          return 16777216 * w + 65536 * x + 256 * y + z;
      }
public static string Long2IP(long Number)
      {//Not used here but this is how to convert longs back to an IPv4address
          long w = (Number / 16777216) % 256;
          long x = (Number / 65536) % 256;
          long y = (Number / 256) % 256;
          long z = (Number) % 256;
          return "" + w + "." + x + "." + y + "." + z;
      }

DataTable DT=new DataTable("Data");
DT.ReadXml("c:\\Whois.xml");
long IPNum = IPtoLonger("8.8.8.8");
DataRow[] Rows = DT.Select("F<=" + IPNum + " AND T>=" + IPNum);//F=From IP, T=To IP
if (Rows.Length > 0)
   return Rows[0]["W"].tostring();

Reverse lookups

Most people tend to think that each web-site has a dedicated IP address and each IP address is only connected to just one site but this is far from the case and under the cover a request to a DNS-Server will return several Ipv4 address and one or two Ipv6 addresses.

A reverse lookup is the process of sending a DNS-Server an IP address and asking for a domain name for the IP back but these results often will not match up and "www.google.com" might return "1.2.3.4" but the reverse lookup for "1.2.3.4" will return dfw06s32-in-f16.1e100.net and if your ISP is hijacking the domain looup then they will often block reverse lookup on the address. (Maybe neede to fool SSL to accept fake certificates, not sure yet)

Sometimes the address for a domain name will be changed based on the country the request is coming from to help load balance the servers so Google.com for someone in the USA might be returned as "1.2.3.4" but a lookup from India might always return "4.3.2.1" however browser and operating system often store the old IP Address so using a VPN later that connects to Google via India using "1.2.3.4" as the cached address will give the game away.

Security & Performance

Any decent DNS-Server worth its salt will have its own built in DNS cache and traffic protection list (TPL) much like the excellent "Ghosty" Firefox plugin that is used across the world by millions of users and this alone will allow internet traffic to be cut by half and so doubles the speed of browsing.

DNS-Server only work at the domain name lever and never get to see the Url or even know if the lookup request will be using HTTP/SSL on port 443 or even port 123 in a UDP request so any DNS-Server with an attached TPL could simply block "www.porno.com" for the children's machines but this is no use at all when trying to block "www.google.com/q=Big%20Girls" which could be blocked using a proxy-server but this only works for HTTP nd not HTTPS. (Same goes for firewall keyword filtering because they cannot see the URL in SSL requests)

The TPL used by this DNS-Server is shared with a proxy-server that shares a data file but the TPL has been extended to include machine specific rules since it is shared across the network plus the inclusion of redirecting some requests to a proxy server and allowing for a "Protective" mode.

C++
//##################### Ghostry TPL #####################
- -clicktale.          //Block if bad keyword found in the Url
- -comscore.           
-d botsvisit.com       //Block the domain name
-d brandaffinity.net
-d brat-online.ro
//############### Extended Ghostry TPL #################
-d mypornsite.com (KidsPC) //Block the kids seeing porn
+d youtube.com             //Allow even if Url has a bad keyword for everyone 
+d TimeForBed.com (KidsPC) //Allow for kids even after bed time, see time schedules later
-r .google.com             //Redirect to a local proxy server to allow or block the request
-p www.microsoft.com       //Protected so the proxy server will allow after faking parts of the request

The "Protective" mode is used as a flag for the proxy-server to tweak the HTTP Request send to any protected sites and may fake the user-agent or country code or even to remove cookies from requests and then use the Tor Network to relay the request.

DNS-Server in action

Shown below is the DNS-Server in action and as you can see the server can block/protect/allow or redirect DNS-Lookups by right clicking on a domain name and the server also lists the machine name of the calling device on the LAN.
Green=Allow  Red=Blocked  Purple = Protected  Brown=Redirect to proxy server 

dns-server

Please note that the yellowish area towards the bottom is for DNS requests that come from a firewalls sys-logs server that uses TCP and not UDP to communicate with the DNS-Server because we don't want to block these types of requests or return "127.0.0.1" in the results to the sys-logs server and this setup also allows the server to returns a country-code and other information in the bespoke reply.

Part of the formal specification for DNS is that TCP should be implemented for large reply packets but the TCP mentioned above just uses an web-type HTTP Request to communicate with the server.

The server can also resolve the windows process ID to a process name for requests on the local machine which helps to track down any virus that is connecting to the network but it was not turned on in the above picture and I will cover how this was achieved in another posting but if you are after the code then download the VS2010 C# project from the top of the page and then view the "ProcEngine" Classes.

Time schedules

No decent enterprise wide DNS-Server would be complete without time schedules to make sure they never block the bosses requests but are able to stop the kids from watching Youtube all night long so here we have time schedules that can be configured to block everything at night and then force certain machines to use the TPL.

The time schedules also double up to resolve IP-Addresses to machine names which is important because you would not want to ask Google DNS server to resolve a local address like "192.168.1.10" and all kinds of strange things starts to happen with windows if the DNS-Server returns empty results for local names that then get pushed around the LAN to resolve

dns-server

Care also needs to be take not to forward broadcast or multicast address to upstream servers and in a multithread DNS-Server care also need to be taken against flooding from browser who will repeat the same request ten times a second in some cases before the upstream server has even had a chance to reply to the first request.

The code used for time schedules is in a class named "TimeSchedules" and just wraps a XML Datatable if anyone would like to use the code on its own. 

Making a DNS-Request to a server

Shown below is a cut down version of the code for building up a request to send to the upstream server and yes I know you can send ten types of requests and build up a huge class library for adding questions but this works just fine for Ipv4 without getting over technical.

C++
//First we need to get byte[]data to send to the upstream server
private static byte[] DnsBytesLookup(string DomainName)
       {//Builds up a request for a DNS-Lookup
           DomainName = DomainName.ToLower();
           byte[] Head = SeedTheHead(DateTime.Now.Second);
           byte[] Footer = new byte[5] { 0, 0, 1, 0, 1 };
           MemoryStream MS = new MemoryStream();
           MS.Write(Head, 0, Head.Length);
           byte[] Domain = DomainNameBytes(DomainName);
           MS.Write(Domain, 0, Domain.Length);
           MS.Write(Footer, 0, Footer.Length);
           return MS.ToArray();
       }

private static int DnsSeed = 4;//We need to send a seed number with each DNS request
private static byte[] SeedTheHead(int Seed)
       {//Returns the seeded head data for both types of DNS-Requests 
           DnsSeed++;
           if (DnsSeed > 255) DnsSeed = 1;
           byte[] Head = new byte[12] { 77, 77, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0 };
           Head[0] = (byte)DnsSeed;
           Head[1] = (byte)Seed;
           return Head;
       }

//Cut down a bit but i think this should about work
byte []Data=DnsBytesLookup("www.bing.com");//Get the data to send
IPEndPoint IPE = new IPEndPoint(IPAddress.Parse("8.8.8.8"), 53);//Our DNS-Server
Socket Soc = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
Soc.ReceiveTimeout = 15000;
Soc.SendTimeout = 15000;
Soc.Connect(IPE);
Soc.Send(this.Request, SocketFlags.None);//Send our request
Thread.Sleep(200);//It's not going to come back much faster than this so take a break
byte[] buffer = new byte[2048];
int Size = Soc.Receive(buffer);
byte[] Reply = new byte[Size];//We will come back to our reply in a bit
Soc.Shutdown(SocketShutdown.Both);
Soc.Close();

//############### For a reverse lookup use the code below ###############
private static byte[] DnsBytesReverseLookup(string IP)
       {//Builds up a request for a Reverse DNS-Lookup
           byte[] Head = SeedTheHead(DateTime.Now.Minute);
           byte[] Footer = new byte[18] { 7, 105, 110, 45, 97, 100, 100, 114, 4, 97, 114, 112, 97, 0, 0, 12, 0, 1 };//in-addr.arpa
           string[] IPDigits = IP.Split('.');
           MemoryStream MS = new MemoryStream();
           MS.Write(Head, 0, Head.Length);
           byte[] IP0 = ByteString(IPDigits[0]); byte[] IP1 = ByteString(IPDigits[1]); byte[] IP2 = ByteString(IPDigits[2]); byte[] IP3 = ByteString(IPDigits[3]);
           MS.Write(IP3, 0, IP3.Length); MS.Write(IP2, 0, IP2.Length); MS.Write(IP1, 0, IP1.Length); MS.Write(IP0, 0, IP0.Length);
           MS.Write(Footer, 0, Footer.Length);
           return MS.ToArray();
       }

A request for a domain name to an upstream server might return several IPv4-Address so we use the first one that is returned from this function below but then increment "StartAt" to get the rest of the addresses that might also be returned.

C++
public static string GetIPFromReply(byte[] Data,ref int StartAt)
        {//External function calls this so i kept it public
            if (Data.Length < 15) return "";
            for (int f = StartAt; f <= Data.Length - 7; f++)
            {//We look for the first IP-Address and use that one
                if (Data[f] == 0 && Data[f + 1] == 4 && Data[f + 6] == 192)//We are looking for 0,4,IP1,IP2,IP3,IP4,192
                {
                    StartAt = f + 5;
                    return Data[f + 2] + "." + Data[f + 3] + "." + Data[f + 4] + "." + Data[f + 5];
                }
            }
            //############ Nope looks like we need to get the last-ip #############
            StartAt = 0;
            if (Data[Data.Length - 6] != 0 && Data[Data.Length - 5] != 4) return "";//The end of this packet does not contain an IP-Address
            return Data[Data.Length - 4] + "." + Data[Data.Length - 3] + "." + Data[Data.Length - 2] + "." + Data[Data.Length - 1];
        }

Using our Reply[] Data from the code two section up we use the above function like this to read our IPv4 Address from the Reply[] byte data.

C++
int StartAt = Request.Length;//Start of the reply contains our request data, dont't read it
string IP = GetIPFromReply(this.Reply, ref StartAt);//We will return first one to the client process
string IPv4s = IP;//IPV4s will contain a full list of Ipv4 addresses that are returned
int Count = 0;
while (StartAt > 0 && Count<10)//I don't want more than ten or to get into a endless loop
   {//Keep reading until we run out of IP-Addresses
   Count++;
   string NextIP = GetIPFromReply(Reply, ref StartAt);
   if (NextIP.Length > 4 && this.IPv4s.IndexOf(NextIP) == -1)
         IPv4s += "," + NextIP;
   }

Reading the reply for a reverse lookup is a little more complicated and you are not always guaranteed to get one if your ISP is anything like mine but for Ipv4 this is all you really need or see DNSClient.cs for the rest of the code.

But what about Ipv6 because we ran out of Ipv4 !

Ipv4 uses 32 bits or 4 bytes to give 256 X 256 X 256 X 256 = 4,294,967,296 or about 4.3 billion possible addreses with about half a billion being reserved by IANA

Ipv6 uses 96 bits or 12 bytes to give 65536 X 65536 X 65536 X 65536 X 65536 X 65536 = (Next line) 

79,228,162,514,264,337,593,543,950,336 or 18,446,744,073,709,551,616 times more IP addreses than provided by Ipv4 if my sums are correct.

Clearly coporations have big plans for the future and the day will come in the not too distant future I thinks where milk bottle in the fridge will ring up the local store to reorder themselves as they become empty but before this can happen all the 6 byte MAC addresses in network cards will need to be upgraded to accommodate the new Ipv6 addresses so I think Ipv4 will be here for quite some time yet and that Ipv6 is more tied in to RFID tracking than internet connections.

I have turned Ipv6 off on my network due to the security risk of Teredo tunnels that are used to connect Ipv4 networks to Ipv6 networks on networks that only support Ipv4 (which is most networks) because this type of VPN can bypass firewall rules ...................

Sorry but the DNS-Server like my ISP does not support Ipv6 

Other consideration 

This DNS-Server was designed from the onset to work hand in hand with a proxy server (second best tool in the toolbox) and shares the same TPL with the server which has the ability to hijack all apiscripst.XXX.com DNS request from the LAN to then point them towards the proxy server that then has the opportunity to examine the full Url that is send later by web-browsers to decide if the request should be allowed or blocked.

If the Proxy-Server allows the request in the case of "apiscripst.XXX.com/JQuerry.js" then the DNS-Server is smart enough to provide the correct IP-Address back to the proxy server instead of returning the hijacked address of the proxy.  This little trick works fine for HTTP traffic but not HTTPS unless the proxy server is acting as a man-in-the-middle and issuing fake CA-Certificates. (See Later projects where I will show you how this is done)

Maybe now is a good time to ask yourself how come ISP's are able to hijack DNS requests that later point towards a HTTPS Site without the browser popping up a warning message. (I think this is why the ISP's block reverse lookup on hijacked domains, still looking in to this)

Another nice to have is the ability to respond to requests such as MyProxyIP.Home (Could be a machine with two network cards) or MyPublicIP.Home and to filter out fake ten digit requests that Firefox sends out (Stop clogging up my network) which have all be catered for in the DNS-Server.

Less obvious is the need to flag certain domain names not to be cached by the DNS-Server or the ability to force  certain domain names not to load balance because the IP-Range is blocked in the firewall (3rd Tool in my toolbox) and to always use the same single unblocked IP address.

What I have discovered over the years is that nine times out of ten HTTPS is being used not to protect your privacy or bank details in any way since most times SSL is used even without anyone being logged in but instead HTTPS is used to hide spyware scripts being downloaded and data then being streamed back to the servers from devices like smart TVs which are the worst in the world. The worse of the worse are devices (won't mention any names here) that won't even allow the owner to select a upstream proxy server !

Well I happen to have such a device and found that it still works just fine by selectively hijacking the DNS-requests yourself and then using the proxy server to tweak the serial number it uploads to servers all over the world as soon as I switch the TV on and that's without using any of the "Smart" functions.

If corporations are going to these lengths to hide what they are doing then hopefully you will understand why I have gone to these lengths to stop them.

Points of Interest

Surprisingly I ran into a bit of trouble with threading on this project when the network became disconnected which resulted in the thread count for the program going sky high and eventually I managed to track this down to the bit of code that I have skinned out below.

Here the code uses async call-backs to make a request to upstream DNS-Servers and the thread manager I use is similar to the System.Threading.Task in .Net 4 and just calls abort() on threads that run for too long.

C++
public bool Running=false;
public Socket Soc=null;
public Thread TH=null;
public void Stop()
{//Called on the main process thread
  Running=false;
  Thread.Sleep(200);//See if the thread will die on its own
  if (TH!=null && TH.State==ThreadState.Running){ShutMeUp(); TH.abort();
}

public void Abort()
{//Called by the thread manager is the threads runs too long
   if(!Running) return;//Already dead
   if (TH!=null) TH.abort();//We must never do this but what else can you do?
   ShutMeUP();
}

private void ShutMeUp()
{//Needs try; catch and should look at the socket state
  Soc.Shutdown(SocketShutdown.Both);
  Soc.Close();
}

public void Start()//Stage One
{
    Running=true;
    TH=MyMananger.NewThread(this.abort,this.run,20);//Run for 20 seconds then call this.Abort()
}                                                   //but only if the thread is still running

public void Run()//Stage Two
{
  IPEndPoint IPE = new IPEndPoint(IPAddress.Parse(ServerIP), ServerPort);//Our DNS-Server address
  this.Soc = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  this.Soc.Timeout =12000;
  Helper.BindToLocalPort(null, Soc, Helper.MyIP4, 0);
  this.Soc.BeginConnect(IPE, new AsyncCallback(BeginConnect), null);
}

private void BeginConnect(IAsyncResult ar)//Stage Threee
{
  if (!Runing) {ShutMeUp();Return}
  Soc.EndConnect(ar);
  Soc.Send(Request, SocketFlags.None);
  Soc.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(BeginReceive), null);
}

private void BeginReceive(IAsyncResult ar)//Stage Four
{
   if (!Runing) {ShutMeUp();Return}
   int Size = Soc.EndReceive(ar, out SocError);
   //Process the data 
   ShutMeUp();
   Running=false;/JD no trouble, thread dies a normal death
}

Now I know abort() should only be used as a last resort but joining a thread that's hung up on a sockets network call does not result in the thread being killed so what else can we do ?

I did a bit more research on this subject and the recommendation seemed to be that we should be using new processes and not threads for this type of transaction but I don't want 20 processes all running at the same time to service outstanding request and the performance costs seems too high a price to pay in this case.

Maybe I could breakout to a second process that runs threads to deal with outstanding requests and then kill the process if the thread count gets too high but this makes the program over complicated in my opinion.

I moved away from using async call-backs for now and just used Soc.Connect(), Soc.Send() , Soc.Read() that runs on a thread and the trouble seemed to have gone away.

Can anyone see anything wrong with the code or can anyone make a better suggestion that works on the 3.5 Framework

Using the code

The executable "DnsApp.exe" and complete VS2010 C# Source code for the project is include in the download at the top of the page but due to the size of the "Whois.zip" database file you will need to download this separately, unzip and copy the XML file to the root of the project "C:\DnsServer" and then simply run the DnsApp.exe shortcut and get started by pressing the "Start" button.

Configuration files to see are  "NoCache.txt"  , " Property.txt" , " ForcedIPAddresses.txt", " DnsAppTimeSchedules.xml" and "trackinglist.txt" for the Tracking protection List.

I have included sample data for Time schedules but you can turn these off from the settings menu to get yourself started and please keep in mind that much of the code in the "Common" project is not used by this program and is a shared library that I use in many of my other project. See ReadMe.txt for more details

Credits to fanboyadblock@googlegroups.com for the original tracking protection list data 

Enjoy , Dr Gadgit

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)