?

Log in

No account? Create an account

Previous Entry | Next Entry

Perl Pinata

Just so all you real programmers out there can have a good laugh, this is the script I hacked together this evening to go hunting dead IPs. Our static IP assignment database is kind of like an inverse roach motel: addresses go out, but they never come back in! So, the plan is to repeatedly sic this thing on several subnets (probably through cron). The back-end is a two-table MySQL database:
mysql> describe tblRanges;
+-----------+--------------+------+-----+---------+-------+
| Field     | Type         | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| RangeName | varchar(100) |      | PRI |         |       |
| StartAddr | varchar(15)  |      |     |         |       |
| StopAddr  | varchar(15)  |      |     |         |       |
+-----------+--------------+------+-----+---------+-------+

mysql> describe tblIPdata;
+-----------+------------------+------+-----+---------+-------+
| Field     | Type             | Null | Key | Default | Extra |
+-----------+------------------+------+-----+---------+-------+
| IP        | varchar(15)      |      | PRI |         |       |
| HitCount  | int(10) unsigned |      |     | 0       |       |
| PassCount | int(10) unsigned |      |     | 0       |       |
| LastHit   | int(10) unsigned |      |     | 0       |       |
| PadIP     | varchar(15)      |      |     |         |       |
+-----------+------------------+------+-----+---------+-------+

The first table has the IP subnet ranges I want to probe, and the second holds the results of the probing -- one record per IP address.

I figure that if I run this thing a couple of times a day for two weeks, any address with a hit count of zero is fair game for purging from the assignment database.

Comments and suggestions on my code are always welcome.

#!/usr/bin/perl -w
#
#  Script to poll IP addresses and record dead/alive status in a MySQL database
#
use DBI;                          # Perl modules to pull in
use Time::Local;
use Net::Ping;

                                  # Global statics
$version  = "0.1 b4";
$selfname = "IP Shark";
$dbname   = "xxx";
$dbuser   = "xxx";
$dbpasswd = "xxx";
$verbose  = 0;

if (defined($param = $ARGV[0])) { # We only take one param... ("verbose on")
  if ($param eq "v" || $param eq "-v" || $param eq "v1" || $param eq "-v1") {
    $verbose = 1
  } elsif ($param eq "v2" || $param eq "-v2") {
    $verbose = 2
  } elsif ($param eq "v3" || $param eq "-v3") {
    $verbose = 3
  }
}
if ($verbose > 0) {
  print "\n***** BEGIN $selfname v$version *****\n";
}
                                  # Open a db handle for working with IP ranges
open_db_handle($dbname,$dbuser,$dbpasswd);

$sthIPrange = $dbh->prepare("SELECT * FROM tblRanges") or 
  fatal_err("Cannot prepare: " . $sthIPrange->errstr());
$sthIPrange->execute() or 
  fatal_err("Cannot execute: " . $sthIPrange->errstr());

while (@f = $sthIPrange->fetchrow_array()) {
  if ($verbose > 0) {
    print " o Range: $f[0]\n";
  }
  poll_hosts($f[1],$f[2]);
}

$dbh->disconnect;                 # Close database connection
if ($verbose > 1) {
  print " o Database connection now closed\n";
}
if ($verbose > 0) {
  print "****** END $selfname v$version ******\n\n";
}

######################################################################
#
# Subroutines below this point
#
######################################################################


sub poll_hosts {

  my $startip = shift;
  my $endip = shift;
  my $i = 0;  my $j = 0;  my $k = 0;  my $l = 0;
  my $istop =0;
  my @startip = ();
  my @endip = ();

  if ($verbose > 0) {
    print "   - Parsing IP addresses\n"
  }

  @startip = split /\./, $startip;
  @endip = split /\./, $endip;

  if ($verbose > 1) {
    print "   - startip = $startip[0].$startip[1].$startip[2].$startip[3]\n";
    print "   - endip   = $endip[0].$endip[1].$endip[2].$endip[3]\n";
    print "   - Starting prowl\n";
    print "   - Setting starting octets\n"
  }

  $ast = $startip[0];             # load individual octets with starting addr.
  $bst = $startip[1];
  $cst = $startip[2];
  $dst = $startip[3];

  if ($verbose > 1) {
    print "   - Setting ending octets\n"
  }
  $aend = $endip[0];              # load individual octets with ending addr.
  $bend = $endip[1];
  $cend = $endip[2];
  $dend = $endip[3];

  $i = $ast;                      # Init counters with starting values
  $j = $bst;
  $k = $cst;
  $l = $dst;

  $istop = $ast + 1;

  if ($verbose > 0) {
    print "   - Starting prowl loop\n"
  }
  while ($i < $istop) {
    while ($j < 256) {
      if ($k == 256) {
        $k = 0;
        $j = $j + 1;
      }
      while ($k < 256) {
        if ($l == 256) {
          $l = 0;
          $k = $k + 1;
        }
        while ($l < 256) {
          $hostaddr = "$i.$j.$k.$l";
          if ($verbose > 1) {
            print "     = Trying $hostaddr"
          }
          # ***** BEGIN FUNKY OOP CODE *****
          $p = Net::Ping->new("icmp");
          $state = 0;         
          $state = 1 unless $p->ping($hostaddr, 2);    
          $p->close();
          # ****** END FUNKY OOP CODE ******
          if ($state == 0) {
            if ($verbose > 1) {
              print " ...found host!\n"
            }
            stow_data($hostaddr,1)
          } else {
            if ($verbose > 1) {
              print "\n"
            }
            stow_data($hostaddr,0)
          }
          if ($l == $dend) {
            if ($k == $cend) {
              if ($j == $bend) {
                if ($i == $aend) {
                  $l = 256;
                }
              }
            }
          }
          $l = $l + 1;
        }
        if ($k == $cend) {
          if ($j == $bend) {
            if ($i == $aend) {
              $k = 256;
            }
          }
        }
      }
      if ($j == $bend) {
        if ($i == $aend) {
          $j = 256;
        }
      }
    }
    if ($j == 256) {
      $j = 0;
      $i = $i + 1;
    }
  }

} # End poll_hosts



