#!/usr/bin/perl
# ipdb/cgi-bin/consistency-check.pl
# Does full check to see if the data in the db is consistent and complete.
###
# SVN revision info
# $Date$
# SVN revision $Rev$
# Last update by $Author$
###

use DBI;
use IPDB qw(:ALL);
use NetAddr::IP;

$dbh = connectDB;

# Schlep up the masters
$sth = $dbh->prepare("select * from masterblocks order by cidr");
$sth->execute;
for ($i=0; @data = $sth->fetchrow_array; $i++) {
  $masterblocks[$i] = new NetAddr::IP $data[0];
}

print "First check:  All blocks must be within one of the master blocks\n";

# First check - make sure ALL routes and allocated blocks are part
# of one of the master blocks.
print "Checking routed blocks: ";
$sth = $dbh->prepare("select cidr from routed");
# union select cidr from allocations union select cidr from freeblocks");
$sth->execute;
ROUTED: while (@data = $sth->fetchrow_array) {
  $cidr = new NetAddr::IP $data[0];
  foreach $master (@masterblocks) {
    if ($master->contains($cidr)) { next ROUTED; }
  }
  print "$cidr not mastered\n";
}
print " done.\n";

# Next test:  All allocations must be part of a master.
print "Checking allocations: ";
$sth = $dbh->prepare("select cidr from allocations");
$sth->execute;
ALLOCATED: while (@data = $sth->fetchrow_array) {
  $cidr = new NetAddr::IP $data[0];
  foreach $master (@masterblocks) {
    if ($master->contains($cidr)) { next ALLOCATED; }
  }
  print "$cidr not mastered\n";
}
print " done.\n";

# Next:  free blocks
print "Checking freeblocks: ";
$sth = $dbh->prepare("select cidr from freeblocks");
$sth->execute;
FREEBLOCK: while (@data = $sth->fetchrow_array) {
  $cidr = new NetAddr::IP $data[0];
  foreach $master (@masterblocks) {
    if ($master->contains($cidr)) { next FREEBLOCK; }
  }
  print "$cidr not mastered\n";
}
print " done.\n";

print "Done checking master containment.\n\nChecking pool containment:\n";

$sth = $dbh->prepare("select pool,ip from poolips order by ip");
$sth->execute;
while (@data = $sth->fetchrow_array) {
  $pool = new NetAddr::IP $data[0];
  $ip = new NetAddr::IP $data[1];
  print "IP $ip listed with incorrect pool $pool\n"
    if !$pool->contains($ip);
}
$sth = $dbh->prepare("select distinct pool from poolips order by pool");
$sth->execute;
while (@data = $sth->fetchrow_array) {
  $sth2 = $dbh->prepare("select cidr from allocations where cidr='$data[0]'");
  $sth2->execute;
  print "Pool $data[0] does not exist in allocations table\n"
    if (($sth2->fetchrow_array)[0] eq '');
}

print "Done checking pool containment.\n\nChecking block-alignment consistency:\n";

# Block alignment consistency:  All allocated+free blocks within a master
# must NOT overlap, and they must show no gaps.
# eg, if we have blocks:
#  master is 192.168.2.0/24
#  allocated 192.168.2.0/29, 192.168.2.8/29, 192.168.2.64/26
#  free 192.168.2.16/28, 192.168.2.32/27, 192.168.2.128/25
# then we're OK, but if any one of the allocated or free blocks is missing,
# something b0rked.

# (select cidr from allocations where cidr <<= '$master') union
#  (select cidr from freeblocks where cidr <<= '$master')
#  order by cidr

foreach $master (@masterblocks) {
  print "Master $master:\n";
  $prev = $master;
  $sth = $dbh->prepare("(select network(cidr) as net, broadcast(cidr) as bcast ".
	" from allocations where cidr <<= '$master') union ".
	"(select network(cidr) as net, broadcast(cidr) as bcast ".
	"from freeblocks where cidr <<= '$master') order by net");
  $sth->execute;

  while (@data = $sth->fetchrow_array) {
    $cur = new NetAddr::IP $data[0];

    if ($master->numeric == $prev->numeric) {
      # check if cur starts with master
      if ($cur->numeric > $prev->numeric) {
        print " Gap from start of master $master to first block $cur\n";
      } elsif ($cur->numeric < $prev->numeric) {
        print " BIG problem!  Current block $cur begins before master $master!\n";
      }
    } else {
      if ($cur->numeric < ($prev->numeric + 1)) {
        print " Block ".$prev->network." overlaps block $cur\n";
      } elsif ($cur->numeric > ($prev->numeric + 1)) {
        print " Gap between end of block ".$prev->network." and block $cur\n";
      }
    }

    $prev = $cur;
    $prev--;

  } # while (@data...)

  $cur--;
  $master--;
  if ($cur->numeric ne $master->numeric) {
    print " Gap from $cur to end of master at $master\n";
  }
  print "done $master.\n";
}
