diff --git a/.gitignore b/.gitignore index 4897e6f..89c8b25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ - .idea -config.php +/config.php +!php-fpm/src/config.php \ No newline at end of file diff --git a/LookingGlass.php b/LookingGlass.php index 5cb8c9f..d0ba507 100644 --- a/LookingGlass.php +++ b/LookingGlass.php @@ -1,4 +1,4 @@ - array("pipe", "r"), - 1 => array("pipe", "w"), - 2 => array("pipe", "w") - ); + $spec = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'] + ]; // sanitize + remove single quotes $host = str_replace('\'', '', filter_var($host, FILTER_SANITIZE_URL)); @@ -238,16 +261,17 @@ class LookingGlass // check for mtr/traceroute if (strpos($cmd, 'mtr') !== false) { $type = 'mtr'; + $parser = new Parser(); } elseif (strpos($cmd, 'traceroute') !== false) { $type = 'traceroute'; } else { $type = ''; } - $fail = 0; - $match = 0; + $fail = 0; + $match = 0; $traceCount = 0; - $lastFail = 'start'; + $lastFail = 'start'; // iterate stdout while (($str = fgets($pipes[1], 4096)) != null) { // check for output buffer @@ -260,12 +284,14 @@ class LookingGlass // correct output for mtr if ($type === 'mtr') { - if ($match < 10 && preg_match('/^[0-9]\. /', $str, $string)) { - $str = preg_replace('/^[0-9]\. /', '  ' . $string[0], $str); - $match++; - } else { - $str = preg_replace('/^[0-9]{2}\. /', ' ' . substr($str, 0, 4), $str); - } + // correct output for mtr + $parser->update($str); + echo '---' . PHP_EOL . $parser->__toString() . PHP_EOL . str_pad('', 4096) . PHP_EOL; + + // flush output buffering + @ob_flush(); + flush(); + continue; } // correct output for traceroute elseif ($type === 'traceroute') { @@ -306,7 +332,7 @@ class LookingGlass } $status = proc_get_status($process); - if ($status['running'] == true) { + if ($status['running']) { // close pipes that are still open foreach ($pipes as $pipe) { fclose($pipe); @@ -328,3 +354,189 @@ class LookingGlass return true; } } + +class Hop +{ + /** @var int */ + public $idx; + /** @var string */ + public $asn = ''; + /** @var float */ + public $avg = 0.0; + /** @var int */ + public $loss = 0; + /** @var float */ + public $stdev = 0.0; + /** @var int */ + public $sent = 0; + /** @var int */ + public $recieved = 0; + /** @var float */ + public $last = 0.0; + /** @var float */ + public $best = 0.0; + /** @var float */ + public $worst = 0.0; + + /** @var string[] */ + public $ips = []; + /** @var string[] */ + public $hosts = []; + /** @var float[] */ + public $timings = []; + +} + +class RawHop +{ + /** @var string */ + public $dataType; + /** @var int */ + public $idx; + /** @var string */ + public $value; +} + +class Parser +{ + /** @var Hop[] */ + protected $hopsCollection = []; + /** @var int */ + private $hopCount = 0; + /** @var int */ + private $outputWidth = 38; + + public function __construct() + { + putenv('RES_OPTIONS=retrans:1 retry:1 timeout:1 attempts:1'); + } + + public function __toString(): string + { + $str = ''; + foreach ($this->hopsCollection as $index => $hop) { + $host = $hop->hosts[0] ?? $hop->ips[0] ?? '???'; + + if (strlen($host) > $this->outputWidth) { + $this->outputWidth = strlen($host); + } + + $hop->recieved = count($hop->timings); + if (count($hop->timings)) { + $hop->last = $hop->timings[count($hop->timings) - 1]; + $hop->best = $hop->timings[0]; + $hop->worst = $hop->timings[0]; + $hop->avg = array_sum($hop->timings) / count($hop->timings); + } + + if (count($hop->timings) > 1) { + $hop->stdev = $this->stDev($hop->timings); + } + + foreach ($hop->timings as $time) { + + if ($hop->best > $time) { + $hop->best = $time; + } + + if ($hop->worst < $time) { + $hop->worst = $time; + } + } + + $hop->loss = $hop->sent ? (100 * ($hop->sent - $hop->recieved)) / $hop->sent : 100; + + $str = sprintf( + "%s%2d.|-- %s%3d.0%% %3d %5.1f %5.1f %5.1f %5.1f %5.1f\n", + $str, + $index, + str_pad($host, $this->outputWidth + 3, ' ', STR_PAD_RIGHT), + $hop->loss, + $hop->sent, + $hop->last, + $hop->avg, + $hop->best, + $hop->worst, + $hop->stdev + ); + } + + return sprintf(" Host%sLoss%% Snt Last Avg Best Wrst StDev\n%s", str_pad('', $this->outputWidth + 7, ' ', STR_PAD_RIGHT), $str); + } + + private function stDev(array $array): float + { + $sdSquare = function ($x, $mean) { + return pow($x - $mean, 2); + }; + + // square root of sum of squares devided by N-1 + return sqrt(array_sum(array_map($sdSquare, $array, array_fill(0, count($array), (array_sum($array) / count($array))))) / (count($array) - 1)); + } + + public function update($rawMtrInput) + { + //Store each line of output in rawhop structure + $things = explode(' ', $rawMtrInput); + + if (count($things) !== 3 && (count($things) !== 4 && $things[0] === 'p')) { + return; + } + + $rawHop = new RawHop(); + $rawHop->dataType = $things[0]; + $rawHop->idx = (int)$things[1]; + $rawHop->value = $things[2]; + + if ($this->hopCount < $rawHop->idx + 1) { + $this->hopCount = $rawHop->idx + 1; + } + + if (!isset($this->hopsCollection[$rawHop->idx])) { + $this->hopsCollection[$rawHop->idx] = new Hop(); + } + + $hop = $this->hopsCollection[$rawHop->idx]; + $hop->idx = $rawHop->idx; + switch ($rawHop->dataType) { + case 'h': + $hop->ips[] = $rawHop->value; + $hop->hosts[] = gethostbyaddr($rawHop->value) ? : null; + break; + case 'd': + //Not entirely sure if multiple IPs. Better use -n in mtr and resolve later in summarize. + //out.Hops[data.idx].Host = append(out.Hops[data.idx].Host, data.value) + break; + case 'p': + $hop->sent++; + $hop->timings[] = (float)$rawHop->value / 1000; + break; + } + + $this->hopsCollection[$rawHop->idx] = $hop; + + $this->filterLastDupeHop(); + } + + // Function to calculate standard deviation (uses sd_square) + + private function filterLastDupeHop() + { + // filter dupe last hop + $finalIdx = 0; + $previousIp = ''; + + foreach ($this->hopsCollection as $key => $hop) { + if (count($hop->ips) && $hop->ips[0] !== $previousIp) { + $previousIp = $hop->ips[0]; + $finalIdx = $key + 1; + } + } + + unset($this->hopsCollection[$finalIdx]); + + usort($this->hopsCollection, function ($a, $b) { + return $a->idx - $b->idx; + }); + } +} diff --git a/backend.php b/backend.php index 9c149ec..58badd5 100644 --- a/backend.php +++ b/backend.php @@ -1,4 +1,4 @@ - LG_TITLE, + 'custom_css' => LG_CSS_OVERRIDES, + 'logo_url' => LG_LOGO_URL, + 'logo_data' => LG_LOGO, + // + 'block_network' => LG_BLOCK_NETWORK, + 'block_lookingglas' => LG_BLOCK_LOOKINGGLAS, + 'block_speedtest' => LG_BLOCK_SPEEDTEST, + 'block_custom' => LG_BLOCK_CUSTOM, + 'custom_html' => '', + // + 'locations' => LG_LOCATIONS, + 'current_location' => LG_LOCATION, + 'maps_query' => LG_MAPS_QUERY, + 'facility' => LG_FACILITY, + 'facility_url' => LG_FACILITY_URL, + 'ipv4' => LG_IPV4, + 'ipv6' => LG_IPV6, + 'methods' => LG_METHODS, + 'user_ip' => LookingGlass::detectIpAddress(), + // + 'speedtest_iperf' => LG_SPEEDTEST_IPERF, + 'speedtest_incoming_label' => LG_SPEEDTEST_LABEL_INCOMING, + 'speedtest_incoming_cmd' => LG_SPEEDTEST_CMD_INCOMING, + 'speedtest_outgoing_label' => LG_SPEEDTEST_LABEL_OUTGOING, + 'speedtest_outgoing_cmd' => LG_SPEEDTEST_CMD_OUTGOING, + 'speedtest_files' => LG_SPEEDTEST_FILES, + // + 'tos' => LG_TERMS, + 'error_message' => false, +]; diff --git a/config.dist.php b/config.dist.php index 8b34072..51becde 100644 --- a/config.dist.php +++ b/config.dist.php @@ -1,5 +1,5 @@ -Company Looking Glass'; +// Define the URL where the logo points to; +const LG_LOGO_URL = 'https://github.com/hybula/lookingglass/'; + +// Define a custom CSS file which can be used to style the LG, set false to disable, else point to the CSS file; +const LG_CSS_OVERRIDES = false; + +// Enable or disable blocks/parts of the LG, set false to hide a part; +const LG_BLOCK_NETWORK = true; +const LG_BLOCK_LOOKINGGLAS = true; +const LG_BLOCK_SPEEDTEST = true; +// This enables the custom block, which you can use to add something custom to the LG; +define('LG_BLOCK_CUSTOM', getenv('ENABLE_CUSTOM_BLOCK') !== false); + +// Define a file here which will be used to display the custom block, can be PHP too which outputs HTML; +const LG_CUSTOM_HTML = __DIR__.'/custom.html.php'; +// Define a file here which will be loaded on top of the index file, this can be used to do some post logic; +const LG_CUSTOM_PHP = __DIR__.'/custom.post.php'; + +// Define the location of this network, usually a city and a country; +define('LG_LOCATION', getenv('LOCATION')); +// Define a query location for the link to openstreetmap (eg: Amsterdam, Netherlands will be https://www.openstreetmap.org/search?query=Amsterdam, Netherlands) +define('LG_MAPS_QUERY', getenv('MAPS_QUERY')); +// Define the facility where the network is located, usually a data center; +define('LG_FACILITY', getenv('FACILITY')); +// Define a direct link to more information about the facility, this should be a link to PeeringDB; +define('LG_FACILITY_URL', getenv('FACILITY_URL')); +// Define an IPv4 for testing; +define('LG_IPV4', getenv('IPV4_ADDRESS')); +// Define an IPv6 for testing; +define('LG_IPV6', getenv('IPV6_ADDRESS')); + +// Define the methods that can be used by visitors to test it out; +const LG_METHODS = [ + LookingGlass::METHOD_PING, + LookingGlass::METHOD_PING6, + LookingGlass::METHOD_MTR, + LookingGlass::METHOD_MTR6, + LookingGlass::METHOD_TRACEROUTE, + LookingGlass::METHOD_TRACEROUTE6, +]; + +// Define other looking glasses, this is useful if you have multiple networks and looking glasses; +const LG_LOCATIONS = [ + 'Location A' => 'https://github.com/hybula/lookingglass/', + 'Location B' => 'https://github.com/hybula/lookingglass/', + 'Location C' => 'https://github.com/hybula/lookingglass/', +]; + +// Enable the iPerf info inside the speedtest block, set too false to disable; +const LG_SPEEDTEST_IPERF = true; +// Define the label of an incoming iPerf test; +const LG_SPEEDTEST_LABEL_INCOMING = 'iPerf3 Incoming'; +// Define the command to use to test incoming speed using iPerf, preferable iPerf3; +const LG_SPEEDTEST_CMD_INCOMING = 'iperf3 -4 -c hostname -p 5201 -P 4'; +// Define the label of an outgoing iPerf test; +const LG_SPEEDTEST_LABEL_OUTGOING = 'iPerf3 Outgoing'; +// Define the command to use to test outgoing speed using iPerf, preferable iPerf3; +const LG_SPEEDTEST_CMD_OUTGOING = 'iperf3 -4 -c hostname -p 5201 -P 4 -R'; +// Define speedtest files with URLs to the actual files; +const LG_SPEEDTEST_FILES = [ + '100M' => 'https://github.com/hybula/lookingglass/', + '1G' => 'https://github.com/hybula/lookingglass/', + '10G' => 'https://github.com/hybula/lookingglass/' +]; + +// Define if you require visitors to agree with the Terms, set false to disable; +define('LG_TERMS', getenv('LG_TERMS') ?: 'https://github.com/hybula/lookingglass/'); diff --git a/index.php b/index.php index e776a8e..682b563 100644 --- a/index.php +++ b/index.php @@ -1,4 +1,4 @@ - @@ -82,9 +86,9 @@ if (LG_BLOCK_CUSTOM) { - <?php echo LG_TITLE; ?> + <?php echo $templateData['title'] ?> - '; } ?> + '; } ?> @@ -92,23 +96,23 @@ if (LG_BLOCK_CUSTOM) {
- - + +
- +
@@ -118,23 +122,23 @@ if (LG_BLOCK_CUSTOM) {
- - Map - + + Map + - +
@@ -143,22 +147,22 @@ if (LG_BLOCK_CUSTOM) {
- - + +
- - + +
- - + +
@@ -166,48 +170,50 @@ if (LG_BLOCK_CUSTOM) {
- + - +

Looking Glass

- +
Target - +
- +
- > - + > +
- +
- '.$errorMessage.'
'; ?> + + +
- + - +

Speedtest

- +
- -

- + +

+
- -

- + +

+
- +
- $link) { ?> - - + $link): ?> + +
- - - + +
+ +