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.
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.
... 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 ...
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.
#!/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 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 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 }
<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 /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">
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.
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/.
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).