\n";
?>
prepare($query))) {
echo "Prepare failed: (" . $mysqli->errno . ") " . $mysqli->error."\n";
} else {
#echo "Prepare ok\n";
}
$query = "
REPLACE INTO origin (block, network, netmask, asn, date, country, authority)
VALUES (?, ?, ?, ?, ?, ?, ?)
";
if (!($q_insert_origin = $db->prepare($query))) {
echo "Prepare failed: (" . $mysqli->errno . ") " . $mysqli->error."\n";
} else {
#echo "Prepare ok\n";
}
$query = "
SELECT asn, description FROM asnumbers WHERE asn = ? LIMIT 1
";
if (!($q_select_asn = $db->prepare($query))) {
echo "Prepare failed: (" . $mysqli->errno . ") " . $mysqli->error."\n";
} else {
#echo "Prepare ok\n";
}
$query = "
REPLACE INTO asnumbers (asn, country, authority, date, description)
VALUES (?, ?, ?, ?, ?)
";
if (!($q_insert_asn = $db->prepare($query))) {
echo "Prepare failed: (" . $mysqli->errno . ") " . $mysqli->error."\n";
} else {
#echo "Prepare ok\n";
}
# Set up filter in a temp file (empty if there is no filter)
$filter = ( array_key_exists('filter', $_GET) ? $_GET['filter'] : '' );
$fname = tempnam('.', 'filter');
$fh = fopen($fname, "w");
fwrite($fh, $filter);
fclose($fh);
# Determine plot color
$color = ( array_key_exists('color', $_GET) ? $_GET['color'] : 'white' ); # Default = white
$color = preg_replace('/[^\#\da-z]+/i', '', $color); # Remove illegal chars to prevent abuse (BE CAREFUL!)
# Determine time window
$timewin = ($_GET['timewin'] ? $_GET['timewin'] : '-300'); # Default = last 5 minutes
$timewin = preg_replace('/[^\d\/\.\:\-]+/', '', $timewin); # Remove illegal chars to prevent abuse (BE CAREFUL!)
$limit = 256;
# Execute nfdump to get a list of network flows
$lines = array();
$cmd = "nfdump -R /opt/nfsen/profiles-data/live/firefly -q -o long -t ".$timewin." -f ".$fname;
exec($cmd, $lines);
# Remove temp file
unlink($fname);
# Each flow should match the following regular expression
$pattern = '/^([\d\-]+ [\d\:\.]+)\s+([\d\.]+)\s+(\w+)[\s\x01]+([\d\.]+)\:(\d+)\s+\-\>[\s\x01]+([\d\.]+)\:(\d+)\s+/';
# Process each flow as returned by nfdump
$hosts = array();
$flows = array();
foreach ($lines as $line) {
$matches = array();
preg_match($pattern, $line, $matches);
if (! array_key_exists(4, $matches) || ! array_key_exists(6, $matches)) { continue; } # Source and/or destination address missing
# Register src and dst IP addresses in associative array $hosts
$hosts[$matches[4]] = array("ip"=>$matches[4], "x"=>0, "y"=>0);
$hosts[$matches[6]] = array("ip"=>$matches[6], "x"=>0, "y"=>0);
# Register flow in associative array $flows
$pair = array($matches[4], $matches[6]);
usort($pair, "by_ip"); # Both A,B and B,A should store as A,B to avoid duplicates
$flows[$pair[0]."-".$pair[1]]['h1'] = $pair[0];
$flows[$pair[0]."-".$pair[1]]['h2'] = $pair[1];
$flows[$pair[0]."-".$pair[1]]['count'] = (array_key_exists('count', $flows[$pair[0]."-".$pair[1]]) ? $flows[$pair[0]."-".$pair[1]]['count']+1 : 1);
}
$number_of_hosts = count($hosts);
# # If we have more than $limit hosts, analyze again but this time combine class C networks
# if ($number_of_hosts > $limit) {
# $hosts = array();
# $flows = array();
#
# foreach ($lines as $line) {
# $matches = array();
# preg_match($pattern, $line, $matches);
#
# if (!$matches[4] || !$matches[6]) { continue; } # Source and/or destination address missing
# $matches[4] = preg_replace('/\.\d+$/', '.0/24', $matches[4]);
# $matches[6] = preg_replace('/\.\d+$/', '.0/24', $matches[6]);
#
# # Register src and dst IP addresses in associative array $hosts
# $hosts[$matches[4]] = array("ip"=>$matches[4], "x"=>0, "y"=>0);
# $hosts[$matches[6]] = array("ip"=>$matches[6], "x"=>0, "y"=>0);
#
# # Register flow in associative array $flows
# $pair = array($matches[4], $matches[6]);
# usort($pair, "by_ip"); # Both A,B and B,A should store as A,B to avoid duplicates
# $flows[$pair[0]."-".$pair[1]]['h1'] = $pair[0];
# $flows[$pair[0]."-".$pair[1]]['h2'] = $pair[1];
# $flows[$pair[0]."-".$pair[1]]['count']++;
#
# }
# $number_of_hosts = count($hosts);
#
# }
# # Still too many hosts? Combine class B networks
# if ($number_of_hosts > $limit) {
# $hosts = array();
# $flows = array();
#
# foreach ($lines as $line) {
# $matches = array();
# preg_match($pattern, $line, $matches);
#
# if (!$matches[4] || !$matches[6]) { continue; } # Source and/or destination address missing
# $matches[4] = preg_replace('/\.\d+\.\d+$/', '.0.0/16', $matches[4]);
# $matches[6] = preg_replace('/\.\d+\.\d+$/', '.0.0/16', $matches[6]);
#
# # Register src and dst IP addresses in associative array $hosts
# $hosts[$matches[4]] = array("ip"=>$matches[4], "x"=>0, "y"=>0);
# $hosts[$matches[6]] = array("ip"=>$matches[6], "x"=>0, "y"=>0);
#
# # Register flow in associative array $flows
# $pair = array($matches[4], $matches[6]);
# usort($pair, "by_ip"); # Both A,B and B,A should store as A,B to avoid duplicates
# $flows[$pair[0]."-".$pair[1]]['h1'] = $pair[0];
# $flows[$pair[0]."-".$pair[1]]['h2'] = $pair[1];
# $flows[$pair[0]."-".$pair[1]]['count']++;
#
# }
# $number_of_hosts = count($hosts);
#
# }
# # Still too many hosts? Combine class A networks
# if ($number_of_hosts > $limit) {
# $hosts = array();
# $flows = array();
#
# foreach ($lines as $line) {
# $matches = array();
# preg_match($pattern, $line, $matches);
#
# if (!$matches[4] || !$matches[6]) { continue; } # Source and/or destination address missing
# $matches[4] = preg_replace('/\.\d+\.\d+\.\d+$/', '.0.0.0/8', $matches[4]);
# $matches[6] = preg_replace('/\.\d+\.\d+\.\d+$/', '.0.0.0/8', $matches[6]);
#
# # Register src and dst IP addresses in associative array $hosts
# $hosts[$matches[4]] = array("ip"=>$matches[4], "x"=>0, "y"=>0);
# $hosts[$matches[6]] = array("ip"=>$matches[6], "x"=>0, "y"=>0);
#
# # Register flow in associative array $flows
# $pair = array($matches[4], $matches[6]);
# usort($pair, "by_ip"); # Both A,B and B,A should store as A,B to avoid duplicates
# $flows[$pair[0]."-".$pair[1]]['h1'] = $pair[0];
# $flows[$pair[0]."-".$pair[1]]['h2'] = $pair[1];
# $flows[$pair[0]."-".$pair[1]]['count']++;
#
# }
# $number_of_hosts = count($hosts);
#
# }
$addresses = array_keys($hosts);
usort($addresses, "by_ip");
# Calculate tangent point for each IP address so they describe a circle
$count = 0;
foreach ($addresses as $address) {
# Circle properties (SVG coordinate space)
$cx = 512;
$cy = 512;
$r = 400;
# Calculate angle from center to tangent point where IP should be plotted
$radians = ((float) (2 * pi() * $count) / $number_of_hosts) + pi(); # First host starts at PI (east)
$degrees = ((float) (360 * $count) / $number_of_hosts); # Label orientation starts at 0 degrees
# Calculate tangent point
$vx = $cx - ($r * cos($radians));
$vy = $cy - ($r * sin($radians));
# Store results with IP address
$hosts[$address]['x'] = $vx;
$hosts[$address]['y'] = $vy;
$hosts[$address]['radians'] = $radians;
$hosts[$address]['degrees'] = $degrees;
$count++;
}
# Plot flow lines
foreach ($flows as $flow) {
$xa = $hosts[$flow['h1']]['x'];
$ya = $hosts[$flow['h1']]['y'];
$xb = $hosts[$flow['h2']]['x'];
$yb = $hosts[$flow['h2']]['y'];
$opacity = $flow['count'] * 0.05;
if ($opacity > 1) { $opacity = 1; }
echo "\n";
}
# Plot host labels
$origin_cache = Array();
$asn_cache = Array();
foreach ($addresses as $address) {
$vx = $hosts[$address]['x'];
$vy = $hosts[$address]['y'];
$degrees = $hosts[$address]['degrees'];
$origin = lookup_origin($address, $origin_cache, $q_select_origin, $q_insert_origin);
$asn = ( array_key_exists('asn', $origin) ? lookup_asn($origin['asn'], $asn_cache, $q_select_asn, $q_insert_asn) : Array() );
$label = $address . ( array_key_exists('org', $asn) ? ' ('.$asn['org'].')' : ( array_key_exists('geo', $origin) ? ' ('.$origin['geo'].')' : '' ));
echo "\n";
}
# Disconnect from MySQL and call it a day.
$db->close();
# Sort $a and $b in IP address order
function by_ip($a, $b) {
$ha = explode(".", $a); # '1.2.3.4' -> Array( '1', '2', '3', '4' )
$hb = explode(".", $b);
if ($ha[0] != $hb[0]) { return ($ha[0] < $hb[0]) ? -1 : 1; } # First octet
if ($ha[1] != $hb[1]) { return ($ha[1] < $hb[1]) ? -1 : 1; }
if ($ha[2] != $hb[2]) { return ($ha[2] < $hb[2]) ? -1 : 1; }
if ($ha[3] != $hb[3]) { return ($ha[3] < $hb[3]) ? -1 : 1; } # Last octet
return 0; # Same IP address
}
function lookup_origin($ip, &$cache, $select, $insert) {
if ($ip == '0.0.0.0' || $ip == '255.255.255.255') { return Array(); }
$address = ip2long($ip);
# Check cache (really just a copy of the last result)
if (array_key_exists('network', $cache)) {
#print "network=".long2ip($cache['network'])." address=".long2ip($address)." netmask=".long2ip($cache['netmask'])."\n";
if (($cache['network'] & $cache['netmask']) == ($address & $cache['netmask'])) {
#print "Origin cache hit\n";
return $cache;
}
}
# Check the MySQL database
#print "Searching database for IP $ip ($address)\n";
if (!$select->bind_param("i", $address)) {
trigger_error("Origin $ip Binding parameters failed: (" . $select->errno . ") " . $select->error, E_USER_WARNING);
}
if (!$select->execute()) {
trigger_error("Origin $ip Execute failed: (" . $select->errno . ") " . $select->error, E_USER_WARNING);
}
if (!$select->bind_result($block, $network, $netmask, $geo, $asn)) {
trigger_error("Origin $ip Binding result set failed: (" . $select->errno . ") " . $select->error, E_USER_WARNING);
}
if ($select->fetch()) {
#print " IP $ip found in database [IP=OK]\n";
$cache = Array( 'cidr' => $block, 'network' => $network, 'netmask' => $netmask, 'geo' => $geo, 'asn' => $asn );
$select->free_result();
return $cache;
} else {
#print " IP $ip NOT found in database [IP=FAIL]\n";
#trigger_error("IP $ip NOT found in database [IP=FAIL] ", E_USER_WARNING);
$select->free_result();
}
# Looks like we have to do a DNS lookup
$parts = explode(".", $ip);
$cmd = "dig +short ".implode(".", array_reverse($parts)).".origin.asn.cymru.com TXT";
$res = Array();
exec($cmd, $res);
#print $res[0]."\n";
if (! $res) { return Array(); }
$matches = Array();
$pattern = '/\"(?P[\d\s]+?)\s*\|\s*(?P[\d\.\/]+)\s*\|\s*(?P[\w]+)\s*\|\s*(?P\w+?)\s*\|.*\"/i';
preg_match($pattern, $res[0], $matches);
# If we found something, store it in the MySQL database
if ($matches && array_key_exists('cidr', $matches)) {
#trigger_error("IP $ip block ".$matches['cidr']." found in DNS ", E_USER_WARNING);
$parts = explode('/', $matches['cidr']);
$network = ip2long($parts[0]); # IP network address converted to unsigned long
$netmask = (0xFFFFFFFF << (32-$parts[1])) & 0xFFFFFFFF; # IP subnet mask converted to unsigned long
#print "cidr=".$parts[1].", 0xFFFFFFFF=".(0xFFFFFFFF).", calculated mask is ".$netmask."\n";
$date = (array_key_exists('date', $matches) ? $matches['date'] : null);
$matches['asn'] = $matches['asn'] * 1;
$insert->bind_param('siissss', $matches['cidr'], $network, $netmask, $matches['asn'], $date, $matches['geo'], $matches['authority']);
if (! $insert->execute()) {
trigger_error("Insert origin ".$matches['cidr']." failed: (".$insert->errno.") ".$insert->error, E_USER_WARNING);
}
$insert->free_result();
$matches['network'] = $network;
$matches['netmask'] = $netmask;
$cache = $matches;
}
return $matches;
}
function lookup_asn($asn, &$cache, $select, $insert) {
$asn = $asn * 1; # Some AS numbers have spaces in them
#print "ASN lookup: $asn\n";
if (! isset($asn)) {
return Array();
}
if (array_key_exists('asn', $cache)) {
if ($cache['asn'] == $asn) {
#print "ASN cache hit\n";
return $cache;
}
}
# Check the MySQL database
#print "Searching database for AS $asn\n";
if (!$select->bind_param("i", $asn)) {
trigger_error("AS $asn Binding parameters failed: (" . $select->errno . ") " . $select->error, E_USER_WARNING);
}
if (!$select->execute()) {
trigger_error("AS $asn Execute failed: (" . $select->errno . ") " . $select->error, E_USER_WARNING);
}
if (!$select->bind_result($asn, $org)) {
trigger_error("AS $asn Binding result set failed: (" . $select->errno . ") " . $select->error, E_USER_WARNING);
}
if ($select->fetch()) {
#print " AS $asn found in database [AS=OK]\n";
$cache = Array( 'asn' => $asn, 'org' => $org );
$select->free_result();
return $cache;
} else {
$select->free_result();
#print " AS $asn NOT found in database [AS=FAIL]\n";
}
# Looks line we have to make a DNS query
$cmd = "dig +short AS".$asn.".asn.cymru.com TXT";
#print "Executing $cmd\n";
$res = Array();
exec($cmd, $res);
if (! $res) { return Array(); }
$matches = Array();
$pattern = '/\"(?P[\d\s]+?)\s*\|\s*(?P[\w]+)\s*\|\s*(?P\w+?)\s*\|\s*(?P.*?)\s*\|\s*(?P.*?)\"/i';
preg_match($pattern, $res[0], $matches);
$asn_cache[$asn] = $matches;
# If we found something, store it in the MySQL database
if ($matches && array_key_exists('asn', $matches)) {
$date = (array_key_exists('date', $matches) ? $matches['date'] : null);
$insert->bind_param('sssss', $asn, $matches['geo'], $matches['authority'], $date, $matches['org']);
if (! $insert->execute()) {
trigger_error("Insert AS ".$matches['asn']." failed: (".$insert->errno.") ".$insert->error, E_USER_WARNING);
}
$insert->free_result();
$cache = $matches;
}
return $matches;
}
?>