Commit c3191473 authored by Leo Leung's avatar Leo Leung
Browse files

Initial commit

parents
{
"name": "steamr/network-utilities",
"type": "library",
"description": "A collection of network utilities",
"keywords": ["id","generator"],
"homepage": "https://steamr.com",
"license": "MIT",
"authors": [
{
"name": "Leo Leung",
"email": "leo@steamr.com"
}
],
"require": {
"php": ">=5.6.0"
},
"autoload": {
"psr-4": {
"Steamr\\NetworkUtilities\\": "src/"
}
}
}
<?php
namespace Steamr\GeoIp;
use GeoIp2\Database\Reader;
class AddressLookup
{
// not an exhaustive list
private $reserved_ips = [
['start' => '0.0.0.0', 'end' => '0.255.255.255', 'reason' => 'software subnet',],
['start' => '10.0.0.0', 'end' => '10.255.255.255', 'reason' => 'private range',],
['start' => '100.64.0.0', 'end' => '100.127.255.255', 'reason' => 'private range',],
['start' => '127.0.0.0', 'end' => '127.255.255.255', 'reason' => 'localhost',],
['start' => '169.254.0.0', 'end' => '169.254.255.255', 'reason' => 'local-link subnet',],
['start' => '172.16.0.0', 'end' => '172.31.255.255', 'reason' => 'private range',],
['start' => '192.0.0.0', 'end' => '192.0.0.255', 'reason' => 'private range',],
['start' => '192.0.2.0', 'end' => '192.0.2.255', 'reason' => 'test net',],
['start' => '192.88.99.0', 'end' => '192.88.99.255', 'reason' => 'reserved subnet',],
['start' => '192.168.0.0', 'end' => '192.168.255.255', 'reason' => 'private range',],
['start' => '198.18.0.0', 'end' => '198.19.255.255', 'reason' => 'private range',],
['start' => '198.51.100.0', 'end' => '192.51.100.255', 'reason' => 'documentation',],
['start' => '203.0.113.0', 'end' => '203.0.113.255', 'reason' => 'documentation',],
['start' => '224.0.0.0', 'end' => '239.255.255.255', 'reason' => 'multicast',],
['start' => '240.0.0.0', 'end' => '255.255.255.254', 'reason' => 'reserved subnet',],
['start' => '255.255.255.255', 'end' => '255.255.255.255', 'reason' => 'limited broadcast',]
];
private $asn_db = "/geoip/GeoLite2-ASN.mmdb";
private $city_db = "/geoip/GeoLite2-City.mmdb";
/**
* Handles a MAC address lookup
* @param $input must be a MAC address
* @return array
*/
public function input($input)
{
// valid IP?
if ($this->isValidAddress($input)) {
return [
'type' => 'ip',
'result' => $this->lookupAddress($input)
];
}
throw new Exception("Unsupported input");
}
/**
* Is a valid IPv4 address
* @param $input
* @return bool
*/
private function isValidAddress($input)
{
return @inet_pton($input) !== false;
}
/**
* Looks up an IP address using the MaxMind database
* @param $ip
* @return array
*/
private function lookupAddress($ip)
{
$reader_asn = new Reader(database_path() . $this->asn_db);
$reader_city = new Reader(database_path() . $this->city_db);
try {
$record = $reader_city->city($ip);
$result = [
'ip' => $ip,
'hostname' => gethostbyaddr($ip),
'city' => $record->city->name,
'region' => $record->mostSpecificSubdivision->name,
'country' => $record->country->isoCode,
'country_name' => Codes::country($record->country->isoCode),
'phone_code' => Codes::phoneCode($record->country->isoCode),
'location' => $record->location->latitude . "," . $record->location->longitude,
'postal' => $record->postal->code,
];
try {
$asn = $reader_asn->asn($ip);
$result['organization'] = $asn->autonomousSystemOrganization;
$result['asn'] = $asn->autonomousSystemNumber;
} catch (Exception $ee) {
}
} catch (Exception $e) {
$result = [
'ip' => $ip,
];
$reserve = $this->isReservedAddress($ip);
if ($reserve === FALSE) {
// unable to retrieve geo ip for a non-reserved IP?
$result['error'] = "Unable to obtain address information.";
} else {
$result['reserved'] = TRUE;
$result['type'] = $this->reservedReason($ip);
}
}
return $result;
}
/**
* Determines if the address is a special reserved address
* @param $ip
* @return false|string
*/
private function isReservedAddress($ip)
{
$ip_long = sprintf('%u', ip2long($ip));
foreach ($this->reserved_ips as $reserved_ip) {
$ip_start = ip2long($reserved_ip['start']);
$ip_end = ip2long($reserved_ip['end']);
if (($ip_long >= $ip_start) && ($ip_long <= $ip_end)) {
return true;
}
}
return false;
}
private function reservedReason($ip)
{
$ip_long = sprintf('%u', ip2long($ip));
foreach ($this->reserved_ips as $reserved_ip) {
$ip_start = ip2long($reserved_ip['start']);
$ip_end = ip2long($reserved_ip['end']);
if (($ip_long >= $ip_start) && ($ip_long <= $ip_end)) {
return $reserved_ip['reason'];
}
}
return "";
}
}
\ No newline at end of file
<?php
namespace Steamr\NetworkUtilities;
use Exception;
use Steamr\GeoIp;
class CidrCalculator
{
/**
* Handles the CIDR query
* @param $input
* @return array
* @throws Exception when the given subnet mask is invalid or if the input is invalid
*/
public function input($input)
{
// supported searches are:
// x.x.x.x/yy - cidr lookup
// x.x.x.x/yy.yy.yy.yy - ditto
// x.x.x.x - y.y.y.y - ip range lookup, list of cidrs
// cidr lookup, ip/netmask, ip/bits
if (strpos($input, "/")) {
list($ip, $netmask) = explode("/", $input);
if ($this->isValidAddress($ip) && $this->isValidNetmask($netmask)) {
return [
'type' => 'cidr',
'result' => $this->processCidr($ip, $netmask)
];
}
}
// range of IPs
if (strpos($input, "-")) {
list($ip_from, $ip_to) = explode("-", $input);
if ($this->isValidAddress($ip_from) && $this->isValidAddress($ip_to)) {
return [
'type' => 'range',
'result' => $this->processRange($ip_from, $ip_to)
];
}
}
throw new Exception("Unsupported input");
}
/**
* Determines the range of the given IP and netmask
* @param $ip
* @param $netmask
* @return array
* @throws Exception
*/
private function processCidr($ip, $netmask)
{
$ipLong = ip2long($ip);
if ($ipLong === FALSE) {
throw new Exception("Invalid IP address");
}
// netmask to bitmask
$mask = $this->netmaskToMask($netmask);
// output the cidr calculation
return [
'start' => long2ip($ipLong & $mask),
'end' => long2ip($ipLong | ~$mask),
'netmask_bits' => $this->maskToNetmaskBits($mask),
'netmask' => $this->maskToNetmaskNet($mask),
'size' => 1 + (ipLong & $mask) - ($ipLong | ~$mask),
];
}
/**
* Returns a list of CIDRs that covers the given IP address range
* @param $ip_start
* @param $ip_end
* @return array
* @throws Exception
*/
private function processRange($ip_start, $ip_end)
{
$subnets = [];
// convert IPs to longs
$low = ip2long($ip_start);
$high = ip2long($ip_end);
if ($low === FALSE || $high === FALSE) {
throw new Exception("Invalid IP address range");
}
if ($low > $high) {
throw new Exception("Invalid IP range");
}
// calculate from low to high the cidrs that fits
while ($low <= $high) {
// lowest bit of the IP address
$bit = $this->lowest_bit($low);
// ensure that the highest IP address with the lowest bitmask
// does not go past the end IP address. if it does, shrink the
// subnet by 1 bit.
$bitmask = $this->netmaskToMask(32 - $bit);
$highest = $low | ~$bitmask;
while ($highest > $high) {
$bitmask = $this->netmaskToMask(32 - --$bit);
$highest = $low | ~$bitmask;
}
// save this cidr subnet
$subnet = [
'start' => long2ip($low),
'end' => long2ip($highest),
'netmask' => $this->maskToNetmaskNet($bitmask),
'netmask_bits' => $this->maskToNetmaskBits($bitmask), // aka 32 - bit
'size' => 1 + $highest - $low,
];
$subnets[] = $subnet;
// raise the lower bound and continue
$low = $low + (0x01 << $bit);
}
return $subnets;
}
/**
* Is a valid IPv4 address
* @param $input
* @return bool
*/
private function isValidAddress($input)
{
return @inet_pton($input) !== false;
}
/**
* Is a valid netmask, as either /xx or /xxx.xxx.xxx.xxx
* @param $netmask
* @return bool
* @throws Exception
*/
private function isValidNetmask($netmask)
{
if ($netmask >= 0 && $netmask <= 32) {
return true;
}
$x = $this->netmaskToMask($netmask);
// todo: netmask should be all 1's in MSB.
return true;
}
/**
* Converts a /xxx.xxx.xxx.xxx netmask to a bitmask value
* @param $netmask
* @return int
* @throws Exception
*/
private function netmaskToMask($netmask)
{
$mask = 0x0;
if (strlen($netmask) > 2 && substr_count($netmask, '.') == 3) {
// a 255.255.255.0-like netmask
$parts = explode('.', $netmask);
for ($i = 0; $i <= 3; $i++) {
$mask = ($mask << 8) | (0xff & $parts[$i]);
}
} else if (is_numeric($netmask) && $netmask >= 0 && $netmask <= 32) {
for ($i = 0; $i < 32 - $netmask; $i++) {
$mask = ($mask << 1) | 0x01;
}
$mask = ~$mask;
} else {
throw new Exception("Invalid netmask '$netmask'");
}
return $mask;
}
/**
* Converts the intermediate mask into a subnet mask bit value (eg. 24)
* @param $bitmask
* @return int
*/
private function maskToNetmaskBits($bitmask)
{
return 32 - $this->lowest_bit($bitmask);
}
/**
* Converts the intermediate mask into a subnet mask (eg. 255.255.255.0)
* @param $bitmask
* @return string
*/
private function maskToNetmaskNet($bitmask)
{
return
(string)(($bitmask & 0xff000000) >> 24) . "." .
(string)(($bitmask & 0x00ff0000) >> 16) . "." .
(string)(($bitmask & 0x0000ff00) >> 8) . "." .
(string)($bitmask & 0x000000ff);
}
/**
* Returns the first non-zero least significant bit
* @param $value
* @return int
*/
private function lowest_bit($value)
{
for ($i = 0; $i < 32; $i++) {
if ($value & (0x01 << $i) != 0) return $i;
}
return 32;
}
}
<?php
namespace Steamr\NetworkUtilities;
class MacLookup
{
private $ouiFile = "/oui/parsed.txt";
/**
* Handles a MAC address lookup
* @param $input must be a MAC address
* @return array
*/
public function input($input)
{
// MAC lookup
if ( $this->isValidMacAddress($input)) {
return [
'type' => 'mac',
'result' => $this->process_mac_address($input)
];
}
throw new Exception("Unsupported input");
}
/**
* Determines if the given MAC address is valid
* @param $input
* @return bool
*/
private function isValidMacAddress($input)
{
// 00:00:00:00:00:00
$input = str_replace(['-', ':', ' '], '', $input);
$input = strtoupper($input);
if (strlen($input) != 12) {
return false;
}
return (preg_match('/([a-fA-F0-9]{2}[:|\-]?){6}/', $input) == 1);
}
/**
* Looks up the given MAC address
* @param $mac_address
* @return array
*/
private function process_mac_address($mac_address)
{
$mac_address = trim($mac_address);
$mac_address = str_replace(['-', ':', ' '], '', $mac_address);
$mac_address = strtoupper($mac_address);
$data = [
'mac' => $this->format_mac_address($mac_address),
'organization' => 'Unknown'
];
// get the first 6 bytes as oui
$oui = substr($mac_address, 0, 6);
// read the file
$fp = fopen(database_path() . $this->ouiFile, "r");
// read each line until matching oui is found.
// not ideal, but simple.
while (($str = fgets($fp, 4096)) !== false) {
$str = trim($str);
if (substr($str, 0, 6) == $oui) {
$parts = explode("|", $str);
// organization name or 'Private'
$data['organization'] = $parts[1];
if (isset($parts[2]) && strlen($parts[2]) > 0)
$data['address'] = $parts[2];
if (isset($parts[3]))
$data['city'] = $parts[3];
if (isset($parts[4])) {
$data['country'] = $parts[4];
$data['country_name'] = Codes::country($parts[4]);
}
}
}
fclose($fp);
return $data;
}
/**
* Formats the given MAC address in colon format.
* @param $input
* @return string formatted MAC address
*/
private function format_mac_address($input)
{
return $input[0] . $input[1] . ":" .
$input[2] . $input[3] . ":" .
$input[4] . $input[5] . ":" .
$input[6] . $input[7] . ":" .
$input[8] . $input[9] . ":" .
$input[10] . $input[11];
}
}
\ No newline at end of file
<?php
namespace Steamr\NetworkUtilities;
class PortLookup
{
private function valid_port($input)
{
if ($input[0] == ':' || strpos($input, "port") !== false) {
// strip non-numeric chars, and see if it's still numeric.
$port = preg_replace("/[^0-9]/", "", $input);
// must be a valid port number
return is_numeric($port) && ($port > 0 && $port <= 65536);
}
}
private function process_port($input)
{
// strip non-numeric chars
$port = preg_replace("/[^0-9]/", "", $input);
$data = [
'port' => $port,
'services' => []
];
$services = `grep -E '\s+$port/' {$this->services_file}`;
$services = explode("\n", $services);
foreach ($services as $s) {
$p = preg_split('/\t+/', $s);
if (count($p) < 2) continue;
$name = $p[0];
$port_proto = $p[1];
unset($p[0]);
unset($p[1]);
$p = implode("\t", $p);
$p = explode("#", $p);
$alias = $p[0];
if (isset($p[1]))
$description = $p[1];
$data['services'][$port_proto] = [
"name" => $name,
"alias" => $alias,
"description" => str_replace("# ", "", $description)
];
}
return $data;
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment