Looking Glasses

Public looking glasses are web interfaces to the world of BGP route server and peering routers at NAPs. They consist of a web front end and query router interfaces via remote shell (rsh), Telnet, secure shell (ssh), or Simple Network Management Protocol (SNMP) embedded in PHP or Perl scripts. Relevant Perl modules are as listed in http://www.cpan.org:

  • Net-Telnet

  • Net-Telnet-Cisco

  • Cisco-Conf

  • Cisco-CopyConfig

  • Cisco-Reconfig

Looking-glass scripts present the output of CLI commands that are relayed to these nodes. Looking glasses historically were used for BGP queries; however, they can be used for every command a CLI provides. They are useful tools for operations and engineering departments and integrate well into web-based network management tools.

The following subsections present looking-glass setups for Cisco IOS devices and Zebra. It is strongly recommended to train operations staff on the use of looking glasses for debugging and troubleshooting BGP problems.

Cisco IOS Configuration

This configuration requires that the user wwwdata exists on the host 192.168.1.1 and restricts access to the Cisco IOS CLI via rsh to this particular host/user combination (see Example 10-28). For additional security, you could kerberize rsh access. Strong security is recommended on 192.168.1.1. For the Cisco IOS rsh configuration to work, the user wwwdata has to execute the query.

Example 10-28. Cisco IOS rsh Configuration

...

username wwwdata access-class 9 nopassword

ip rcmd rsh-enable

ip rcmd remote-host wwwdata 192.168.1.1 wwwdata

access-list 99 permit 192.168.1.1

...


The Looking Glass CGI Script and HTML Code

You need to place the script, Example 10-29, in an executable script directory of your web server, and place the code in Example 10-30 in the document tree. Figure 10-19 presents the web interface of this looking glass.

Example 10-29. Looking-Glass Source Code (Based on DIGEX Script)

#!/usr/local/bin/perl



######################################################

#

# Looking Glass Based on DIGEX Code

#

######################################################



## The script will now cache the results as simple files in the $cache_dir,

## named after the type of query (queries must, of course, be one word no

## spaces).  Modify $max_time_diff to set the lifetime for each cache.

## Currently, cache lifetime is the same for all queries.



###### Set these appropriately: ######################



  # for most web servers, cache_dir must be writable by uid nobody

  #

  $cache_dir = "/var/www/lglass/cache" ;

  #

  # when to display cache?  max time difference (in seconds)

  #

  $max_time_diff = "600" ;

  $max_time_diff = "60" ;



###### Should be okay from here on ###################



# grab CGI data

&cgi_receive ;

&cgi_decode ;



$ROUTER = $FORM{router};



#

# first, make sure they typed in something,

# for bgp make sure addr is specified

#



if (!$FORM{query} | | ($FORM{query} eq "bgp" && !$FORM{addr}))

