Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

Juniper Router Audit

by cleen (Pilgrim)
on Feb 27, 2001 at 12:38 UTC ( [id://61057]=sourcecode: print w/replies, xml ) Need Help??
Category: Networking Code
Author/Contact Info Mark Thomas mark@ackers.net
Description: Juniper (www.juniper.net) is a provider of high-end routing equipment, even the lower-end juniper equipment (m20) can out-preform cisco's high-end 12k GSR's.

I needed a way to easily audit my juniper configurations on a ever expanding juniper-core based network, thus this was born.

Writing the template configuration file:
Writing the template configuration is a little on the complex side, and it takes a little bit of explaining, so I put the configuration readme at juniper-audit-readme.txt

Overall this code is in beta, and I know there are many things I could do much better, and I intend on doing so, any suggestions and comments would also be great!
#!/usr/bin/perl

use strict;
use Getopt::Std;
use Net::Telnet;

my %globhash;
my %subhash;
my %args = ();

getopts("fksdu:p:i:c:h", \%args);

my @ips;
my $IP_FILE;
my $CONF_FILE;
my $debug = 0;
my $USER = '';
my $PASS = '';

$debug = 1 if $args{d};
$USER = $args{u} if $args{u};
$PASS = $args{p} if $args{p};
$IP_FILE = $args{i};
$CONF_FILE = $args{c};

if ($args{h}) {
        &showhelp;
}

die "usage: $0 [-dupicskhf] -u <user> -p <pass> -i <ipfile> -c <conf f
+ile>" unless ($IP_FILE && $CONF_FILE);

open(IPS, "$IP_FILE");
while(<IPS>) {
        chomp;
        push @ips,$_;
}
close(IPS);
foreach my $ip (@ips) {
        doaudit($ip);
        unlink "$ip.tmp" if !$args{k};
} 

sub showhelp {
print <<EOF
        -u <user>:      username of the juniper routers

        -p <pass>:      password of the juniper routers

        -i <ipfile>:    file of ip juniper ip address's

        -c <config>:    the audit configuration file (see README)

        -d :            debugging information (mucho text)

        -s :            show matched information also 
                        (Not just the not matched)

        -h :            this help screen ;)

        -k :            keep a copy of the juniper's config file in <h
+ostname>.tmp

        -f :            show stuff that was left over that was in the 
+juniper.conf
                        but not in the audit configuration file (stuff
+ that was in
                        the juniper.conf but not in the template
EOF
}

sub getconfig {
        my($host) = @_;
        my $t;
        my $CMD = 'show configuration | no-more';
        $t = new Net::Telnet (Timeout => 10);
        $t->open("$host");
        $t->login($USER, $PASS);
        my @lines = $t->cmd($CMD);
        open(TMP, ">$host.tmp");
        foreach my $line (@lines) {
                print TMP "$line";
        }
        close(TMP);
}

sub getintnames {
        # usage getintnames(IP, TYPE(OC-3));
        # returns array
        my (@cardz,$slotcounter,$slot,$cardtype,$pic,$ports);
        my($ip,$type) = @_;
        my $t;
        my $CMD = 'show chassis fpc pic-status';
        $t = new Net::Telnet (Timeout => 10);
        $t->open("$ip");
        $t->login($USER, $PASS);
        my @lines = $t->cmd($CMD);
        foreach my $fpc (@lines) {
                if ($fpc =~ /Slot (\d+?).*$/) {
                        $slotcounter = 1;
                        $slot = $1;
                        next;
                }
                if ($slotcounter eq 1) {
                        my $knownthing = 0;
                        if ($fpc =~ /PIC (\d+?)\s+(.*?)\,.*$/) {
                                $cardtype = $2;
                                $pic = $1;
                                        if($cardtype =~ /$type/) {
                                                $ports = $cardtype;
                                                $ports =~ /^(\d)x.*/;
                                                $ports = $1;
                                                $knownthing  = 1;
                                                for (my $i = 0; $i < $
+ports; $i++) {
                                                        my $card = "so
+-$slot/$pic/$i";
                                                        push @cardz,$c
+ard;
                                                }
                                        }
                        }
                }
        }
        return @cardz;
}

sub parsit {
        my (@matched,@body);
        my ($host,$where) = @_;

        open(FILE, "$host.tmp");
        my $inside = "";
        my %hash = ();  
                        
        while (<FILE>) {
                chomp;
                push @body,$_;
        }
        close(FILE); 
        while (my $line = shift @body) {
                if($line =~ /^([^\{]+)\{/) {
                        my $start = $1;
                        $start =~ s/^\s+//g;
                        $start =~ s/\s+$//g;
                        if($inside) {
                                $inside .= ":$start";

                        }
                        else {
                                $inside = $start;
                        }
                        next;
                }
                elsif ($line =~ /\}/) {
                        push @{$hash{$inside}},undef;
                        if ($inside =~ /\w+(\:\w+)+/) {
                                my @tmp = split ':', $inside;
                                $#tmp--;
                                $inside = join ':', @tmp;
                        }
                        else {
                                $inside = "";
                                next;
                        }
                }
                else {
                        $line =~ s/^\s+//g;
                        $line =~ s/\s+$//g;
                        push @{$hash{$inside}},$line;
                }
        }
        foreach my $thingy ( keys %hash ) {
                foreach my $blah (@{ $hash{$thingy} }) {
                        $blah =~ s/^\s+//g;
                        push @matched,$blah if $thingy eq $where; 
                }
        }

        return @matched;
}





sub doaudit {
        my($host) = @_;
        my (@all);
        getconfig($host);
        open(FILE, "$CONF_FILE");

        while(<FILE>) {
                chomp;
                s/^\#.*$//g; # i want comments damnit
                s/\n$//g;
                s/\s+$//g;
                s/^\s+//g;
                print "DEBUG: while(<FILE>) {: pushing $_ into \@all\n
+"
                        if $debug eq 1;
                push @all, $_;
        }
        close(FILE);
        print "\n\n" 
                if $debug eq 1;
        my $startgroup = 0;
        my $brackets = 0;
        my $globalcounter = 0;
        my $subcounter = 0;
        my $group;
        my $sub;
        my %globhash = ();
        my %subhash = ();
        foreach my $line (@all) {
                if ($line =~ /^GROUP: (\S+?)\s+\{$/) {
                        $group = $1;
                        $startgroup = 1;
                        print "DEBUG: \$line matched GROUP: $group\n"
                                if $debug eq 1;
                        next;
                }
                if ($startgroup eq 1) {
                        if ($line =~ /GLOBAL {$/) {
                                print "DEBUG: Matched GLOBAL for \"$li
+ne\"\n"
                                        if $debug eq 1;
                                $globalcounter = 1;
                                next;
                        }
                        if ($globalcounter eq 1) {
                                print "DEBUG: \$globalcounter ='s 1\n"
                                        if $debug eq 1;
                                if ($line =~ /}/) {
                                        print "DEBUG: \$line matched }
+\n"
                                                if $debug eq 1;
                                        $globalcounter = 0;
                                        next;
                                }
                                else {
                                        print "DEBUG: \$line isnt }\n"
                                                if $debug eq 1;
                                        if ($globhash{$group} eq "") {
                                                $globhash{$group} = "$
+line";
                                                print "DEBUG globhash 
+\$globhash{$group} = \"$line\"\n"
                                                        if $debug eq 1
+;
                                                        next;
                                        }
                                        else {
                                                $globhash{$group} .= "
+!:!$line";
                                                print "DEBUG globhash 
+\$globhash\{$group\} = \"!:!$line\"\n"
                                                        if $debug eq 1
+;
                                                        next;
                                        }
                                }
                        }
                        if ($line =~ /SUB: (.*?)\s+{$/) {
                                $sub = $1;
                                $subcounter = 1;
                                print "DEBUG \$line matched SUB: $line
+\n"
                                        if $debug eq 1;
                                        next;
                        }
                        if ($subcounter eq 1) {
                                print "DEBUG \$subcounter ='s 1: $line
+\n"
                                        if $debug eq 1;
                                if ($line =~ /}/) {
                                        $subcounter = 0;
                                        print "DEBUG found } so \$subc
+ounter ='s 0 now\n"
                                                if $debug eq 1;
                                        next;
                                }
                                else {
                                        my $both = "$group:$sub";
                                        if ($subhash{$both} eq "") {

                                                $subhash{$both} = "$li
+ne";
                                                print "DEBUG subhash \
+{$both\} = \"$line\"\n"
                                                        if $debug eq 1
+;
                                                next;
                                        }
                                        else {
                                                $subhash{$both} .= "!:
+!$line";
                                                print "DEBUG subhash \
+{$both\} = \"!:!$line\"\n"
                                                        if $debug eq 1
+;
                                                next;
                                        }

                                }
                        }
                }
        }
        my $type;
        print "\n$host audit\n----------------------------\n";
        print "GLOBALS\n";
        while(my($k,$v) = each %globhash) {  
                my @stuff = split(/!:!/,$v);
                my $worde;
                my @matched = parsit($host,$k);
                for (my $j=0; $j < @stuff; $j++) {
                        $stuff[$j] =~ /(.*?):(.*?)$/;
                        $type = $1;
                        $worde = $2;
                        my $knownthing = 0;
                        for (my $i=0; $i < @matched; $i++) {
                                $worde =~ s/^\s+//g;
                                $matched[$i] =~ s/^\s+//g;
                                $matched[$i] =~ s/\s+$//g;
                                if ($type eq "EXACT") {
                                        if ($worde eq $matched[$i]) {
                                                print "\tMATCHED EXACT
+: $k -- $matched[$i]\n" if $args{s};
                                                splice(@matched, $i, 1
+);
                                                splice(@stuff, $j, 1);
                                                $j--;
                                                $i--;
                                                $knownthing = 1;
                                        }
                                }
                                if ($type eq "INCLUDE") {
                                        if ($matched[$i] =~ /$worde/) 
+{
                                                print "\tMATCHED REGEX
+: $k -- $matched[$i]\n" if $args{s};
                                                splice(@matched, $i, 1
+);
                                                splice(@stuff, $j, 1);
                                                $j--;
                                                $i--;
                                                $knownthing = 1;
                                        }
                                }
                        }
                }
                if ($args{f}) {
                        print "\tNOT IN TEMPLATE:\n";
                        foreach my $crap (@matched) {
                                next if $crap =~ /^\s+$/;
                                print "\t\t$crap\n";
                        }
                }
                print "\tNOT IN HOSTS JUNIPER.CONF:\n";
                foreach my $crap (@stuff) {
                        next if $crap =~ /^\s+$/;
                        print "\t\t$crap\n";
                }

        }
        print "\nSUBS\n";
        my @stuff;
        while(my($k,$v) = each %subhash) {
                my ($intmatch) = 0;
                my ($worde);
                my ($ktmp);
                my $haveint = 0;
                @stuff = split(/!:!/,$v);
                if ($k =~ /MATCH-INTERFACES (.*?)($|:(.*))/) {
                        my $devtype = $1;
                        my $whatelse = $2;
                        my @intnames = getintnames($host,$devtype);
                        foreach my $int (@intnames) {
                                $ktmp = $k;
                                $ktmp =~ s/MATCH-INTERFACES.*?($|:)/$i
+nt:/g;
                                getsubs(\@stuff,$host,$ktmp);
                        }
                        next;
                } 
                else {
                        $ktmp = $k;
                        getsubs(\@stuff,$host,$ktmp);
                        next;
                }
                sub getsubs {
                        my ($stuff,$host,$k) = @_;
                        my @matched = parsit($host,$k);
                        for (my $j=0; $j < @$stuff; $j++) {
                                $stuff->[$j] =~ /(.*?):(.*?)$/;
                                $type = $1;
                                $worde = $2;
                                my $knownthing = 0;
                                for (my $i=0; $i < @matched; $i++) {
                                        $worde =~ s/^\s+//g;
                                        $matched[$i] =~ s/^\s+//g;
                                        $matched[$i] =~ s/\s+$//g;
                                        if ($type eq "EXACT") {
                                                if ($worde eq $matched
+[$i]) {
                                                        print "\t\t\tM
+ATCHED EXACT: $k -- $matched[$i]\n" if $args{s};
                                                        splice(@matche
+d, $i, 1);
                                                        $i--;
                                                        $knownthing = 
+1;
                                                }
                                        }
                                        if ($type eq "INCLUDE") {
                                                if ($matched[$i] =~ /$
+worde/) {
                                                        print "\t\t\tM
+ATCHED REGEX: $k -- $matched[$i]\n" if $args{s};
                                                        splice(@matche
+d, $i, 1);
                                                        $i--;
                                                        $knownthing = 
+1;
                                                } 
                                        }
                                }
                                print "\tNOT IN JUNIPER.CONF: $k -- $w
+orde\n" if !$knownthing;
                        }
                        if ($args{f}) {
                                foreach my $crap (@matched) {
                                        next if $crap =~ /^\s+$/;
                                        next if $crap eq "";
                                        print "\t\tNOT IN TEMPLATE $k 
+-- $crap\n";
                                }
                        }
                }
        }
}
Replies are listed 'Best First'.
(ichimunki) re: Juniper Router Audit
by ichimunki (Priest) on Feb 27, 2001 at 20:42 UTC
    Sadly, I can't comment on much here except for some stylistic issues. And I'll chip in these two bits, just because this is what struck me as I looked at this code and recalled both my own experiences with similar things and some reading I've been doing lately on the practice of programming. Of course, this script was probably written quickly for a specific purpose, and I don't doubt that it works well. But if it comes time to change this in a few months, or if someone else is going to adapt it or use it, the structure of the code is going to be an obstacle.

    The deeply nested "if-else" and looping structures do a lot to obscure the logic behind this program. In more than one instance there is an "if" followed by another "if", neither of which takes an "else" case. It is much clearer in these case to use an "and". Likewise, many of the condition branchings could be probably be eliminated by similarly using "and" and "or" instead of nesting.

    Following this thought, I've also got a bias towards moving functionality into smaller routines of 5-25 lines and calling those from within branches and loops as much as possible. This assists the reader in understanding the code more than it assists the machine in being fast, but this is Perl, so speed is not our primary concern. Done this way, each level of routine reads quickly (like an outline) and I don't need to keep mental track of any more details than necessary. It may eventually mean less work for the programmer as well, since the functions will be easier to test in isolation... and comments become almost unneeded-- the code becomes self-documenting.

    Again, these are just my thoughts, and I don't want to seem overly critical. I find myself programming this way all the time for shorter scripts, but it seems like taking a few minutes to do some higher level planning, even writing pseudocode for the main body of the program, can often save some real headaches when the script starts to get bigger than the screen (and more likely, starts to get bigger than my brain can manage to keep track of).
Re: Juniper Router Audit
by danger (Priest) on Feb 27, 2001 at 21:38 UTC

    Like ichimunki, I won't really say much beyond style, and in particular, deep nested conditionals.

    As one example of reducing the complexity of your nesting (but different than ichimunki's suggestion of using logical operators), here is an alternate version of the foreach loop found within the getintnames() routine -- your loop was 26 lines long and 4 levels deep (with a couple of unnecessary indentations added as well).

    foreach my $fpc (@lines) { if ($fpc =~ /Slot (\d+?).*$/) { $slotcounter = 1; $slot = $1; next; } next unless $slotcounter eq 1; next unless ($cardtype, $pic) = $fpc =~ /PIC (\d+?)\s+(.*?)\,.*$/; next unless $cardtype =~ /$type/; ($ports = $cardtype) =~ s/^(\d)x.*/$1/; push @cardz, "so-$slot/$pic/$_" for 0 .. $ports - 1; }

    Note, I removed your $knownthing variable altogether as you didn't really do anything with it. Also note, I can't really test the above for errors beyond syntax, so for that reason just consider it model rather than a plug-in alternative.

    And a final note: my point was not to reduce the size of the loop, that was only a side-effect of reducing the nesting. However, some people frown at multiple points of escaping a loop iteration (though in this case the nested ifs amount to the same thing), so tmtowtdi and ymmv.

      ah yes, that took down some of the complexity of the code quite a bit, and I will post the revision tommorow once I am in work again. Another thing I noticed is the fact that each time it finds a MATCH INTERFACE type, it goes and gets a relist of all of that type of card, which extends the overhead of the program with time and processing. I think the $knownthing was supposed to be part of that future logic, but it didnt get into a final revision =\ Thanks for your input it was greatly needed!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: sourcecode [id://61057]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (6)
As of 2024-03-28 12:12 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found