Friday, December 26, 2008

Passive monitoring of ping times on incoming TCP connections

For those of hosting web applications, it is frequently convenient to have ping time information available for all incoming connections. Most networks these days block icmp ping requests, making it impossible to actively ping users. One workaround is to passively monitor events in the TCP connection which have approximately the same round trip time as a ping request. Specifically, the time between a packet from a client with SYN and ACK flags set and the next packet received with an ACK flag set are monitored and reported.

Below is a PHP script which uses tcpdump to monitor incoming connections on ports 80 and 443. Due to the fact that packet sniffing is used, root access is required for execution.

#!/usr/bin/php

/**
* This is a script which tracks the tcp handshake to attempt to passively monitor our client's ping times.
*
*/


$ip_latency = array();

$handle = popen('sudo tcpdump -n -tt "tcp[tcpflags] & (tcp-syn|tcp-ack) != 0 && (port 80 or port 443)" 2>/dev/null', 'r');

while(! feof($handle)) {
$output = fgets($handle);

if (preg_match('/^([0-9.]+).* > ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\.([0-9]+): S .* ack /', $output, $matches)) {
// it's a SYN+ACK
// record the timestamp and wait for the response
list($dummy, $timestamp, $remote_ip, $remote_port) = $matches;

$ip_latency[$remote_ip.':'.$remote_port] = array('start' => $timestamp);

// echo("SYNACK $timestamp, $remote_ip, $remote_port\n");
} elseif (preg_match('/^([0-9.]+) IP ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\.([0-9]+) > .*: \. ack 1 /', $output, $matches)) {
// it's an ACK 1
list($dummy, $timestamp, $remote_ip, $remote_port) = $matches;

if (isset($ip_latency[$remote_ip.':'.$remote_port]) && ! isset($ip_latency[$remote_ip.':'.$remote_port]['end'])) {
$ip_latency[$remote_ip.':'.$remote_port]['end'] = $timestamp;

$latency = $timestamp - $ip_latency[$remote_ip.':'.$remote_port]['start'];
// echo("ACK $timestamp, $remote_ip, $remote_port\n");
printf("%s - %s : %2.8f\n", $timestamp, $remote_ip, $latency);
unset($ip_latency[$remote_ip.':'.$remote_port]);
}

if (rand(0, 100) == 0) {
// clean up, clear out all entries older than 100s

foreach($ip_latency as $key => $timing) {
if ($timestamp - $timing['start'] > 100) {
// echo("PURGE $key\n");
unset($ip_latency[$key]);
}
}
}
}
}

No comments: