From eBower Wiki
Jump to: navigation, search
(What is my DNS?)
(Website Configuration)
Line 377: Line 377:
 
     # out the client nameserver IP addresses, and potentially choose the last
 
     # out the client nameserver IP addresses, and potentially choose the last
 
     # one.
 
     # one.
     $dns_server = `tail -n 25 /var/log/query.log | grep query:\ $dns_host | awk '{print $2}' | awk -F\# '{print $1}'`;
+
     $dns_server = `tail -n 25 /var/log/query.log | grep query:\ $dns_host | awk '{print $4}' | awk -F\# '{print $1}'`;
 
     print $dns_server;
 
     print $dns_server;
 
   } else {
 
   } else {

Revision as of 09:06, 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

Logging

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

Zone Configuration

Now we need to tell BIND how to resolve the appropriate token zones. I've created three zones, a dual-stack, ipv4 and ipv6 zone. I first need to edit /etc/bind/named.conf.local to add the following lines:

//
// Do any local configuration here
//
 
zone "token.ds.whatismydns.ebower.com" in {
        type master;
        file "/etc/bind/db.token.ds.whatismydns.ebower.com";
};
 
zone "token.ipv4.whatismydns.ebower.com" in {
        type master;
        file "/etc/bind/db.token.ipv4.whatismydns.ebower.com";
};
 
zone "token.ipv6.whatismydns.ebower.com" in {
        type master;
        file "/etc/bind/db.token.ipv6.whatismydns.ebower.com";
};

And now I need to create the three files I just referenced, they all look like this with a different $ORIGIN value.

; BIND db file for token.whatismydns.ebower.com
 
$TTL 30
 
@       IN      SOA     whatismydns.ebower.com.      nobody.ebower.com. (
                        2013022210      ; serial number YYMMDDNN
                        28800           ; Refresh
                        7200            ; Retry
                        864000          ; Expire
                        30           ; Min TTL
                        )
 
                NS      whatismydns.ebower.com.
 
 
$ORIGIN token.ds.whatismydns.ebower.com.
*       CNAME   www.ebower.com.

Note that the TTL probably isn't important and setting it to 86400 (one day) should work fine. Essentially what this says is that anything.token.[ds|ipv4|ipv6].whatismydns.ebower.com should just be a CNAME to my webserver. Note that this is true even for IPv4-only and IPv6-only lookups, remember we don't care how your browser gets to us we care how the DNS server gets to us.

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"];
  # 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';
  }
  # Arrays are much nicer to deal with...
  $host_arr = explode(".", $host);
  # If this is a token hostname we can do a lookup against the query log.
  if ($host_arr[1] == 'token') {
    $dns_server = `tail -n 25 /var/log/query.log | grep query:\ $host | awk '{print $2}' | awk -F\# '{print $1}' | tail -n1`;
    print $dns_server;
  } else {
    # If we don't see a token embedded in the hostname we should generate one.
    $token = md5($_SERVER["REMOTE_ADDR"].$_SERVER["REMOTE_PORT"].$_SERVER["REQUEST_TIME_FLOAT"]);
    $hostname = $token.".token.".$host;
    # Now issue a redirect.
    header("Location: http://$hostname");
  }
?>

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.

For 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:

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 $4}' | 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.multi.".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/multi.php?token=$token' 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