From eBower Wiki
Jump to: navigation, search

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. There are plenty of one-stop shops for this data (this is one of my favorites), but sometimes you just want to build it yourself in a method you can script.

Can I Use These Websites?

Sure! However, realize that I'm not exactly a bulletproof corporate solution so don't expect 100% uptime. I'm publishing the blueprints to these pages because if you need it to work you may want to build it yourself.

If you can't remember where they are, you can hit eBower.com for a list.

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 then ran:

sudo service apache2 reload
<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 Host?

Very similar to the What is My IP websites, this simply returns a reverse lookup on your hostname:

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

And these sites will return your IPv6 address:

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/whatismyhost.conf, then I created a symlink to /etc/apache2/sites-enabled/whatismyhost.conf and then ran:

sudo service apache2 reload
<VirtualHost *:80>
        ServerAdmin webmaster@ebower.com
        ServerName whatismyhost.ebower.com
        ServerAlias *.whatismyhost.ebower.com
        DocumentRoot /var/www/whatismyhost
        <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 whatismyhost.ebower.com, but anything under whatismyhost.ebower.com will work as well thanks to the ServerAlias line. If I created a DNS record for foo.whatismyhost.ebower.com it would work with no changes.

Webpage Configuration

The webpage itself is pretty simple. I placed it in /var/www/whatismyhost/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`;
  }
  system("dig -x ".$myip." | awk 'f{print $5;f=0} /^;; ANSWER/{f=1}'");
?>

I use the same call to check if I'm coming from my internal network, and then just do a dig to get the PTR record.

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

You don't need to run this on your web server, but you will need to scrape the logs from the web server. This could mean logging to an NFS mounted drive or an RPC call in the back end, but I'm going to assume you're just scanning the log directly.

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; 
        print-time yes;
    }; 
    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

Options

Most of /etc/bind/named.conf.options is just fine, however you probably don't want to have an open resolver since that could be a target for a DNS reflection attack where someone could flood your DNS server with requests destined for their target, using your IP address. To disable this edit the options file and add these lines to the bottom:

        allow-transfer {"none";};
        allow-recursion {"none";};
        recursion no;

Now you should be able to resolve the domain you explicitly know about, but you won't ask another DNS server for things you don't know about.

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 300
 
@       IN      SOA     whatismydns.ebower.com.      nobody.ebower.com. (
                        2015070801      ; serial number YYMMDDNN
                        28800           ; Refresh
                        7200            ; Retry
                        864000          ; Expire
                        300             ; 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.

Now that you're complete, you can run:

sudo service bind9 restart

Router Config

Remember you'll also need to open up port 53 to point to wherever you installed this server. Technically you should open up both UDP and TCP, but unless you're looking at really long hashes UDP alone should be enough and may provide some additional protections. DNS will default to UDP but if there's a response too large for a single packet it will ask to connect via TCP. This could be a boon (you now know that the IP address connecting to you is probably legit) or just yet another potential opening (having port 53 open on TCP is easy to detect and identify you as a DNS server, if you've got a secure DNS server you don't even need to respond on a DNS request you don't care about).

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 $4}' | 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:

There are some downsides with this solution as well:

  • It's a more complex webpage we're serving up with an invisible image and an iframe. For browser users this isn't bad, but for scripting it's a little more complex.
  • The iframe and image are loaded asynchronously, since we don't know that the iframe will be loaded after the DNS lookup for the image we need to delay the log scrape. This could be avoided through an even more complex page using Javascript, but then compatibility may suffer.
  • The token is freeform text. In the previous implementation the hostname is used, which means we know roughly what the format is and there are limits to the characters in the token. Using arbitrary strings we may want to be a little more careful with processing it.
  • The delay limits the load the server can handle. Since I limit the lookup to the last "n" lines multiple simultaneous lookups could scroll past this limit. The alternative is to increase or eliminate "n" but then you may be able to scroll back to previous lookups which, while I can't imagine how if we assume reasonable token generation, seems like it could be a privacy concern.
  • Because we're not forcing a lookup on the hostname as part of retrieving the hostname it's a lot easier to see previous users' requests. Technically this is possible with the other solution as well, by creating an /etc/hosts entry for the hostname you want to look up you won't hit my DNS server to overwrite the stale entry.

Still, this gives different data which may be useful despite the limitations.

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

Is It Up?

This doesn't follow the same simple, UI-free pattern of the other sites in this list (although it could be re-written to do so). In general I want to have a simple input field when I do this, I don't have much use for making this programmatic at the moment so I made the output a little more complex than a simple "up/down" response.

There are plenty of websites out there that will tell you if a website is up remotely, but they don't have the features I need. I want to know:

  • If the host is responding to HTTP requests - most sites will tell you this.
  • If the hostname is resolving.
  • If the hostname supports IPv6.
  • If the host supports TLS.
  • If a .onion site only accessible via Tor is up.
  • If a site is being blocked from my server's ISP or geography.
  • If a site containing UNICODE characters is up.

There are sites that do some of these things, but I have yet to find one that does all of them. Now the problem with hosting one of these sites myself is that I don't have multiple points of presence. If my particular ISP has issues connecting I can't tell if the host is down or if my ISP is having problems, if I had other servers in other geographies on other ISPs I could more deterministically figure out if it's up or down.

I didn't make an isitup.ebower.com hostname or this one since it is a bit different from the rest. However, the premise should be the same as the other non-DNS pages here.

You'll notice that this also works as a .onion site. Some of the others will as well, but they won't give you useful information. Your IP address will always be ::1 (perhaps 127.0.0.1), your host will be blank or localhost, the DNS detection will just return "false" (that's my default response to an invalid host header). The http://rn6g7xhttyls7qca.onion/whatismyuastring/ site will be meaningful but that's about it.

Webpage Configuration

The website consists of three files. The is_it_up.css style sheet provides basic formatting but is otherwise uninteresting. To support UNICODE I needed to support Punycode, but unfortunately that's not native to PHP so I used a class I found here. You can download the punycode_class.php file from GitHub and add it to your server.

The important bit is input validation. Since you're running code on your server to reach out and talk to another server you probably want to make sure things look reasonable before you try anything. Hence I borrowed a hostname validator and spend a lot of lines trying to sanitize the input before I do a DNS lookup.

For my own uses I don't care about specific URLs and really only want a hostname. I want to know what the HTTP response is, not just an up/down. And I want to know about cert warnings and the like. I have Tor running on my webserver (see Tor Server Configuration) to configure a Hidden Service (this also runs at http://rn6g7xhttyls7qca.onion/is_it_up/) so I just need to specify a SOCKS proxy for the .onion support.

Note that I'm not running a truly hidden service, the same content is accessible via the public Internet traceable to an IP address associated with me. If this was a hidden service there's no way I would want to do arbitrary DNS lookups or print out the resulting IP addresses. If I set up a DNS server (like the What is my DNS? above) and trick you into doing a DNS lookup via the public Internet I can get your DNS server. And the ECS EDNS0 extension means that even if you use a centralized DNS server I may get your actual subnet and narrow down who you are. And, of course, if you try to access the webserver over the Internet I get your actual IP address. All of this means that if you don't want people to know who you are, *only* do Tor-based lookups and even then you're running a significant risk.

Aside from the formatting and that Punycode converter library we've only got index.php that we need. No fancy Javascript wait screens because I don't want to rely on just using a browser, just some PHP to process the input and some vanilla HTML output.

<html>
<?php
// For non-ASCII hostnames
//   Reference: http://www.zedwood.com/article/php-idn-punycode-converter
include_once("punycode.class.php");
 
// Reference: https://stackoverflow.com/questions/1755144/how-to-validate-domain-name-in-php
function is_valid_domain_name($domain_name) {
    return (
      preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name) && //valid chars check
      preg_match("/^.{1,253}$/", $domain_name) && //overall length check
      preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name)   ); //length of each label
}
 
function do_curl($url, $version) {
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $url);
  // I'm interested in an HTTP response
  curl_setopt($ch, CURLOPT_HEADER, true);
  // We don't want to output the response.
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2);
  curl_setopt($ch, CURLOPT_TIMEOUT, 10);
  if ( $version == 'v4' ) {
    curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  } elseif ( $version == 'v6' ) {
    curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
  } elseif ( $version == 'tor' ) {
    // To grab content via Tor we use a local SOCKS proxy
    // This enables checking .onion sites.
    curl_setopt($ch, CURLOPT_PROXYTYPE, 7);
    curl_setopt($ch, CURLOPT_PROXY, 'http://localhost:9050/');
  }
 
  if( ! $result = curl_exec($ch)) {
    $response = curl_error($ch);
  } else {
    // I like the formatting of the first line instead of parsing the
    // response code directly.
    $response = strtok($result, "\n");;
  }
 
  curl_close($ch);
  return $response;
}
 
  $host = $_GET["host"];
  // Don't show the output div by default.
  $show_div = 'none';
  // If someone puts in a URL we can pull out the host.
  $parsed_host = parse_url($host);
  if ( isset($parsed_host["host"]) ) {
    $host = $parsed_host["host"];
  }
 
  // Preserve the input hostname in all it's glory.
  $input_host = $host;
 
  // If the hostname uses Unicode we need to convert to Punycode.
  $host = Punycode::encodeHostname($host);
 
  // Check if it's a domain.
  $is_domain = is_valid_domain_name($host);
 
  // Check if it's a direct IP.
  $is_ip = filter_var($host, FILTER_VALIDATE_IP);
  if ( $is_ip != "" ) {
    $is_ip = true;
  } else {
    $is_ip = false;
  }
 
  // If it's neither a domain nor IP show an error.
  if ( (! $is_domain ) && ( ! $is_ip ) ) {
    if ( $host != "" ) {
      echo "<div class='err_div'>Invalid hostname: $host</div><br>";
    }
  } else {
    // It looks like we've got valid content, show the output div.
    $show_div = 'visible';
 
    // If it's an IP, we don't need to do a lookup.
    if ( filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) {
      $ipv4 = $host;
    } else {
      // Technically we could pull this out of the curl results...
      $dns = dns_get_record($host, DNS_A);
      $ipv4 = $dns[0]['ip'];
    }
    if ( filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
      $ipv6 = $host;
      $host = '['.$host.']';
    } else {
      $dns = dns_get_record($host, DNS_AAAA);
      $ipv6 = $dns[0]['ipv6'];
    }
 
    // If we don't have a valid IP, don't bother with the curl.
    if ( filter_var($ipv4, FILTER_VALIDATE_IP) ) {
      $v4_http = do_curl('http://'.$host, "v4");
      $v4_https = do_curl('https://'.$host, "v4");
    } else {
      $ipv4 = "No v4 address";
      $v4_http = "<center>N/A</center>";
      $v4_https = "<center>N/A</center>";
    }
    if ( filter_var($ipv6, FILTER_VALIDATE_IP) ) {
      $v6_http = do_curl('http://'.$host, "v6");
      $v6_https = do_curl('https://'.$host, "v6");
    } else {
      $ipv6 = "No v6 address";
      $v6_http = "<center>N/A</center>";
      $v6_https = "<center>N/A</center>";
    }
 
    // Tor is special, the host may not need to resolve.
    $tor_http = do_curl('http://'.$host, "tor");
    $tor_https = do_curl('https://'.$host, "tor");
  }
?>
 
<head>
  <title>Is it up? - eBower style</title>
  <link rel="stylesheet" href="is_it_up.css">
</head>
<body>
<div class="input_div">
  <form method="get">
    <input type="text" id="host_input" name="host" placeholder="hostname" value="<?php echo $input_host;?>" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
  </form>
</div>
<div class="output_div" style="display: <?php echo $show_div; ?>;">
<table>
  <tr>
    <td></td>
    <th>HTTP</th>
    <th>HTTPS</th>
  </tr>
  <tr>
    <!--
      Of course, exposing the IP address of the hostname will expose your
      server's location as well. If you're running strictly as a .onion site
      do not resolve the hostname, let alone display the IP address, and
      instead only use the Tor-enabled check.
     -->
    <th>IPv4<br><span class="ipaddress">(<?php echo $ipv4; ?>)</span></th>
    <td><?php echo $v4_http; ?></td>
    <td><?php echo $v4_https; ?></td>
  </tr>
  <tr>
    <th>IPv6<br><span class="ipaddress">(<?php echo $ipv6; ?>)</span></th>
    <td><?php echo $v6_http; ?></td>
    <td><?php echo $v6_https; ?></td>
  </tr>
  <tr>
    <th>Tor</th>
    <td><?php echo $tor_http; ?></td>
    <td><?php echo $tor_https; ?></td>
  </tr>
</table>
</div>
</body>
</html>

Other Solutions

There are other implementations that can bring this more inline with the other sites. For example, passing variables like ip_version=4 and protocol=https can let you return whatever response you want, including just an "up" if there's an HTTP response code and "down" if not.

I don't support non-standard port numbers, mostly because they're not a good solution. Using something like nmap on the back end you can do a TCP ping on a specific port to see if it's up for non-web protocols like SSH, or write custom protocol "pings" like using ssh-keyscan to return the server type or public key of a server.

You can also reconfigure Curl to behave differently:

// Follow redirects.
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
 
// Ignore TLS errors.
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

References

  1. Ubuntu Server Guide, Troubleshooting DNS