\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 "".$label."\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; } ?>