netcurmudgeon (netcurmudgeon) wrote,
netcurmudgeon
netcurmudgeon

  • Mood:
  • Music:

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
Subscribe
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 2 comments