I was having some issues with what turned out to be a stale DNS entry when it got me thinking - we seldom hardcode DNS entries (e.g. Google’s 8.8.8.8 or OpenDNS’ 208.67.222.222) - meaning that whenever we use DHCP (like with public WiFi), there’s no telling what IPs the given nameserver will resolved queries to. And it’d be fairly trivial to poison those.

With this in mind I wanted to write a simple script that would look at the DNS cache and compare it with the output from say Google’s DNS. We’ll be using Powershell on a Windows 10 box, learning as we go along.

DNS Cache

You can view your Windows DNS cache using either ipconfig /displaydns from the command prompt or you can open Powershell:

PS C:\Users\axiomiety> Get-DnsClientCache

Entry                     RecordName                Record Status    Section TimeTo Data   Data                                                      
                                                    Type                     Live   Length                                                           
-----                     ----------                ------ ------    ------- ------ ------ ----                                                      
www.gstatic.com           www.gstatic.com           A      Success   Answer      38      4 216.58.221.67                                             
hk2sch130021919.wns.wi... HK2SCH130021919.wns.wi... A      Success   Answer    1796      4 111.221.29.155                                            
nexus-websocket-a-3879...                           AAAA   NoRecords                                                                                 
www.youtube.com           www.youtube.com           CNAME  Success   Answer      92      8 youtube-ui.l.google.com                                   
www.youtube.com           youtube-ui.l.google.com   A      Success   Answer      92      4 216.58.221.78                                             
win10.ipv6.microsoft.com  win10.ipv6.microsoft.com  CNAME  Success   Answer      80      8 windows.ipv6.microsoft.com.akadns.net
...

The Entry column is resolved to what is displayed in Data. If RecordType is A it will be an IP address, but if it’s CNAME it will be another domain name that needs to be resolved separately. We can see that by filtering on www.youtube.com:

PS C:\Users\axiomiety> Get-DnsClientCache -Entry www.youtube.com

Entry                     RecordName                Record Status    Section TimeTo Data   Data                                                      
                                                    Type                     Live   Length                                                           
-----                     ----------                ------ ------    ------- ------ ------ ----                                                      
www.youtube.com           www.youtube.com           CNAME  Success   Answer       7      8 youtube-ui.l.google.com                                   
www.youtube.com           youtube-ui.l.google.com   A      Success   Answer       7      4 74.125.68.93                                              
www.youtube.com           youtube-ui.l.google.com   A      Success   Answer       7      4 74.125.68.91                                              
www.youtube.com           youtube-ui.l.google.com   A      Success   Answer       7      4 74.125.68.136                                             
www.youtube.com           youtube-ui.l.google.com   A      Success   Answer       7      4 74.125.68.190       

In a nutshell we’ll want our script to validate each Entry against Data. Before we start though let’s see how many entries I have:

PS C:\Users\axiomiety> Get-DnsClientCache | Measure-Object -Line

Lines Words Characters Property
----- ----- ---------- --------
  123                 

Okay that’s a lot. Now each entry has a TTL (Time To Live), which when it reaches 0 drops out of the cache. But some entries clearly like to stick around (note the one with 1796 seconds to go). We can clear our cache via:

C:\Users\axiomiety>ipconfig /flushdns

Windows IP Configuration

Successfully flushed the DNS Resolver Cache.

And just to check:

PS C:\Users\axiomiety> Get-DnsClientCache | Measure-Object -Line

Lines Words Characters Property
----- ----- ---------- --------
    0     

So we’re starting with a clean slate. It’s worth noting the cache won’t stay empty for long. If you have any kind of background processes that do things like ping for updates etc… this will soon populate back.

Parsing the output

Powershell is displaying the output in tabular form. We don’t need all the columns though - which we can specify by piping the output of Get-DnsClientCache to Format-Table -Property [properties]. To figure outu which Properties are available, we can do:

PS C:\Users\axiomiety> Get-DnsClientCache | Get-Member -MemberType Property


   TypeName: Microsoft.Management.Infrastructure.CimInstance#ROOT/StandardCimv2/MSFT_DNSClientCache

Name           MemberType Definition                   
----           ---------- ----------                   
Caption        Property   string Caption {get;set;}    
Data           Property   string Data {get;} 
...

We’re only really looking for Entry and Data:

PS C:\Users\axiomiety> Get-DnsClientCache | Format-Table -Property Entry,Data

Entry                                Data                              
-----                                ----                              
hk2sch130021919.wns.windows.com      111.221.29.155                    
www.youtube.com                      youtube-ui.l.google.com           
www.youtube.com                      74.125.200.190                    
www.youtube.com                      74.125.200.91     
...

And to loop through each row of data, we leverage ForEach-Object (which is context sensitive - how cool!):

