Introduction

NetAddr arose through my need as a network engineer for a back-end module that could easily handle such advanced tasks as automating the subnetting/supernetting of IP space, performing calculations on IP CIDR blocks, and other various items. At that time there were no modules that could do any of the things that I needed, so I set out to create my own. Since it has proven to be fairly useful to me, I have decided to share the code with the Ruby community.

I have added things that I find immediately useful for me. I am open to suggestions if there is something that I could add to make your life easier.

Dustin Spinhirne

Example Script

#!/usr/bin/ruby

# A script to parse static routes from a Cisco router.
# Performs the following tasks:
#  - organizes statics by directly connected next-hop interface
#  - corrects recursive static routes
#  - reports statics with no directly connected next-hop
#  - reports duplicate static routes
#  - reports static routes that overlap directly connected interfaces
#
# Change the variable 'infile' to that of the name of the router configuration
# file that should be parsed.

require 'rubygems'
require_gem 'netaddr'

# change this variable to that of the router config file
infile = 'router_config.txt'

ip_interface_count = 0
static_route_count = 0
connected_routes = []
static_routes = []
statics = {}
grouped_routes = {}
duplicates = []
recursives = []
corrected_statics = []
overlapping_statics = []

# sort through router config. put connected ip interfaces into 'connected_routes'
# and static routes into 'statics'. Set aside duplicate static routes.
File.open(infile, 'r') do |file|
    while line = file.gets
        if (line =~/^interface .+/)
            interface = line.chomp
        elsif (line =~ /^\s*ip address/) # connected interface
            ip_interface_count += 1
            addr = line.split('ip address ').join # ['x.x.x.x y.y.y.y']
            cidr = NetAddr::CIDR.create(addr, :Tag => {:interface => interface})
            connected_routes.push(cidr)
        elsif (line =~/^ip route/) # static route
            static_route_count += 1
            elements = line.split(' ') # ['ip','route','x.x.x.x','y.y.y.y','x.x.x.x',]
               ip = elements[2]
               netmask = elements[3]
               nexthop = elements[4]

            # keep all but loopbacks, nulls, or defaults
            if ( (nexthop !~ /Loopback/) && (nexthop !~ /Null/) && (ip != '0.0.0.0') )
                nexthop_cidr = NetAddr::CIDR.create(nexthop)
                cidr = NetAddr::CIDR.create("#{ip} #{netmask}",
                                            :Tag => {:route => line, :nexthop => nexthop_cidr})

                if (!statics.has_key?(cidr.desc))
                    statics[cidr.desc] = cidr
                else
                    msg = '! overlaps with - ' + statics[cidr.desc].tag[:route] + line + "!\n"
                    duplicates.push(msg)
                end
            end
        end
    end
end

# look for statics that overlap with a connected interface, and
# group static routes with their next-hop interface.
statics.each_key do  |desc|
    cidr = statics[desc]
    nexthop = cidr.tag[:nexthop]
    route = cidr.tag[:route]

    overlaps_with = nil
    nexthop_int = nil
    connected_routes.each do |interface|
        if (interface.contains?(cidr)) # overlapping static
            overlaps_with = interface
            break
        elsif (interface.contains?(nexthop)) # next-hop directly connected
            nexthop_int = interface
            break
        end
    end

    if (nexthop_int)
        key = "#{nexthop_int.tag[:interface]} -> #{nexthop_int.desc}"
        if (grouped_routes.has_key?(key))
            grouped_routes[key].push(route)
        else
            grouped_routes[key] = [route]
        end
        static_routes.push(cidr)
    elsif (overlaps_with)
        overlap = "! overlaps with: #{overlaps_with.tag[:interface]} -> #{overlaps_with.desc}"
        overlap << "\n#{route}!\n"
        overlapping_statics.push(overlap)
    else
        recursives.push(cidr)
    end
end

# process recursive routes. update next-hop so that it points
# to next hop of a directly connected ip. remove any that do not point to a
# directly connected ip. We must continually cycle through the list, as the
# 'static_routes' tree is constantly updated. We do this until our list of
# recursive static routes stops getting any shorter
recursives_count = 0
until (recursives_count == recursives.length)
    recursives_count = recursives.length
    recursives.each do  |cidr|
        nexthop = cidr.tag[:nexthop]
        route = cidr.tag[:route]

        found = nil
        static_routes.each do |static|
            if (static.contains?(nexthop))
                found = static
                break
            end
        end

        if (found)
            updated = 'no ' + route
            updated << "ip route #{cidr.network} #{cidr.netmask_ext} #{found.tag[:nexthop].ip}\n"
            updated << "!\n"
            corrected_statics.push(updated)
            static_routes.push(cidr)
            recursives.delete(cidr)
        end
    end
end

# print results.
puts "--- STATISTICS ---"
puts "#{ip_interface_count} Connected IP Interfaces\n#{static_route_count} IP Static Routes\n"

print "\n\n"

puts "--- OVERLAPPING STATIC ROUTES ---"
overlapping_statics.each do |overlap|
    puts overlap
end

print "\n\n"

puts "--- DUPLICATE STATIC ROUTES ---"
duplicates.each do |route|
    puts route
end

print "\n\n"

puts "--- STATIC ROUTES WITH UPDATED NEXT-HOP ---"
corrected_statics.each do |route|
    puts route
end

print "\n\n"

puts "--- STATIC ROUTES WITH UNKNOWN NEXT-HOP ---"
recursives.each do |cidr|
    puts cidr.tag[:route]
end

print "\n\n"

puts "--- STATIC ROUTES GROUPED BY NEXT-HOP INTERFACE ---"
grouped_routes.each_key do |interface|
    routes = grouped_routes[interface]
    puts interface
    routes.each do |route|
        puts route
    end
    print "\n"
end