Now, if you're familiar with routing protocols, you might point out that retrieving connected routes from each router is wasteful; any one router with an OSPF process running in a given area will already know the complete topology for that area. And if you want the topologies of several areas, you just need to retrieve topology information from a few ABRs. I have two problems with this method. First, one of the things I had in mind when doing this was discovery: I would like to eventually discover the routers on the network rather than resort to maintaining a list. Finding ABRs is a more complex process than just noting any routers I come across. Indeed I doubt there would be any way of doing so without first discovering all of the routers on the network. Second, if I wanted to generate a topology for a network that used a distance-vector routing protocol, or only static routes, then the idea of extracting topology information from a single router goes right out the window.
If a third point could be made it is that I somehow find the idea of charting connected routes to be the most elegant solution. At the very least, it is probably the way I would document a layer-3 network if I had to by hand. Using CDP would require that I make too many "stops" along the way.
When I started this I had never worked with graphviz before. In fact, I didn't even have graphviz on my machine. Luckily some smart developers ported graphviz over to Mac OS. You can find the latest version here: http://www.pixelglow.com/graphviz/. For examples of syntax, I found this nice article: http://www.linuxjournal.com/article/7275. And for information about graphviz's wealth of attributes I used http://www.graphviz.org/doc/schema/attributes.xml.
After figuring out graphviz I now needed to write an Expect script to pull connected routes from a Cisco router. The code is straightforward:
Click to expand code
#!/usr/bin/expect -f
if { $argv == "" } {
puts "Usage: get_routes_conn.exp <router_ip> \[<router_ip>\]...\n"
exit
}
set match ""
set prompt ""
set timeout 20
foreach rtr $argv {
# Kludge for a problem where the use of continue
# on the last iteration causes foreach to
# run an iteration with a null list item. Bug in
# TCL/Expect?
if {$rtr == ""} {
break
}
# Check if IP is well-formed
regexp {[0-9]{1,3}(\.[0-9]{1,3}){3}} $rtr match
if {$match == ""} {
puts "$rtr is not a well-formed IP!\n"
continue
} else {set match ""}
log_user 0
puts ":$rtr -"
spawn telnet $rtr
expect {
#For when a username implies priveleged mode
"sername: " {
send "jstorm\r"
expect "assword: "
send "somepassword\r"
set prompt "#"
}
#For when no username implies unprivileged mode
default {
send "somepassword\r"
set prompt ">"
}
}
expect "$prompt"
log_user 1
send "sho ip route conn | incl ^C_\r"
# Scroll through any paginated output
while {1} {
expect {
--
" --More-- " {send " "}
"$prompt" {break}
}
}
log_user 0
send "exit\r"
close
puts ""
}
exit
if { $argv == "" } {
puts "Usage: get_routes_conn.exp <router_ip> \[<router_ip>\]...\n"
exit
}
set match ""
set prompt ""
set timeout 20
foreach rtr $argv {
# Kludge for a problem where the use of continue
# on the last iteration causes foreach to
# run an iteration with a null list item. Bug in
# TCL/Expect?
if {$rtr == ""} {
break
}
# Check if IP is well-formed
regexp {[0-9]{1,3}(\.[0-9]{1,3}){3}} $rtr match
if {$match == ""} {
puts "$rtr is not a well-formed IP!\n"
continue
} else {set match ""}
log_user 0
puts ":$rtr -"
spawn telnet $rtr
expect {
#For when a username implies priveleged mode
"sername: " {
send "jstorm\r"
expect "assword: "
send "somepassword\r"
set prompt "#"
}
#For when no username implies unprivileged mode
default {
send "somepassword\r"
set prompt ">"
}
}
expect "$prompt"
log_user 1
send "sho ip route conn | incl ^C_\r"
# Scroll through any paginated output
while {1} {
expect {
--
" --More-- " {send " "}
"$prompt" {break}
}
}
log_user 0
send "exit\r"
close
puts ""
}
exit
When you feed this script the IP of a Cisco router or layer-3 switch running IOS it should produce output similar to the following:
Click to expand code
:172.17.0.207 -
sho ip route conn | incl ^C_
C 172.171.0.207/32 is directly connected, Loopback0
C 172.20.0.207/32 is directly connected, Loopback1
C 172.25.207.0/24 is directly connected, FastEthernet1/1
TUNNEL-rtr#
sho ip route conn | incl ^C_
C 172.171.0.207/32 is directly connected, Loopback0
C 172.20.0.207/32 is directly connected, Loopback1
C 172.25.207.0/24 is directly connected, FastEthernet1/1
TUNNEL-rtr#
Hooray! We have connected routes and interfaces! Of course, the problem then becomes turning that output into this:
Click to expand code
"172.25.207.0/24" [label="172.25.207.0/24", shape=ellipse, \
fillcolor="#ecd9cc"];
"172.17.0.207" [label="TUNNEL-rtr\n172.17.0.207", shape=box, fillcolor="#ccd9ec", \
URL="telnet://172.17.0.207"];
"172.17.0.207" -- "172.25.207.0/24" [label="Fa 1/1"];
fillcolor="#ecd9cc"];
"172.17.0.207" [label="TUNNEL-rtr\n172.17.0.207", shape=box, fillcolor="#ccd9ec", \
URL="telnet://172.17.0.207"];
"172.17.0.207" -- "172.25.207.0/24" [label="Fa 1/1"];
A messy affair, indeed. Fortunately, I've written a BASH script that does exactly this:
Click to expand code
#!/bin/bash
#
# FILENAME: make_rtr_topo.sh
# DESCRIPTION: Uses get_routes_conn.exp to create a diagram from a list of given routers.
# LAST UPDATE: 2009-09-27 - jds (Lots of things)
#
EXPFILE='/home/jstorm/scripts/get_routes_conn.exp'
if [ -z $1 ]; then
echo "Usage: make_rtr_topo.sh <router_ip> [<router_ip>]...\n"
exit
fi
#Set attributes for graphviz output
NETATTR='[label=\"\1\", shape=ellipse, fillcolor="#ecd9cc"];'
RTRATTR='[label=\"\2\\n\1\", shape=box, fillcolor="#ccd9ec", URL=\"telnet:\/\/\1\"];'
EDGEATTR='[label=\"\2\"];';
nets=""
cnxnlist=""
#graphviz graph header
echo "graph BACKBONE"
echo "{"
echo " edge [len=2.5];"
echo " node [style=filled];"
echo " size=\"25,20\";"
echo " splines=true;"
echo " normalize=true;"
echo " overlap=false;"
echo " comment=\"BACKBONE - `date '+%m/%d/%Y %H:%M'`\";"
echo
#Fetch connected routes, extracting the hostname, routes, and interfaces;
# format as NEATO node or edge notation
for ip in $@; do
tmp="`${EXPFILE} $ip`"
hostname="`echo \"$tmp\" | tail -n 1 | sed -e 's/[>#]$//'`"
tmp2="`echo \"$tmp\" | egrep '^:|^C' | sed -e 's/^C *//' \
-e 's/ is .*, \([a-zA-Z][a-zA-Z]\)[a-zA-Z]*\([0-9]*\(\/*[0-9]*\)*\.*[0-9]*\)^M$/,\1 \2/'`"
router=`echo -n "$tmp2" | head -n 1 | sed -e 's/://' -e "s/ -$/,$hostname/"`
nets="`echo \"$nets\"; echo \"$tmp2\" | tail -n +2`"
cnxnlist="`echo \"$cnxnlist\"; echo \"$tmp2\" | tail -n +2 | \
sed -e \"s/^/$router -- /\"`"
done
nets="`echo \"$nets\" | sed -e 's/,.*$//'`"
#Prune networks not common between two or more routers
# (i.e. DO NOT display all routed networks, lest it fries your brain)
nets="`echo \"$nets\" | sort | uniq -d`"
#Prune edges whose network nodes are not defined
tmplist=""
for net in `echo $nets`; do
tmplist="`echo \"$tmplist\"; echo \"$cnxnlist\" | grep $net`"
done
cnxnlist="`echo \"$tmplist\" | sort | uniq`"
#Output network nodes
echo "$nets" | sed -e "s/^\(.*\)$/\"\1\" $NETATTR/" | sed -e 's/^/ /'
echo
#Output router nodes
echo "$cnxnlist" | tail -n +2 | sed -e 's/ --.*$//' | sort | uniq | \
sed -e "s/^\(.*\),\([0-9a-zA-Z\-]*\)/\"\1\" $RTRATTR/" | sed -e 's/^/ /'
echo
#Output router-to-network node edges
echo "$cnxnlist" | tail -n +2 | sed -e 's/^\(.*\),\([0-9a-zA-Z\-]*\) --/\"\1\" --/' \
-e "s/-- \(.*\),\(.*\)/-- \"\1\" $EDGEATTR/" | sed -e 's/^/ /'
echo "}"
#
# FILENAME: make_rtr_topo.sh
# DESCRIPTION: Uses get_routes_conn.exp to create a diagram from a list of given routers.
# LAST UPDATE: 2009-09-27 - jds (Lots of things)
#
EXPFILE='/home/jstorm/scripts/get_routes_conn.exp'
if [ -z $1 ]; then
echo "Usage: make_rtr_topo.sh <router_ip> [<router_ip>]...\n"
exit
fi
#Set attributes for graphviz output
NETATTR='[label=\"\1\", shape=ellipse, fillcolor="#ecd9cc"];'
RTRATTR='[label=\"\2\\n\1\", shape=box, fillcolor="#ccd9ec", URL=\"telnet:\/\/\1\"];'
EDGEATTR='[label=\"\2\"];';
nets=""
cnxnlist=""
#graphviz graph header
echo "graph BACKBONE"
echo "{"
echo " edge [len=2.5];"
echo " node [style=filled];"
echo " size=\"25,20\";"
echo " splines=true;"
echo " normalize=true;"
echo " overlap=false;"
echo " comment=\"BACKBONE - `date '+%m/%d/%Y %H:%M'`\";"
echo
#Fetch connected routes, extracting the hostname, routes, and interfaces;
# format as NEATO node or edge notation
for ip in $@; do
tmp="`${EXPFILE} $ip`"
hostname="`echo \"$tmp\" | tail -n 1 | sed -e 's/[>#]$//'`"
tmp2="`echo \"$tmp\" | egrep '^:|^C' | sed -e 's/^C *//' \
-e 's/ is .*, \([a-zA-Z][a-zA-Z]\)[a-zA-Z]*\([0-9]*\(\/*[0-9]*\)*\.*[0-9]*\)^M$/,\1 \2/'`"
router=`echo -n "$tmp2" | head -n 1 | sed -e 's/://' -e "s/ -$/,$hostname/"`
nets="`echo \"$nets\"; echo \"$tmp2\" | tail -n +2`"
cnxnlist="`echo \"$cnxnlist\"; echo \"$tmp2\" | tail -n +2 | \
sed -e \"s/^/$router -- /\"`"
done
nets="`echo \"$nets\" | sed -e 's/,.*$//'`"
#Prune networks not common between two or more routers
# (i.e. DO NOT display all routed networks, lest it fries your brain)
nets="`echo \"$nets\" | sort | uniq -d`"
#Prune edges whose network nodes are not defined
tmplist=""
for net in `echo $nets`; do
tmplist="`echo \"$tmplist\"; echo \"$cnxnlist\" | grep $net`"
done
cnxnlist="`echo \"$tmplist\" | sort | uniq`"
#Output network nodes
echo "$nets" | sed -e "s/^\(.*\)$/\"\1\" $NETATTR/" | sed -e 's/^/ /'
echo
#Output router nodes
echo "$cnxnlist" | tail -n +2 | sed -e 's/ --.*$//' | sort | uniq | \
sed -e "s/^\(.*\),\([0-9a-zA-Z\-]*\)/\"\1\" $RTRATTR/" | sed -e 's/^/ /'
echo
#Output router-to-network node edges
echo "$cnxnlist" | tail -n +2 | sed -e 's/^\(.*\),\([0-9a-zA-Z\-]*\) --/\"\1\" --/' \
-e "s/-- \(.*\),\(.*\)/-- \"\1\" $EDGEATTR/" | sed -e 's/^/ /'
echo "}"
As you may already have guessed, this script relies heavily on
sort
, assuming that it will sort deterministically. If you see differences in the sort that is produced between different machines (as I did) then you may consider setting the LC_COLLATE
environment variable. I prefer that LC_COLLATE
be set to 'C', making '1' come before '10' and so on. If you would like to see the existing locale setting, try issuing the locale
command; env
may also be a good place to look.With the output from
make_rtr_topo.sh
redirected to a file, graphviz's NEATO takes care of the rest. For output to an SVG use the following:neato -ol3topo.svg -Tsvg l3topo.dot
Here, 'l3topo.svg' is the output file, '-T' specifies the output format, and 'l3topo.dot' contains the output from
make_rtr_topo.sh
. Easy, eh?Here is an example of the output.
Cheers.
No comments:
Post a Comment