PS C:\Users\axiomiety> Get-DnsClientCache | ForEach-Object {Write-Host $_.Entry, $_.Data}
hk2sch130021919.wns.windows.com 111.221.29.155
i1.ytimg.com ytimg.l.google.com
i1.ytimg.com 216.58.221.78
...

Validating an entry

Given an Entry, we want to validate Data with an independent DNS query. We could use nslookup for that but Powershell has a Resolve-DnsName Cmdlet that will do just fine:


PS C:\Users\axiomiety> Resolve-DnsName www.youtube.com -NoHostsFile -Server 8.8.8.8

Name                           Type   TTL   Section    NameHost                                                                                      
----                           ----   ---   -------    --------                                                                                      
www.youtube.com                CNAME  86287 Answer     youtube-ui.l.google.com                                                                       

Name       : youtube-ui.l.google.com
QueryType  : AAAA
TTL        : 187
Section    : Answer
IP6Address : 2404:6800:4003:c00::be


Name       : youtube-ui.l.google.com
QueryType  : A
TTL        : 299
Section    : Answer
IP4Address : 74.125.68.93
...

Or in condensed form:

PS C:\Users\axiomiety> Resolve-DnsName www.youtube.com -NoHostsFile -Server 8.8.8.8 -Type A | ForEach-Object {Write-Host $_.IP4Address,$_.NameHost}
 youtube-ui.l.google.com
74.125.68.190 
74.125.68.93 
74.125.68.136 
74.125.68.91 

Note the use of -NoHostsFile - we want to bypass anything locally that may be messing with the ‘true’ result.

We the above we should now be in a position to write a function which taks a domain name and expected value as an argument, and checks it against the output from Resolve-DnsName. Let’s get to it.

The validating function

Our function will take 2 arguments - the entry and its data - and a default alternative DNS to validate the result against.

Function ValidateDNSResults
{
    Param($entry, $data, $dns='8.8.8.8')
    $resolve_out = @()
    Resolve-DnsName $entry -NoHostsFile -Server $dns | ForEach-Object {$resolve_out += $_.IP4Address; $resolve_out += $_.NameHost}
    $resolve_out = $resolve_out | ? {$_}  
    If ($resolve_out -notcontains $data)
    {
        $o = $resolve_out -join ','
        Write-Host "Entry mismatch for $entry - did not find $data in $o" -foregroundcolor Red
    }
}

Trying it out:

PS C:\Users\axiomiety> ValidateDNSResults 'www.youtube.com' 'foo'
Entry mismatch for www.youtube.com - did not find foo in youtube-ui.l.google.com,74.125.68.190,74.125.68.91,74.125.68.136,74.125.68.93

PS C:\Users\axiomiety> ValidateDNSResults 'www.youtube.com' 'youtube-ui.l.google.com'

Seems to work alright!

Wiring it up

With the above in hand it’s just a matter of piping the output from Get-DnsClientCache accordingly (you can run it for the whole cache, I just filtered on an entry which showed a mismatch).

PS C:\Users\axiomiety> Get-DnsClientCache -Entry 'securepubads.g.doubleclick.net' | ForEach-Object {ValidateDNSResults $_.Entry $_.Data '192.168.1.1'}

PS C:\Users\axiomiety> Get-DnsClientCache -Entry 'securepubads.g.doubleclick.net' | ForEach-Object {ValidateDNSResults $_.Entry $_.Data '8.8.8.8'}
Entry mismatch for securepubads.g.doubleclick.net - did not find 74.125.68.155 in partnerad.l.doubleclick.net,74.125.200.157,74.125.200.154,74.125.200.156,74.125.200.155
Entry mismatch for securepubads.g.doubleclick.net - did not find 74.125.68.154 in partnerad.l.doubleclick.net,74.125.200.157,74.125.200.156,74.125.200.155,74.125.200.154
Entry mismatch for securepubads.g.doubleclick.net - did not find 74.125.68.156 in partnerad.l.doubleclick.net,74.125.200.155,74.125.200.157,74.125.200.156,74.125.200.154
Entry mismatch for securepubads.g.doubleclick.net - did not find 74.125.68.157 in partnerad.l.doubleclick.net,74.125.200.155,74.125.200.157,74.125.200.156,74.125.200.154

Here we can see that resolving securepubads.g.doubleclick.net using a local DNS (192.168.1.1) returns a different result to Google’s own DNS. Saying this is probably nothing to worry about - 74.125.0.0/16 is owned by Google - so anything after 74.125 hits Google’s own network.

Conclusion

I was actually surprised by the sheer number of different entries. I’m guessing a number of those services are behind some sort of load balancer or using anycast. It’d be great to be able to dig deeper into those and understand why that is the case but I’d need more network-foo than I currently have.

On an unrelated note, Powershell rocks!