### Local Variables: *** ### mode:perl *** ### comment-column:0 *** ### comment-start: "### " *** ### comment-end: "***" *** ### End: *** # # ****************DO NOT MOVE OR CHANGE LINES ABOVE THIS********************* # # The first set of lines runs perl from any shell. The second set of lines # identifies the rest of the file as PERL for EMACS autoformatting. # See end of copyright for more information. # # # ------------------------------------------------------------------- # X-BONE # # http://www.isi.edu/xbone # USC Information Sciences Institute (USC/ISI) # Marina del Rey, California 90292, USA # Copyright (c) 1998-2005 # # ------------------------------------------------------------------- # # Copyright (c) 1998-2005 by the University of Southern California. # All rights reserved. # # Permission to use, copy, modify, and distribute this software and # its documentation in source and binary forms for non-commercial # purposes and without fee is hereby granted, provided that the above # copyright notice appear in all copies and that both the copyright # notice and this permission notice appear in supporting # documentation, and that any documentation, advertising materials, # and other materials related to such distribution and use acknowledge # that the software was developed by the University of Southern # California, Information Sciences Institute. The name of the # University may not be used to endorse or promote products derived # from this software without specific prior written permission. # # THE UNIVERSITY OF SOUTHERN CALIFORNIA MAKES NO REPRESENTATIONS ABOUT # THE SUITABILITY OF THIS SOFTWARE FOR ANY PURPOSE. THIS SOFTWARE IS # PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, # INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. # # Other copyrights might apply to parts of this software and are so # noted when applicable. # # ------------------------------------------------------------------- # # Effort partly sponsored by the Defense Advanced Research Projects # Agency (DARPA) and Air Force Research Laboratory, Air Force Materiel # Command, USAF, under agreement numbers F30602-98-1-0200 (X-Bone) and # F30602-01-2-0529 (DynaBone). The views and conclusions contained # herein are those of the authors and should not be interpreted as # necessarily representing the official policies or endorsements, # either expressed or implied, of the Defense Advanced Research # Projects Agency (DARPA), the Air Force Research Laboratory, or the # U.S. Government. # # This work was partly supported by the NSF STI-XTEND (ANI-0230789) # and NETFS (ANI-0129689) projects. Any opinions, findings, and # conclusions or recommendations expressed in this material are those # of the authors and do not necessarily reflect the views of the # National Science Foundation. # # ------------------------------------------------------------------- # $RCSfile: XB_Route.pm,v $ # # $Revision: 1.71 $ # $Author: pingali $ # $Date: 2005/03/31 07:03:56 $ # $State: Exp $ # ---------------------------------------------------------------------------- # # Primary Author: Amy Hughes # This script is used in place of the unix 'route' command. # It takes the same input as 'route'. # # usage: # XXX - This needs to be fixed. # add_route( [-net|-host], destinationIP, [-netmask mask], # (gateway | -interface name)) # delete_route ([-net|-host], destinationIP, [-netmask mask]) # get_route ([-net|-host], destinationIP, [-netmask mask]) # Supported OS: # FreeBSD (Default OS) # Linux (modifications are marked) package XB_Route; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(); @EXPORT_OK = qw(add_route delete_route get_route init); use strict; use sigtrap; use Fcntl; use POSIX qw(:errno_h); use File::Copy; use Socket; use IPC::Open3; use Net::Netmask; use Net::IP; use XB_Log; use XB_Params; use IO::Select; use XB_CiscoSSH; my $log = 0; my $module = "XB_Route::"; ########################################################################### # # GENERAL CODE # ########################################################################### # Function: # verify_input # Used by: ALL # Description: # verify the route command # Arguments: # \%hash # ( command => (add|delete|get) # ovlname => # routing_method => (static|dynamic) # family => (-inet|-inet6) # dstarg => (-net|-host) # destination => # netmask_opt => (-netmask|-prefixlen) # netmask => (|) # gateway => # ); # Returns: # 1 on success # Exceptions: # "XB_Route::add_route" on error, nothing to clean up by caller sub verify_input($){ my ($inputhash) = @_; my $proc = "verify_input"; XB_Log::log "info" => "-> $module$proc $inputhash"; eval { # overlay if (not defined $inputhash->{ovlname}){ XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Undefined overlayname "; die "arg"; } # command if (not defined $inputhash->{command}){ XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Undefined $inputhash->{command} "; die "arg"; } if (($inputhash->{command} ne "get") and ($inputhash->{command} ne "add") and ($inputhash->{command} ne "delete")){ XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Wrong command $inputhash->{command}"; die "arg"; } # routing method if (not defined $inputhash->{routing_method}){ XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Undefined $inputhash->{routing_method} "; die "arg"; } if (($inputhash->{routing_method} ne "static") and ($inputhash->{routing_method} ne "dynamic")){ XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Wrong routing method $inputhash->{routing_method}"; die "arg"; } # family if (not defined $inputhash->{family}){ XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Undefined $inputhash->{family} "; die "arg"; } if (($inputhash->{family} ne "-inet6") and ($inputhash->{family} ne "-inet")){ XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Wrong family $inputhash->{family}"; die "arg"; } # dstarg if (not defined $inputhash->{dstarg}){ XB_Log::log "err","Unable to execute route commands. Consistency check failed." . "Undefined $inputhash->{dstarg} "; die "arg"; } if (($inputhash->{dstarg} ne "-host") and ($inputhash->{dstarg} ne "-net")){ XB_Log::log "err","Unable to execute route commands. Consistency check failed." . "Wrong arg $inputhash->{dstarg}"; die "arg"; } # destination if (not defined $inputhash->{destination}){ XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Undefined $inputhash->{destination} "; die "arg"; } my $n = new Net::IP($inputhash->{destination}); if (not defined $n){ XB_Log::log "err","Unable to execute route commands. Consistency check failed." . "Wrong $inputhash->{destination}"; die "arg"; } # netmask option if (not defined $inputhash->{netmask_opt}){ XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Undefined $inputhash->{netmask_opt} "; die "arg"; } unless ((($inputhash->{family} eq "-inet6") and ($inputhash->{netmask_opt} eq "-prefixlen")) or (($inputhash->{family} eq "-inet") and ($inputhash->{netmask_opt} eq "-netmask"))){ XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Inappropriate network mask - $inputhash->{netmask_opt}"; die "arg"; } # netmask if (not defined $inputhash->{netmask}){ XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Undefined $inputhash->{netmask} "; die "arg"; } # dev_opt if (defined $inputhash->{dev_opt}) { if (($inputhash->{dev_opt} ne "-interface") and ($inputhash->{dev_opt} ne "dev")){ XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Wrong arg $inputhash->{dev_opt}"; die "arg"; } if (not defined $inputhash->{dev}){ XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Undefined $inputhash->{dev} "; die "arg"; } if ($inputhash->{dev} =~ m/^(([a-zA-Z]+\d+)(:\d+)?)$/){ $inputhash->{dev} = $1; } else { XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Wrong arg $inputhash->{dev}"; die "arg"; } } elsif (defined $inputhash->{gateway}){ my $n = new Net::IP($inputhash->{gateway}); if (not defined $n){ XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "Incorrect gateway $inputhash->{gateway}"; die "arg"; } } else { XB_Log::log "err", "Unable to execute route commands. Consistency check failed." . "No gateway or interface"; } }; #eval XB_Log::log "info" => "<- $module$proc $inputhash"; return 1 unless ($@); if ($@ !~ /(arg)/){ XB_Log::log "err" => "Error: $@"; } die "$module$proc"; } ########################################################### # Exported API ########################################################### # Function: # add_route # Used by: ALL # Description: # Add the given route # Arguments: # hash # ( ovlname => # routing_method => (static|dynamic) # family => (-inet|-inet6) # dstarg => (-net|-host) # destination => # gateway => # ); # Returns: # 1 on success # Exceptions: # "XB_Route::add_route" on error, nothing to clean up by caller sub add_route($) { my($params) = @_; my $static = 0; # corresponding lines my $line; my $proc = "add_route"; XB_Log::log "info" => "-> $module$proc $params"; eval { my $cpid; die ("Incorrect argument") if (not defined $params); # parse the destination and specify the command. my $dip = new Net::IP::XB_IP ($params->{destination}); if (not defined $dip){ XB_Log::log "err", "Unable to create routes. ". "Received incorrect format: " . $params->{destination}; die "arg"; } if ($dip->version() == 4) { $params->{netmask_opt} = "-netmask"; $params->{netmask} = $dip->mask; $params->{destination} = $dip->addr; $params->{family} = "-inet"; } else { $params->{netmask_opt} = "-prefixlen"; $params->{netmask} = $dip->masklen; $params->{destination} = $dip->addr; $params->{family} = "-inet6"; } $params->{command} = "add"; if (not verify_input($params)){ XB_Log::log "err" => "Unable to create routes. Input could not be parsed"; die "parse"; } if ($XB_Params::node_opts{os} =~ /cisco/i) { # Cisco IOS if ((defined($params->{gateway})) && ($params->{gateway} ne "")) { if ((defined($params->{netmask})) && ($params->{netmask} ne "")) { my @cmd = "ip route $params->{destination} $params->{netmask} $params->{gateway} \n exit \n"; XB_Log::log "debug", "Route comand on Cisco: @cmd"; $cpid = XB_CiscoSSH::cmd @cmd or XB_Log::log "err" => "$!: Route command failed" and die "route"; } } elsif ((defined($params->{dev_opt})) && ($params->{dev_opt} ne "")) { if ((defined($params->{netmask})) && ($params->{netmask} ne "")) { my @cmd = ("ip route $params->{destination} $params->{netmask} $params->{dev_opt} \n exit \n"); $cpid = XB_CiscoSSH::cmd @cmd or XB_Log::log "err" => "$!: Route command failed" and die "route"; } else { # netmask not provided. using a dummy netmask : 255.255.255.255, # since CISCO IOS IP route command requires a netmask my @cmd = ("ip route $params->{destination} 255.255.255.255 $params->{gateway} \n exit \n"); $cpid = XB_CiscoSSH::cmd @cmd or XB_Log::log "err" => "$!: Route command failed" and die "route"; } } else { XB_Log::log "err" => "No destination specified for route command" and die "route"; } } elsif ($XB_Params::node_opts{os} =~ /linux/i) { # Linux OS my @cmd = (\*WTR, \*RDR, \*ERR, "route", "-n", "add", $params->{dstarg}, $params->{destination}); my @remaining = (); if ((defined($params->{gateway})) && ($params->{gateway} ne "")) { if (($params->{dstarg} !~ /host/) && (defined($params->{netmask})) && ($params->{netmask} ne "")) { @remaining = ( "netmask", $params->{netmask}, "gw", $params->{gateway}); } else { @remaining = ( "gw", $params->{gateway} ); } } elsif ((defined($params->{dev_opt})) && ($params->{dev_opt} ne "")) { if ((defined($params->{netmask})) && ($params->{netmask} ne "")) { @remaining = ($params->{netmask_opt}, $params->{netmask}, $params->{dev_opt}, $params->{dev}); } else { @remaining = ($params->{dev_opt}, $params->{dev}); } }else { XB_Log::log "err" => "Unable to create routes. No destination specified for route command" and die "route"; } # collected all the parameters. now, execute it. $cpid = open3(@cmd, @remaining) or XB_Log::log "err" => "Unable to create routes. $!: Route command failed" and die "route"; } else { # Default OS (FreeBSD) my @remaining = (); my @cmd = ( \*WTR, \*RDR, \*ERR, "route", "-n", "-q", "add", $params->{family}, $params->{dstarg}, $params->{destination}); if ((defined($params->{gateway})) && ($params->{gateway} ne "")) { if ((defined($params->{netmask})) && ($params->{netmask} ne "")) { @remaining = ($params->{netmask_opt}, $params->{netmask}, $params->{gateway}); }else { @remaining = ($params->{gateway}); } } elsif ((defined($params->{dev_opt})) && ($params->{dev_opt} ne "")) { if ((defined($params->{netmask})) && ($params->{netmask} ne "")) { @remaining = ($params->{netmask_opt}, $params->{netmask}, $params->{dev_opt}, $params->{dev}); } else { @remaining = ($params->{netmask_opt}, $params->{netmask}, $params->{dev_opt}, $params->{dev}); } } else { XB_Log::log "err" => "Unable to create routes. No destination specified for route command" and die "route"; }; #XB_Log::log "info" => "executing route command @cmd @remaining \n"; # now execute the command. $cpid = open3(@cmd, @remaining) or XB_Log::log "err" => "Unable to create routes. $!: Route command failed" and die "route"; }; # else if ($XB_Params::node_opts{os} !~ /cisco/i) { # check output from route and warn if not successful my @route = ; close WTR or XB_Log::log "err" => "Unable to cleanup after creating routes. Could not close: $!" and die "close_route"; close RDR or XB_Log::log "err" => "Unable to cleanup after creating routes. Could not close: $!" and die "close_route"; close ERR or XB_Log::log "err" => "Unable to cleanup after creating routes. Could not close: $!" and die "close_route"; if (defined $cpid) { waitpid $cpid, 0 == $cpid or XB_Log::log "err" => "Operating system resource error. ". "Could not create a child process $cpid" and die "wait"; } if (scalar @route) { # error XB_Log::log "err" => "Unable to create routers. Route command returned an error: @route" and die "route"; } else { return; } # no error } }; # eval block XB_Log::log "info" => "<- $module$proc $params"; return 1 unless $@; if ($@ =~ /^(parse|close_route|route|wait|make_backup|route_exists| XB_CiscoSSH::cmd)/) { XB_Log::log "err" => "$module$proc: caught error: $@"; # no cleanup } elsif ($@ =~ /^(gopen|gduplicate|gclose|checkconf)/) { XB_Log::log "err" => "$module$proc: caught error: $@"; } elsif ($@ =~ /^(gmove|grestart)/) { XB_Log::log "err" => "$module$proc: caught error: $@"; } else { XB_Log::log "warning" => "$module$proc caught unexpected exception $@"; } die "$proc"; } # sub add_route($) # Function: # delete_route # Used by: ALL # Description: # Deletes given route # Arguments: # %hash # ( ovlname => # routing_method => (static|dynamic) # family => (-inet|-inet6) # dstarg => (-net|-host) # destination => # netmask_opt => (-netmask|-prefixlen) # netmask => (|) # gateway => # ); # Returns: # 1 on success # Exceptions: # "XB_Route::delete_route" on error, nothing to clean up by caller # sub delete_route(@) { my $proc = "delete_route"; my ($params) = @_; XB_Log::log "info" => "-> $module$proc $params"; eval { #just a sanity check. have to check the type instead. if (not defined $params){ XB_Log::log "err" => "Unable to delete routes. Incorrect arguments"; die "parse"; } # parse the destination and specify the command. my $dip = new Net::IP::XB_IP ($params->{destination}); if (not defined $dip){ XB_Log::log "err" => "Unable to delete routes. Incorrect format: " . $params->{destination}; die "parse"; } if ($dip->version() == 4) { $params->{netmask_opt} = "-netmask"; $params->{netmask} = $dip->mask; $params->{destination} = $dip->addr; $params->{family} = "-inet"; } else { $params->{netmask_opt} = "-prefixlen"; $params->{netmask} = $dip->masklen; $params->{destination} = $dip->addr; $params->{family} = "-inet6"; } $params->{command} = "delete"; if (not verify_input($params) ){ XB_Log::log "err" => "Input could not be parsed"; die "parse"; } # This is the ugly hack for the Linux problem!!!! my $cpid; if ($XB_Params::node_opts{ os} =~ /cisco/i) { # Cisco IOS if ((defined($params->{gateway})) && ($params->{gateway} ne "")) { if ((defined($params->{netmask})) && ($params->{netmask} ne "")) { my @cmd = "no ip route $params->{destination} $params->{netmask} $params->{gateway} \n exit \n"; $cpid = XB_CiscoSSH::cmd @cmd or XB_Log::log "err" => "$!: Route command failed" and die "route"; } } elsif ((defined($params->{dev_opt})) && ($params->{dev_opt} ne "")) { if ((defined($params->{netmask})) && ($params->{netmask} ne "")) { my @cmd = ("no ip route $params->{destination} $params->{netmask} $params->{dev_opt} \n exit \n"); $cpid = XB_CiscoSSH::cmd @cmd or XB_Log::log "err" => "$!: Route command failed" and die "route"; } else { # netmask not provided. using a dummy netmask : 255.255.255.255, since CISCO IOS IP route command requires a netmask my @cmd = ("no ip route $params->{destination} 255.255.255.255 $params->{gateway} \n exit \n"); $cpid = XB_CiscoSSH::cmd @cmd or XB_Log::log "err" => "$!: Route command failed" and die "route"; } } else { XB_Log::log "err" => "No destination specified for route command" and die "route"; } } elsif ($XB_Params::node_opts{os} =~ /linux/i) { # Linux OS my @cmd = (\*WTR, \*RDR, \*ERR, "route", "-n", "del", $params->{dstarg}, $params->{destination}); my @remaining = (); if (($params->{dstarg} !~ /host/) && (defined($params->{netmask})) && ($params->{netmask} ne "")) { @remaining = ( "netmask", $params->{netmask} ); }; $cpid = open3(@cmd, @remaining) or XB_Log::log "err" => "Unable to delete routes. ". "$!: Route command failed" and die "route"; } else { # Default OS (FreeBSD) my @cmd = (\*WTR, \*RDR, \*ERR, "route", "-n", "delete", $params->{family}, $params->{dstarg}, $params->{destination}); my @remaining = (); if ((defined($params->{netmask})) && ($params->{netmask} ne "")){ @remaining = ( $params->{netmask_opt}, $params->{netmask} ); }; $cpid = open3(@cmd, @remaining) or XB_Log::log "err" => "Unable to delete routes. ". "$!: Route command failed" and die "route"; } if ($XB_Params::node_opts{os} !~ /cisco/i){ # check output from route and warn if not successful my @route = ; close WTR or XB_Log::log "err" => "Unable to cleanup after deleting routes. Close failed: $!" and die "close_route"; close RDR or XB_Log::log "err" => "Unable to cleanup after deleting routes. Close failed: $!" and die "close_route"; close ERR or XB_Log::log "err" => "Unable to cleanup after deleting routes. Close failed: $!" and die "close_route"; if (defined $cpid) { waitpid $cpid, 0 == $cpid or XB_Log::log "err" => "Operating system resource error. ". "No child $cpid" and die "wait"; } # NOTE: NO ERROR WHEN DELETING A NON-EXISTING ROUTE. NEED REVIEW! #if (defined(@route)) { ### This is an ugly hack to avoid giving an error if the route ### didn't exist. if (scalar @route and ($route[0] !~ m/No such process/i)) { # error XB_Log::log "err" => "route command returned an error: @route" and die "route"; } else { return; } # no error } }; # eval block XB_Log::log "info" => "<- $module$proc $params"; return 1 unless $@; if ($@ =~ /^(parse|close_route|route|wait|make_backup|checkconf| XB_Cisco::cmd)/) { XB_Log::log "err" => "$module$proc: caught error: $@"; # no cleanup } elsif ($@ =~ /^(open|close)/) { XB_Log::log "err" => "$module$proc: caught error: $@"; } elsif ($@ =~ /^(move|restart|checkconf)/) { XB_Log::log "err" => "$module$proc: caught error: $@"; } else { XB_Log::log "warning" => "$module$proc caught unexpected exception $@"; } die "$module$proc $@"; } # sub delete_route(@) # Function: # get_route # Used by: ALL # Description: # Retrieve configuration for given route # Arguments: # %hash = # ( ovlname => # routing_method => (static|dynamic) # family => (-inet|-inet6) # dstarg => (-net|-host) # destination => # netmask_opt => (-netmask|-prefixlen) # netmask => (|) # gateway => # ); # Returns: # 1 if route is configured # 0 otherwise # Exceptions: # "XB_Route::get_route" on error, nothing to clean up by caller # sub get_route(@) { my $proc = "get_route"; my (@args) = @_; my $params; XB_Log::log "info" => "-> $module$proc @args"; $params->{command} = "get"; if (not verify_input($params)){ XB_Log::log "err" => "Unable to obtain routes. Input could not be parsed"; die "parse"; } my $get_route = 0; $get_route = eval { if ($XB_Params::node_opts{os} =~ /cisco/i) { # Cisco IOS my @cpid = ""; my $line; my @cmd; if ((defined($params->{netmask})) && ($params->{netmask} ne "")) { @cmd = ("show running-config | include ip route $params->{destination} $params->{netmask}"); @cpid = XB_CiscoSSH::show_cmd @cmd or XB_Log::log "err" => "$!: Route command failed" and die "route"; } else { @cmd = ("show running-configuration | include ip route $params->{destination}"); @cpid = XB_CiscoSSH::show_cmd @cmd or XB_Log::log "err" => "$!: Route command failed" and die "route"; } @cpid = split "\n", $cpid[1]; @cpid = split(" ", $cpid[0]); if ($cpid[0] ne "") { if ($cpid[2] eq $params->{destination}) { if ((defined($params->{netmask})) && ($params->{netmask} ne "") && ($cpid[3] eq $params->{netmask})) { return 1; } else { return 1; } } } else { return 0; } } elsif ($XB_Params::NODEOS =~ /linux|nist/i) { # Linux OS (No "route get") my $pipe = "route -n |"; open ROUTE, $pipe or XB_Log::log "err" => "Unable to obtain routes. $!: Could not open pipe $pipe" and die "route"; my $line; while (defined($line = )) { my @line = split(" ", $line); if ($line[0] eq $params->{destination}){ if (defined $params->{netmask}){ if ($line[2] eq $params->{netmask}){ return 1; } else { return 0; } } else { return 1; } } } close ROUTE; return 0; } else { # Default OS (FreeBSD) my $cpid; my @cmd = (\*WTR, \*RDR, \*ERR, "route", "-n", "get", $params->{dstarg}, $params->{destination}); my @remaining = (); if ((defined($params->{netmask})) && ($params->{netmask} ne "")) { @remaining = ($params->{netmask_opt}, $params->{netmask}); } open3(@cmd, @remaining) or XB_Log::log "err" => "Unable to obtain routes. ". "$!: Route command failed" and die "route"; if (defined(my $line = )) { close WTR or XB_Log::log "err" => "Unable to cleanup after obtaining routes. Close failed: $!" and die "close"; close RDR or XB_Log::log "err" => "Unable to cleanup after obtaining routes. Close failed: $!" and die "close"; close ERR or XB_Log::log "err" => "Unable to cleanup after obtaining routes. Close failed: $!" and die "close"; if (defined $cpid) { waitpid $cpid, 0 == $cpid or XB_Log::log "err" => "Operating system resource error. No child $cpid" and die "wait"; } return 0; } else { while (defined(my $line = )) { if ($line =~ m/route to:/) { # get "route to:" line and extract IP $line =~ s/\s$//g; my @line = split(':', $line); my $goal = $line[1]; $goal =~ s/^\s*//; # get "destination:" line and extract IP $line = ; $line =~ s/\s$//g; @line = split(':', $line); my $dest = $line[1]; $dest =~ s/^\s*//; close WTR; close RDR; close ERR; if (defined $cpid) { waitpid $cpid, 0 == $cpid or XB_Log::log "err" => "Operating system resource error. No child $cpid" and die "wait"; } if ($goal eq $dest) { return 1; } # explicit route defined else { return 0; } # uses default route last; } } #while (defined(my $line = )) } } # else }; # eval XB_Log::log "info" => "<- $module$proc @args"; return $get_route unless $@; if ($@ =~ /^(open|route|wait|close|XB_CiscoSSH::show_cmd)/){ XB_Log::log "err" => "$module$proc: caught error: $@"; } else { XB_Log::log "warning" => "$module$proc caught unexpected exception $@"; } die "$module$proc"; } # sub get_route(@) # Description: # Initialize the routing module. Call this *once* before using # any function in here. # Arguments: # - # Returns: # - # Exceptions: # "XB_Route::init" on error, nothing to clean up by caller sub init () { } 1;