diff --git a/lib/DDG/Goodie/SubnetCalc.pm b/lib/DDG/Goodie/SubnetCalc.pm new file mode 100755 index 000000000..114755715 --- /dev/null +++ b/lib/DDG/Goodie/SubnetCalc.pm @@ -0,0 +1,91 @@ +#!/bin/env perl +package DDG::Goodie::SubnetCalc; +# ABSTRACT: Calculate IP ranges & Subnet Information from a host & CIDR or netmask +use strict; +use warnings; + +use DDG::Goodie; + +# TODO: This (sh|c)ould be re-written to be more precise +triggers query => qr`^([0-9]{1,3}\.){3}([0-9]{1,3})[\s/](([1-3]?[0-9])|(([0-9]{1,3}\.){3}([0-9]{1,3})))$`; + +zci answer_type => "subnet_info"; +zci is_cached => 1; + +attribution github => ['https://github.com/mintsoft', 'mintsoft']; + +primary_example_queries '10.1.2.3/25'; +secondary_example_queries '46.51.197.88/255.255.254.0', '176.34.131.233/32'; + +category 'computing_tools'; +topics 'sysadmin'; + +handle query => sub { + sub ip2str($) { + my ($ip) = @_; + return sprintf "%u.%u.%u.%u", ($ip>>24&0xff),($ip>>16&0xff),($ip>>8&0xff),($ip&0xff); + } + sub array2long(@) { + return (int($_[0])<<24) + (int($_[1])<<16) + (int($_[2])<<8) + (int($_[3])); + } + + my ($input) = @_; + my ($address,$cidr) = split qr`[\s/]`, $input; + + my @octlets = split /\./,$address; + for (@octlets) { + return if int($_)>255; + } + + my $addressRaw = array2long(@octlets); + my $cidrHostMaskRaw = 0; + my $cidrRaw = 0; + + # Convert CIDR & Subnets + if ($cidr =~ /^[0-9]+$/) { + return if ($cidr>32 || $cidr<1); + $cidrHostMaskRaw = 2**(32-$cidr)-1; + $cidrRaw = 0xffffffff ^ $cidrHostMaskRaw; + } + elsif ($cidr =~ /([0-9]{1,3}\.){3}([0-9]{1,3})/) { + my @cidr_octlets = split /\./,$cidr; + for(@cidr_octlets) { + return if int($_)>255; + } + + $cidrRaw = array2long(@cidr_octlets); + my $cidrBinary = sprintf("%b",$cidrRaw); + return unless ($cidrBinary =~ /^1+0*$/); + $cidrHostMaskRaw = 0xffffffff ^ $cidrRaw; + $cidr = ($cidrBinary =~ tr/1//); #count number of 1's + } + else { + return; + } + + my $host = $addressRaw & $cidrHostMaskRaw; + my $network = $addressRaw & $cidrRaw; + + my $start = $network+1; + my $end = $network+($cidrHostMaskRaw-1); + my $broadcast = $network+$cidrHostMaskRaw; + my $hostCount = 1+$end-$start; + + my $whichSpecified = "Host #".($host); + $whichSpecified = "Network" if ($host==0); + $whichSpecified = "Broadcast" if ($host==$hostCount+1); + $whichSpecified = "Point-To-Point (".ip2str($end).", ".ip2str($start).")" if ($cidr==31); + $whichSpecified = "Host Only" if ($cidr==32); + + my $outputStr = "Network: ".ip2str($network)."/$cidr,". + " Netmask: ".ip2str($cidrRaw).",". + " $whichSpecified specified"; + $outputStr .= ", Host Address Range: ".ip2str($start)."-".ip2str($end).",". + " $hostCount Usable Addresses,". + " Broadcast: ".ip2str($broadcast) + unless($cidr > 30); + return $outputStr; + +}; + +1; \ No newline at end of file diff --git a/t/SubnetCalc.t b/t/SubnetCalc.t new file mode 100644 index 000000000..aca1cf454 --- /dev/null +++ b/t/SubnetCalc.t @@ -0,0 +1,28 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use Test::More; +use DDG::Test::Goodie; + +zci answer_type => 'subnet_info'; +zci is_cached => 1; + +ddg_goodie_test( + [ + # This is the name of the goodie that will be loaded to test. + 'DDG::Goodie::SubnetCalc' + ], + "10.0.0.0/24" => test_zci("Network: 10.0.0.0/24, Netmask: 255.255.255.0, Network specified, Host Address Range: 10.0.0.1-10.0.0.254, 254 Usable Addresses, Broadcast: 10.0.0.255"), + "192.168.0.0/24" => test_zci("Network: 192.168.0.0/24, Netmask: 255.255.255.0, Network specified, Host Address Range: 192.168.0.1-192.168.0.254, 254 Usable Addresses, Broadcast: 192.168.0.255"), + "8.8.8.8 255.255.224.0" => test_zci("Network: 8.8.0.0/19, Netmask: 255.255.224.0, Host #2056 specified, Host Address Range: 8.8.0.1-8.8.31.254, 8190 Usable Addresses, Broadcast: 8.8.31.255"), + "4.4.4.255/24" => test_zci("Network: 4.4.4.0/24, Netmask: 255.255.255.0, Broadcast specified, Host Address Range: 4.4.4.1-4.4.4.254, 254 Usable Addresses, Broadcast: 4.4.4.255"), + "42.24.42.24/17" => test_zci("Network: 42.24.0.0/17, Netmask: 255.255.128.0, Host #10776 specified, Host Address Range: 42.24.0.1-42.24.127.254, 32766 Usable Addresses, Broadcast: 42.24.127.255"), + "172.32.1.3/31" => test_zci("Network: 172.32.1.2/31, Netmask: 255.255.255.254, Point-To-Point (172.32.1.2, 172.32.1.3) specified"), + "10.255.255.16/32" => test_zci("Network: 10.255.255.16/32, Netmask: 255.255.255.255, Host Only specified"), + "10.1.2.3/255.0.0.0" => test_zci("Network: 10.0.0.0/8, Netmask: 255.0.0.0, Host #66051 specified, Host Address Range: 10.0.0.1-10.255.255.254, 16777214 Usable Addresses, Broadcast: 10.255.255.255"), + "192.168.16.1 23" => test_zci("Network: 192.168.16.0/23, Netmask: 255.255.254.0, Host #1 specified, Host Address Range: 192.168.16.1-192.168.17.254, 510 Usable Addresses, Broadcast: 192.168.17.255") +); + +done_testing; \ No newline at end of file