From eBower Wiki
Jump to: navigation, search
(Alternate Method)
(What is my DNS?)
Line 127: Line 127:
 
Detecting your public DNS server is a bit difficult in many cases. If you look at your computer configuration often it's your router so you have no idea when it changes. If you use Google DNS you may think it's 2001:4860:4860::8888 or 2001:4860:4860::8844 (if you're still rocking a TRS-80 you may use the old 8.8.8.8 and 8.8.4.4), but this is a common AnyCast address and you've got no idea what datacenter you're going to. So, how does one figure this out?
 
Detecting your public DNS server is a bit difficult in many cases. If you look at your computer configuration often it's your router so you have no idea when it changes. If you use Google DNS you may think it's 2001:4860:4860::8888 or 2001:4860:4860::8844 (if you're still rocking a TRS-80 you may use the old 8.8.8.8 and 8.8.4.4), but this is a common AnyCast address and you've got no idea what datacenter you're going to. So, how does one figure this out?
  
The premise is that you run a limited DNS server on (or close to) your web server. You don't even need to configure it, you just need it to listen to requests for a specific hostname structure. Now you can configure a website to grab an object from that non-existent hostname and check the DNS server logs via an AJAX call to figure out who made the request. You could go the fancy Javascript route, or you could go for simplicity and use just images and iframes. I've found that Chrome will try multiple times to resolve a hostname that doesn't exist, this can provide additional insight into your DNS infrastructure so I return every DNS server I can find.
+
The premise is that you run a limited DNS server on (or close to) your web server. You don't even need to configure it, you just need it to listen to requests for a specific hostname structure. By encoding a "token" (a mostly unique text string) into the hostname I can use the host header to scan the BIND logs for when it was last looked up. The base page is essentially just a redirect to the token-based hostname.
  
 
This is a simple webpage that will return either IPv4 or IPv6 DNS servers:
 
This is a simple webpage that will return either IPv4 or IPv6 DNS servers:
Line 135: Line 135:
 
If you want to limit the response to IPv4 you can use this URL:
 
If you want to limit the response to IPv4 you can use this URL:
 
* [http://ipv4.whatismydns.ebower.com http://ipv4.whatismydns.ebower.com]
 
* [http://ipv4.whatismydns.ebower.com http://ipv4.whatismydns.ebower.com]
 +
* [http://a.whatismydns.ebower.com http://a.whatismydns.ebower.com]
 +
  
 
And to check IPv6 transport, use this one:
 
And to check IPv6 transport, use this one:
 
* [http://ipv6.whatismydns.ebower.com http://ipv6.whatismydns.ebower.com]
 
* [http://ipv6.whatismydns.ebower.com http://ipv6.whatismydns.ebower.com]
 +
* [http://aaaa.whatismydns.ebower.com http://aaaa.whatismydns.ebower.com]
  
An alternative implementation is to make two simple requests in quick succession, first to the non-existent hostname and the other to the website passing the hostname to search for. For example, the following generates a token by mashing together the hostname and the date/time and taking the md5sum of it. Then I do a dig and a curl to get the data:
+
You can script this using the -L option of cURL, alternatively you can try to generate your own token and hit it directly:
 
 
 
<syntaxhighlight lang="bash">
 
<syntaxhighlight lang="bash">
token=$(echo $(hostname)$(date +%s) | md5sum | awk '{print $1}') \
+
curl -L http://whatismydns.ebower.com
&& dig +short $token.token.ds.whatismydns.ebower.com \
+
curl $(echo $(hostname)$(date +%s) | md5sum | awk \'{print $1}\').token.ds.whatismydns.ebower.com
; curl http://ds.whatismydns.ebower.com/?token=$token
 
 
</syntaxhighlight>
 
</syntaxhighlight>
 
 
 
== BIND configuration ==
 
== BIND configuration ==
  

Revision as of 06:13, 8 July 2015

Everyone could use some quick, simple information every so often. There are plenty of options for many of these, but they're surprisingly simple to set up yourself.

What Is My IP?

You can find dozens of these sites, most with a lot of information you don't always care about like a map of where it thinks you are. I created a simple one mimicking whatismyip.akamai.com which only returns your IP address. This list of sites will return your IP address:

These sites will return your IPv4 address, assuming you have one:

And these sites will return your IPv6 address:

That's an awful lot of configuration, it must have taken me days and hundreds of lines of code and configuration to get it done, right?

DNS Configuration

Of course, you'll need to have the right DNS entries in most cases. Here I took each of the hostnames above and added a CNAME record pointing to www.ebower.com which represents the server they all run on. Note that one of them is just a directory off my server, that's potentially an easier way to go.

The IPv4-only sites only have an A record, so you can't reach them over IPv6. Conversely the IPv6-only sites only have a AAAA record so you can't get to them over IPv4. The Dual Stack sites, of course, have both.

Apache Configuration

If you're going the route of dedicated hostnames, you'll need an Apache configuration to support them. I created the following in /etc/apache2/sites-available/whatismyip.conf, then I created a symlink to /etc/apache2/sites-enabled/whatismyip.conf and did a sudo service apache2 reload to enable things.

<VirtualHost *:80>
        ServerAdmin webmaster@ebower.com
        ServerName whatismyip.ebower.com
        ServerAlias *.whatismyip.ebower.com
        DocumentRoot /var/www/whatismyip
        <Directory />
                Options FollowSymLinks
                AllowOverride None
        </Directory>
 
        ErrorLog ${APACHE_LOG_DIR}/error.log
 
        # Possible values include: debug, info, notice, warn, error, crit,
        # alert, emerg.
        LogLevel warn
 
        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

You'll see that the primary service (ServerName) is whatismyip.ebower.com, but anything under whatismyip.ebower.com will work as well thanks to the ServerAlias line. If I created a DNS record for foo.whatismyip.ebower.com it would work with no changes.

Webpage Configuration

The webpage itself is pretty simple. I placed it in /var/www/whatismyip/index.php so if I wanted to I could access it off my main page with the SSL cert.

<?php 
  $myip = $_SERVER['REMOTE_ADDR'];
  if ( $myip == '192.168.1.1' ) {   
    $myip = `curl whatismyip.akamai.com`;
  }
  echo $myip;
?>

This should be even simpler, I could just print $_SERVER['REMOTE_ADDR'] but if I'm coming from my router's IPv4 address I'm coming from my internal network. Rather than printing the RFC1918 address I would rather the behavior be to print my public address. So I just have my server make a call to Akamai's service since we're both behind the same NAT and the address is the same.

For IPv6 this isn't needed, IPv6 just works while IPv4 needs these little kludges.

What is my User Agent String?

The User Agent String is a text field that your browser uses to identify itself. Untouched, it represents the operating system and browser version - but you can easily modify it so it's hardly reliable. Sometimes you may need to know what's being presented by your browser. For example:

Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.104 Safari/537.36

This is much simpler than "whatismyip" because we don't care about the transport type.

DNS Configuration

This is a simple CNAME, whatismyuastring.ebower.com to www.ebower.com.

Apache Configuration

Again, slightly simpler since we don't need the wildcard ServerAlias:

<VirtualHost *:80>
        ServerAdmin webmaster@ebower.com
        ServerName whatismyuastring.ebower.com
 
        DocumentRoot /var/www/whatismyuastring
        <Directory />
                Options FollowSymLinks
                AllowOverride None
        </Directory>
 
        ErrorLog ${APACHE_LOG_DIR}/error.log
 
        # Possible values include: debug, info, notice, warn, error, crit,
        # alert, emerg.
        LogLevel warn
 
        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Webpage Configuration

Simple again, I could even punt on the intermediary variable if I wanted.

<?php
  $myuastring = $_SERVER['HTTP_USER_AGENT'];
  echo $myuastring;
?>

What is my DNS?

Detecting your public DNS server is a bit difficult in many cases. If you look at your computer configuration often it's your router so you have no idea when it changes. If you use Google DNS you may think it's 2001:4860:4860::8888 or 2001:4860:4860::8844 (if you're still rocking a TRS-80 you may use the old 8.8.8.8 and 8.8.4.4), but this is a common AnyCast address and you've got no idea what datacenter you're going to. So, how does one figure this out?

The premise is that you run a limited DNS server on (or close to) your web server. You don't even need to configure it, you just need it to listen to requests for a specific hostname structure. By encoding a "token" (a mostly unique text string) into the hostname I can use the host header to scan the BIND logs for when it was last looked up. The base page is essentially just a redirect to the token-based hostname.

This is a simple webpage that will return either IPv4 or IPv6 DNS servers:

If you want to limit the response to IPv4 you can use this URL:


And to check IPv6 transport, use this one:

You can script this using the -L option of cURL, alternatively you can try to generate your own token and hit it directly:

curl -L http://whatismydns.ebower.com
curl $(echo $(hostname)$(date +%s) | md5sum | awk \'{print $1}\').token.ds.whatismydns.ebower.com

BIND configuration

Under Ubuntu this is fairly easy[1], just run:

sudo apt-get install bind9

There is one change that makes things a bit more secure, BIND defaults to logging to /var/log/syslog - but that's a little scary to parse and display to end users in case there's some odd buffer overflow issue. So let's move it to /var/log/query.log.

First you'll need to edit /etc/bind/named.conf.local and add this to the bottom of the file:

logging {
    channel query.log {      
        file "/var/log/query.log"; 
        severity debug 3; 
    }; 
    category queries { query.log; }; 
};

Next we need to create and change the ownership of the logs:

sudo touch /var/log/query.log
sudo chown bind:bind /var/log/query.log

Now we need to update AppArmor by editing /etc/apparmor.d/usr.sbin.named:

/var/log/query.log w,

And then running these commands:

cat /etc/apparmor.d/usr.sbin.named | sudo apparmor_parser -r
sudo service bind9 restart

Apache Configuration

I created /etc/apache2/sites-available/whatismydns.conf as shown below. Then I created a symlink to it at /etc/apache2/sites-enabled/whatismydns.conf and restarted apache.

<VirtualHost *:80>
        ServerAdmin webmaster@ebower.com
        ServerName whatismydns.ebower.com
        ServerAlias *.whatismydns.ebower.com
        DocumentRoot /var/www/whatismydns
        <Directory />
                Options FollowSymLinks
                AllowOverride None
        </Directory>
 
        ErrorLog ${APACHE_LOG_DIR}/error.log
 
        # Possible values include: debug, info, notice, warn, error, crit,
        # alert, emerg.
        LogLevel warn
 
        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Website Configuration

I used a single index.php file and just changed the behavior based on whether or not we were passed a token:

<?php
  $host = $_SERVER["HTTP_HOST"];
  # The main site is really an alias for the dual-stack site.
  if ($host == 'whatismydns.ebower.com') {
    $host = 'ds.whatismydns.ebower.com';
  }
  # If we were passed a token we should parse the BIND logs and just return 
  # client IP addresses.
  if ( isset($_GET["token"]) ) {
    # I allow you to pass a "show=all" parameter to show all of the DNS 
    # servers, otherwise I just show the last one to hit the server.
    if ( isset($_GET["show"]) ) {
      if ($_GET["show"] == "all") {
        $show = "all";
      }
    }
    $token = $_GET["token"];
    # We need a pause, the DNS lookup and page request are often simultaneous.
    sleep(1);
    # We want to construct the full hostname from the token, this can help
    # prevent trying to do naughty things with the logs.
    $dns_host = $token.".token.".$host;
    # If we don't want to show everything we need to tack on a tail -n1
    if (isset($show)) {
      $tail = '';
    } else {
      $tail = '| tail -n1';
    }
    # Only look at the last 25 entries, find queries for the $dns_host, strip
    # out the client nameserver IP addresses, and potentially choose the last
    # one.
    $dns_server = `tail -n 25 /var/log/query.log | grep query:\ $dns_host | awk '{print $2}' | awk -F\# '{print $1}' $tail`;
    print $dns_server;
  } else {
    # If we're here we can assume you hit whatismydns in a browser. 
    # First generate a token.
    $token = md5($_SERVER["REMOTE_ADDR"].$_SERVER["REMOTE_PORT"].$_SERVER["REQUEST_TIME_FLOAT"]);
    # Now generate the hostname
    $dns_host = $token.".token.".$host;
    # Download a dummy image from this hostname.
    print "<img src='http://$dns_host/nothing.png' style='display:none'>";
    # Open an iframe and pass the token.
    print "<iframe width='100%' height='100%' src='http://$host/?token=$token&show=all' frameborder='0'>";
    # A friendly message to CURL/WGET/etc. users.
    print '\n<noscript>
If you are looking at this using something like CURL, try this:
token=$(echo $(hostname)$(date +%s) | md5sum | awk \'{print $1}\') && dig +short $token.token.ds.whatismydns.ebower.com ; curl http://ds.whatismydns.ebower.com/?token=$token
</noscript>';
  }
?>

DNS Configuration

This is a bit more interesting. We, of course, need entries for the various whatismydns.ebower.com websites. In my case I have the following all CNAMEd to www.ebower.com:

  • whatismydns.ebower.com
  • ds.whatismydns.ebower.com
  • ipv4.whatismydns.ebower.com
  • ipv6.whatismydns.ebower.com

But we also need what are known as NS (NameServer) records. I created domains as follows:

  • token.ds.whatismydns.ebower.com --> whatismyip.ebower.com
  • token.ipv4.whatismydns.ebower.com --> a.whatismyip.ebower.com
  • token.ipv6.whatismydns.ebower.com --> aaaa.whatismyip.ebower.com

All of these represent my webserver. I send anything of the format *.token.ds.whatismydns.ebower.com to my webserver over either IPv4 or IPv6. I send anything to *.token.ipv4.whatismydns.ebower.com to a hostname that will only resolve an IPv4 address, preventing IPv6 nameservers from hitting me. And I send anything to *.token.ipv6.whatismydns.ebower.com to a hostname that will only resolve an IPv6 address, killing the IPv4 transport for these hostnames.

Alternate Method

The downside of this method is that you may have a situation where there are one or more rotating DNS servers. Many corporate environments will round robin DNS servers, sending one request out of location A and the next out of location B which could be very far apart. This causes issues with CDNs in that a user at corporate HQ could get good performance on the first request but the second could be served from across the country or even internationally.

Chrome (at least) has a quirk. If there's an unsuccessful DNS lookup it tries again. And again and again. You can grab all of these if you simply fail to setup BIND correctly.

BIND Configuration

Pretty much the same as above, but you don't need to properly set up a zone. In this case I've created a new zone so I can run both simultaneously.

Apache Configuration

Again you can use the above configuration.

Website Configuration

Here you can see the differences. We can't just open up a website with a token embedded in the hostname because that won't resolve. Instead I create a simple webpage with an image and an iFrame. If I wanted to make something a bit fancier I would use Javascript to do AJAX calls, but I want it simple and as compatible as possible.

Note that you can still use CURL, if a token is passed in the URL I don't generate a webpage but instead just scan the log. This allows you to run a simple script to do a DNS lookup and then a CURL to get the IP list:

token=$(echo $(hostname)$(date +%s) | md5sum | awk \'{print $1}\') \
 && dig +short $token.token.ds.whatismydns.ebower.com \
 ; curl http://ds.whatismydns.ebower.com/multi.php?token=$token

Here's the PHP code which I put into multi.php:

<?php
  $host = $_SERVER["HTTP_HOST"];
  # Rewrite some aliases
  if ($host == 'www.ebower.com') {
    $host = 'ds.whatismydns.ebower.com';
  }
  if ($host == 'whatismydns.ebower.com') {
    $host = 'ds.whatismydns.ebower.com';
  }
  if ($host == 'a.whatismydns.ebower.com') {
    $host = 'ipv4.whatismydns.ebower.com';
  }
  if ($host == 'aaaa.whatismydns.ebower.com') {
    $host = 'ipv6.whatismydns.ebower.com';
  }
 
  # If we were passed a token we should parse the BIND logs and just return 
  # client IP addresses.
  if ( isset($_GET["token"]) ) {
    $token = $_GET["token"];
    # We need a pause, the DNS lookup and page request are often simultaneous.
    sleep(2);
    # We want to construct the full hostname from the token, this can help
    # prevent trying to do naughty things with the logs.
    $dns_host = $token.".token.multi.".$host;
    # Only look at the last 25 entries, find queries for the $dns_host, strip
    # out the client nameserver IP addresses, and potentially choose the last
    # one.
    $dns_server = `tail -n 25 /var/log/query.log | grep query:\ $dns_host | awk '{print $2}' | awk -F\# '{print $1}'`;
    print $dns_server;
  } else {
    # If we're here we can assume you hit whatismydns in a browser. 
    # First generate a token.
    $token = md5($_SERVER["REMOTE_ADDR"].$_SERVER["REMOTE_PORT"].$_SERVER["REQUEST_TIME_FLOAT"]);
    # Now generate the hostname
    $dns_host = $token.".token.".$host;
    # Download a dummy image from this hostname.
    print "<img src='http://$dns_host/nothing.png' style='display:none'>";
    # Open an iframe and pass the token.
    print "<iframe width='100%' height='100%' src='http://$host/?token=$token&show=all' frameborder='0'>";
    # A friendly message to CURL/WGET/etc. users.
    print '\n<noscript>
If you are looking at this using something like CURL, try this:
token=$(echo $(hostname)$(date +%s) | md5sum | awk \'{print $1}\') && dig +short $token.token.ds.whatismydns.ebower.com ; curl http://ds.whatismydns.ebower.com/multi.php?token=$token
</noscript>';
  }
?>

DNS Configuration

Pretty much the same as above, I use the same CNAMEs but I add a .multi. to the NS records.

  • token.multi.ds.whatismydns.ebower.com --> whatismyip.ebower.com
  • token.multi.ipv4.whatismydns.ebower.com --> a.whatismyip.ebower.com
  • token.multi.ipv6.whatismydns.ebower.com --> aaaa.whatismyip.ebower.com

References

  1. Ubuntu Server Guide, Troubleshooting DNS