sub stow_data {                   # Records success/fail of ping in db

  my $hostaddr = shift;
  my $hits = shift;
  my $passes = 0;
  my $now = time;
  my $sql = "";
  my @f = ();
  my $s1 = "";
  my $s2 = "";
  my $padip = pad_ip($hostaddr);

  if ($hits == 0) {               # If no hit, store a zero time - otherwise
    $now = 0                      # fall through and store the current time
  }

  if ($verbose > 2) {
    print "       --> Checking host data\n"
  }

  $sql = "SELECT * FROM tblIPdata WHERE IP = \"$hostaddr\"";
  $sthHost = $dbh->prepare($sql) or fatal_err("Cannot prepare: " . $sthHost->errstr());
  $sthHost->execute() or fatal_err("Cannot execute: " . $sthHost->errstr());

  @f = $sthHost->fetchrow_array();

  if (@f == 0) {
    if ($verbose > 2) {
      print "       --> Inserting!\n"
    }
    $sql = "INSERT INTO tblIPdata VALUES (\"$hostaddr\",$hits,1,$now,\"$padip\")";
  } else {
    if ($hostaddr ne $f[0]) {
      fatal_err("SQL oops in fetching host record: $hostaddr!")
    }
    if ($verbose > 2) {
      print "       --> Updating!\n"
    }
    $hits = $f[1] + $hits;
    $passes = $f[2] + 1;
    $s1 = "UPDATE tblIPdata SET HitCount = $hits, PassCount = $passes, ";
    $s2 = "LastHit = $now WHERE IP = \"$hostaddr\"";
    $sql = $s1 . $s2
  }

  if ($verbose > 2) {
    print "       --> SQL = \"$sql\"\n"
  }

  $dbh->do( "$sql") or fatal_err($dbh->errstr())

} # End stow_data



sub pad_ip {                      # Takes a "normal" IP address and returns
                                  # one padded with zeros
  my $hostaddr = shift;
  my @f = split(/\./,$hostaddr);
  my $k = 0;
  my $pad = "";

  while ($k < 4) {
    if ($f[$k] > 9 and $f[$k] < 100) {
      $f[$k] = "0" . $f[$k]
    } elsif ($f[$k] < 10) {
      $f[$k] = "00" . $f[$k]
    }
    $k++
  }

  $pad = $f[0] . "." . $f[1] . "." . $f[2] . "." .$f[3];

  return $pad  

} # End pad_ip()



sub open_db_handle {              # Opens connection to database

                                  # Describe database server and db name
  my $host="localhost";
  my $port="3306";
  my $db = shift;
  my $userid = shift;
  my $passwd = shift;   
                                  # Composite this into a connection string
  $connectionInfo="DBI:mysql:database=$db;$host:$port";

                                  # Create a database handle (dbh) by invoking
                                  # the connect method of the DBI object
  $dbh = DBI->connect($connectionInfo,$userid,$passwd)
    or fatal_err("Cannot connect: $DBI::errstr");

  if ($verbose > 1) {
    print " o Database connection ($db on $host) now open\n";
  }

} # End open_db_handle()



sub fatal_err {                   # Spits out error info & exits

  my $err = shift;

  print "FATAL: $err\n";
  exit;

} # End fatal_err()


# End

Comments

( 2 comments — Leave a comment )
also_huey
Nov. 29th, 2005 02:52 am (UTC)
Did you write that for fun?

I'm thinking that it'd be a lot simpler to write a little script to process Ethereal or snort logs, leave the sniffer on for a couple weeks, and then once you've separated all of your IP space into "things you've seen" and "things you haven't seen", probe everything on the list of things you haven't seen, to make sure you aren't missing anything.
netcurmudgeon
Nov. 29th, 2005 11:17 am (UTC)
Did you write that for fun?

Kind of. Though it may be better described as 'enjoying your work'. :-)

The rub with the Ethereal idea is that I'd have to move the sniffer from site-to-site to cover everything; I don't have any sort of distributed sniffer system (one of the types of thing the Federal program that has paid for 89% of this network won't buy is any sort of NMS hardware or software). Ultimately, that would mean traversing about sixty sites within the city. I'd much rather rain pings down on them from a central location!
( 2 comments — Leave a comment )

Latest Month

January 2017
S M T W T F S
1234567
891011121314
15161718192021
22232425262728
293031    

Tags

Page Summary

Powered by LiveJournal.com
Designed by Lilia Ahner