#!/usr/bin/perl
use strict;
use warnings;
#
# ONLY OpenBSD modules included with the default installation are permitted!
#
use HTTP::Tiny;
use Net::Ping;
use Time::HiRes;

# Configuration hash (editable)
my %config = (
  'timeout'  => 0.20,   # How long to wait before giving up on server response, in seconds
  'debug'    => 1,      # Debug output?
  'top'      => 3,      # How many entries to return?
  'protocol' => 'tcp',  # tcp, udp, icmp, et al
  'port'     => 'http', # (used in getservbyname sub)
);

# hash to store servers and response times to protocol/port requests
my %serverstats;

# create HTTP::Tiny instance
my $http = HTTP::Tiny->new;

# Get a list of all current OpenBSD FTP servers
my $response = $http->get('http://ftp.eu.openbsd.org/pub/OpenBSD/ftplist');
die "Failed!\n" unless $response->{success};

# Iterate through server list and get TCP/80 response time in ms
foreach my $line (split("\n", $response->{content})) {
  if ($line !~ /(cdn)/) {
    if ($line =~ /(http:\/\/)(.+?)(\/\S+)/) {
        my $response = &httping($2);
        $serverstats{$1.$2.$3} = $response if ($response);
    }
  }
}

# Sort & print servers by response time in ms
my $i = 0;
foreach my $key (sort {$serverstats{$a} <=> $serverstats{$b} } keys %serverstats) {
  $i++;
  print "$key\n";
  last if $i eq $config{'top'};
}

#
# Ping TCP/80 and return response time or 0 if unresponsive + some diagnostic output
#

sub httping ($) {
  my $host = shift;
  my $ping = Net::Ping->new($config{'protocol'});
  $ping->hires();
  $ping->{port_num} = getservbyname($config{'port'}, $config{'protocol'});
  print ("Trying $host... ") if $config{'debug'};
  my ($retval, $duration, $ip) = $ping->ping($host, $config{'timeout'});
  $ping->close();
  $duration = int($duration * 1000);

  if ($retval) {
    print ("$duration ms\n") if $config{'debug'};
    return $duration;
  } else {
    print ("unresponsive\n") if $config{'debug'};;
    return 0;
  }
}