{ $results[0] = "A full BGP table dump would cause too much stress on the router and this

graphics/ccc.gif machine. <p>Please step back and input an address.\n" ; &print_results ; }

#{ $results[0] = "You did not supply all of the requested information. \n<p>Please step

graphics/ccc.gif back and try again.\n" ; &print_results ; }



#

# handle ip addr

#



#if (($FORM{query} eq "ping" | | $FORM{query} eq "trace")

#       && ($FORM{addr} !~ /^\d+\.\d+\.\d+\.\d+$/) )

#{ $results[0] = "The IP address \"$FORM{addr}\" is not valid.\n

#<p>

#Please step back and try again.\n" ; &print_results ; }



if ($FORM{query} eq "bgp flap-statistics") {



  if (!$FORM{addr}) {  # cache requests with no addr



        $file = "$cache_dir/$FORM{query}" ;



        if (-e $file)

        { # see if cache exists

               @stat = stat($file);

               $ftime = $stat[9] ;

               $dtime = time - $stat[9] ;



               if ($dtime <= $max_time_diff)

               { # see if we are within cache time

                      open(CACHE,"$file") ;

                      while (<CACHE>) { $results[$#results + 1] = $_ ; }

                      close CACHE ;

                      $seconds = $dtime ;

                      &print_results ;

               }

       }



       # else, execute command



       @results = &DoRsh($ROUTER,"sh ip $FORM{query}");



       open(CACHE,">$file") | | die "couldn't create file $file" ;

       foreach $n (0 .. $#results)

         {

           print CACHE $results[$n] ;

         }

       close CACHE ;

       &print_results ;

  }



  else  { # if addr, execute command

       @results = &DoRsh($ROUTER,"sh ip $FORM{query} $FORM{addr}");

       &print_results ;

  }



} # end flap-statistics



elsif ($FORM{query} eq "bgp dampened-paths")  {

        @results = &DoRsh($ROUTER,"sh ip bgp dampened-paths");

        &print_results ;

}



elsif ($FORM{query} eq "trace")  {

       @results = &DoRsh($ROUTER,"trace $FORM{addr}");

       &print_results ;

}



elsif ($FORM{query} eq "ping")  {

       @results = &DoRsh($ROUTER,"ping $FORM{addr}");

       &print_results ;

}



elsif ($FORM{query} eq "environmental all")  {

       @results = &DoRsh($ROUTER,"sh enviro all");

       &print_results ;

}



elsif ($FORM{query} eq "environmental table")  {

        @results = &DoRsh($ROUTER,"sh enviro table");

        &print_results ;

}



elsif ($FORM{query} eq "proc mem")  {

        @results = &DoRsh($ROUTER,"sh proc mem");

        &print_results ;

}



elsif ($FORM{query} eq "proc cpu")  {

        @results = &DoRsh($ROUTER,"sh proc cpu");

        &print_results ;

}



elsif ($FORM{query} eq "buffers")  {

        @results = &DoRsh($ROUTER,"sh buffers");

        &print_results ;

}



elsif ($FORM{query} eq "version")  {

        @results = &DoRsh($ROUTER,"sh version");

        &print_results ;

}



elsif ($FORM{query} eq "hardware")  {

        @results = &DoRsh($ROUTER,"sh hard");

        &print_results ;

}



elsif ($FORM{query} eq "bgp community") {

        if ($FORM{commu})

        { @results = &DoRsh($ROUTER,"sh ip bgp community $FORM{commu}"); }

        # don't let them execute without a community identifier.

        # This should be taken care of above, but this is a double-check.

        else { $results[0] = "Please specify a community identifier." ; }

        &print_results ;

}



elsif ($FORM{query} eq "bgp regexpr")  {

       @results = &DoRsh($ROUTER,"sh ip bgp regexpr");

       &print_results ;

}



elsif ($FORM{query} eq "bgp") {

       if ($FORM{addr})

        { @results = &DoRsh($ROUTER,"sh ip bgp $FORM{addr}"); }

       # don't let them execute without a host name.

       # This should be taken care of above, but this is a double-check.

       else { $results[0] = "Please specify a host." ; }

        &print_results ;

}



elsif ($FORM{query} eq "bgp regexp") {

        if ($FORM{addr})

        { @results = &DoRsh($ROUTER,"sh ip bgp regexp $FORM{addr}"); }

        # don't let them execute without a host name or regexp.

        # This should be taken care of above, but this is a double-check.

        else { $results[0] = "Please specify a host or regexp." ; }

        &print_results ;

}



elsif ($FORM{query} eq "summary") {

       @results = &DoRsh($ROUTER,"sh ip bgp summary");

       $FORM{addr} = "" ;

        &print_results ;

}



exit ;









sub print_results {



#if ($ENV{'SERVER_NAME'}) { #i.e. if we're in CGI land



print <<END ;

Content-type: text/html



<html>

<HEAD>

<title>IKTech Looking Glass Results</title>

</HEAD>



<BODY>

<center>

<img src="http://192.168.1.1/lglass/iktech.jpg" align="center">

</center>

<center>

<h2>IKTech Looking Glass Results</h2>

</center>





<p>

<hr size=2 width=85%>

<p>



<!--- start page content --->



<center>

<b>Query:</b> $FORM{query}

<br>

END



if ($FORM{addr}) { print "<b>Addr:</b> $FORM{addr}\n"; }

print <<END ;

<!--$cached-->

</center>

<p>



<pre>



END



if ($seconds) { print "<b>From cache (number of seconds old (max 600)):</b> $seconds\n" ; }



foreach $n (0 .. $#results)

 { print $results[$n] ; }



print <<END ;

</pre>



<!--- end page content --->



</body>



<p>

<hr size=2 width=85%>

<p>





</body>



<tail>

<center>

<i>

  Please e-mail questions or comments to Gernot Schmied,

 <a href=mailto:gernot.schmied\@iktech.net>gernot.schmied\@iktech.net</a>

</i>

</center>

</tail>

</html DIGEX_LAST_MODIFIED="">

END



#}

#else { print "$results\n"; }



#  date, host name, query, addr



$date = `/bin/date` ;

chop $date ;

open(LOG,">>$cache_dir/log") ;

($ENV{REMOTE_HOST}) && ( print LOG "$ENV{'REMOTE_HOST'} ") ;

($ENV{REMOTE_ADDR}) && ( print LOG "$ENV{'REMOTE_ADDR'} ")  ;

print LOG "- - [$date] $FORM{query} $FORM{addr}\n"  ;

close LOG ;



exit;



}  #end sub print_results





######## The rest is borrowed from NCSA WebMonitor "mail" code



sub cgi_receive {

    if ($ENV{'REQUEST_METHOD'} eq "POST") {

        read(STDIN, $incoming, $ENV{'CONTENT_LENGTH'});

    }

    else {

        $incoming = $ENV{'QUERY_STRING'};

    }

}







sub cgi_decode {

    @pairs = split(/&/, $incoming);



    foreach (@pairs) {

        ($name, $value) = split(/=/, $_);



        $name  =~ tr/+/ /;

        $value =~ tr/+/ /;

        $name  =~ s/%([A-F0-9][A-F0-9])/pack("C", hex($1))/gie;

        $value =~ s/%([A-F0-9][A-F0-9])/pack("C", hex($1))/gie;



        #### Strip out semicolons unless for special character

        $value =~ s/;/$$/g;

        $value =~ s/&(\S{1,6})$$/&\1;/g;

        $value =~ s/$$/ /g;



        $value =~ s/\|/ /g;

        $value =~ s/^!/ /g; ## Allow exclamation points in sentences



        #### Skip blank text entry fields

        next if ($value eq "");

        #### Check for "assign-dynamic" field names

        #### Mainly for on-the-fly input names, especially check boxes

        if ($name =~ /^assign-dynamic/) {

            $name = $value;

            $value = "on";

        }



        #### Allow for multiple values of a single name

        $FORM{$name} .= ", " if ($FORM{$name});



        $FORM{$name} .= $value;

        push (@fields, $name) unless ($name eq $fields[$#fields]);

    }

}



sub DoRsh

{

       local ($router,$cmd)=@_;

       return &DoCmd("/usr/bin/rsh","$router $cmd");

}



sub DoCmd

{

       local ($program,$cmd)=@_;



       local (@cmd)=($program);

       push(@cmd,split(/\s+/,$cmd));

       local (@results);



       return @results;

}# add error processing as above

       local($sleep_count) = (0);

       local ($pid);

       do {

              $pid = open(KID_TO_READ, "-|");

              unless (defined $pid) {

                     warn "cannot fork: $!";

                     die "bailing out" if $sleep_count++ > 6;

                     sleep 10;

              }

         until defined $pid;



       if ($pid) {   # parent

              while (<KID_TO_READ>) {

                     # do something interesting

                     push(@results,$_);

              }

              close(KID_TO_READ) | | warn "kid exited $?";

       } else {      # child

              ($EUID, $EGID) = ($UID, $GID); # suid only

              exec(@cmd) | | die "can't exec program '$cmd[0]': $!";

              # NOTREACHED

       }


Figure 10-19. Web Interface to This Script

[View full size image]
graphics/10fig19.gif


Example 10-30. Looking-Glass HTML Code

<html>

<HEAD>

<title>IKTech Looking Glass</title>

</HEAD>



<BODY>



<center>

<img src="IKTech.gif" width="210" height="209">

<h2>IKTech Looking Glass</h2>

</center>



<p>

<hr size=2 width=85%>

<p>



<!--- start page content --->



<form method="POST" action="http://192.168.1.1/lglass/lg.cgi">



<b>Router </b>

<select name="router" size="1">

<option selected>ganymed</option>

<option>callisto</option>

<option>castor</option>

<option>pollux</option>

<option>europa</option>

<option>scar</option>

<option>laurel</option>

<option>hardy</option>

<option>chaplin</option>

</select>



<dl>

<dt> <b>Query</b>

   <dd>

   <input type="radio" name="query" value="bgp"> bgp

   <dd>

   <input type="radio" name="query" value="bgp community"> bgp community

   <dd>

   <input type="radio" name="query" value="summary"> bgp summary

   <dd>

   <input type="radio" name="query" value="bgp dampened-paths"> bgp dampened-paths

   <dd>

   <input type="radio" name="query" value="bgp flap-statistics"> bgp flap-statistics

   <dd>

   <input type="radio" name="query" value="bgp regexp"> bgp regexp

   <dd>

   <input type="radio" name="query" value="ping"> ping

   <dd>

   <input type="radio" name="query" value="trace"> trace

   <dd>

   <input type="radio" name="query" value="version"> sh version

   <dd>

   <input type="radio" name="query" value="hardware"> sh hardware

   <dd>

   <input type="radio" name="query" value="proc mem"> sh processes memory

   <dd>

   <input type="radio" name="query" value="buffers"> sh buffers

   <dd>

   <input type="radio" name="query" value="proc cpu"> sh processes cpu

   <dd>

   <input type="radio" name="query" value="environmental all"> sh environmental all

   <dd>

   <input type="radio" name="query" value="environmental table"> sh environmental table

   <dd>

</dd>



<p>

<dt> <b>Address or regular expression </b>  <input name="addr" value="_65000$" size=40>

<a href=http://www.cisco.com/en/US/products/hw/switches/ps718

graphics/ccc.gif/products_command_reference_chapter09186a008009166c.html></br> CISCO Regexp Reference </a>

</dl>



<b>BGP community identifier </b>

<select name="commu" size="1">

<option selected>65000:090</option>

<option>65000:100</option>

<option>65000:110</option>

<option>65000:120</option>

</select>

<a href=http://192.168.1.1/community.html> IKTech Community Overview </a>

<p>



<input type="submit" value="Submit">

<input type="reset" value="Reset">



</form>



<!--- end page content --->



<p>

<hr size=2 width=85%>

<p>





<p>

<center>

<i>

  Please email questions/comments or things you would like added to

 <a href="mailto:gernot.schmied@iktech.net">gernot.schmied@iktech.net</a>

</i>

</center>

</tail>



</body>



<tail>

</html IKTech_LAST_MODIFIED="16-05-2003">


Zebra Looking Glasses

Besides the mother of all looking glasses from DIGEX, other useful approaches have evolved. The DIGEX code supports only rsh approaches. This section covers two newer-generation looking glasses: Data Telekom LG and MRLG.

Data Telecom LG (http://www.version6.net/) provides a Perl Common Gateway Interface (CGI) script capable of ssh, Telnet, and rsh access to Zebra, Cisco, and Juniper routers. It supports both IPv4 and IPv6 commands, almost all BGP-related commands, ping, traceroute, and the relaying of these queries to other looking glasses. Figure 10-20 shows a screenshot of the Data Telecom LG.

Figure 10-20. Data Telecom Looking Glass

[View full size image]
graphics/10fig20.jpg


As a second example, I want to introduce Denis Ovsienko's MRLG (Multi-Router Looking Glass), which is a PHP script capable of querying Zebra and Cisco routers (see Figure 10-21). You can retrieve MRLG from http://pilot.org.ua/mrlg/.

Figure 10-21. Web Interface to the MRLG

[View full size image]
graphics/10fig21.jpg


NOTE

A few words on looking-glass security: Whenever possible, at least protect your Cisco routers via the methods presented in the rsh example (unpriviledged dedicated user, kerberized rsh access, access lists). In addition, protect the UNIX workstation that the looking glass is running on (web server security, rsh user ID, ssh, firewalls).