はじめに
この記事はNetOpsCoding Advent Calendar 2015 の22日目の記事です。検証作業を捗らせるために一度に複数台のCiscoルータのルーティングテーブルを見える化するツールをRuby/Sinatra+SNMPで試作しました。
目的
検証作業では、複数台のルータのルーティングテーブルを都度確認する必要があります。ネットワークエンジニアなら各ルータにTELNET/SSHしてshow ip routeコマンドを打つのは手馴れていると思います。
ただ、ルータの台数が多くなると結構面倒です。マクロなどでshow ip routeを自動実行してもいいですが、CLIではルーティングテーブルを確認するのは大変です。GUIでルーティングテーブルを確認しようと考えた結果、WEBブラウザでルーティングテーブルを見える化するツールを試作しました。
ルータのルーティングテーブルを正規化して表形式で表示することで見やすくします。実装にあたっては、show ip routeを正規表現で抽出するのは至難の業なので、SNMPを使って抽出することにしました。
概要
使用している技術はNetOpsCoding Advent Calendar 2015の14日目のCiscoルータのポートをRuby/Sinatraで見える化して検証捗らせてみたと同等となります。そのため、実行環境等はまったく同じです。
- Cisco 1812J
- Ubuntu 15.04
- Ruby 2.2.3
- snmp 1.2.0(RubyでSNMP)
- sinatra 1.4.6(RubyのWebフレームワーク)
- slim(RubyのHTMLテンプレートエンジン)
- bootstrap3(HTML/CSSデザイン)
今回はグローバルルーティングテーブル(address-family ipv4)のルーティングテーブルを見える化します。ルーティング情報は標準MIBのIP-FORWARD-MIBのipCidrRouteEntryを用いています。
'1.3.6.1.2.1.4.24.4.1.5', # IP-FORWARD-MIB::ipCidrRouteIfIndex
'1.3.6.1.2.1.4.24.4.1.1', # IP-FORWARD-MIB::ipCidrRouteDest
'1.3.6.1.2.1.4.24.4.1.2', # IP-FORWARD-MIB::ipCidrRouteMask
'1.3.6.1.2.1.4.24.4.1.4', # IP-FORWARD-MIB::ipCidrRouteNextHop
'1.3.6.1.2.1.4.24.4.1.6', # IP-FORWARD-MIB::ipCidrRouteType
'1.3.6.1.2.1.4.24.4.1.8', # IP-FORWARD-MIB::ipCidrRouteAge
スクリプト
ルーティングテーブル見える化ツールは下記のファイルで構成されています。
├── Gemfile -- Ruby依存関係の記述ファイル
├── views
│ └── index.slim -- HTMLテンプレートファイル(slim形式)
└── webroutes.rb -- 実行ファイル本体兼Webサーバ
Gemfile
はBundleでRubyGemsの依存関係を記述したファイルです。
source 'https://rubygems.org'
gem 'snmp'
gem 'sinatra'
gem 'sinatra-contrib'
gem 'slim'
gem 'parallel'
gem 'ipaddress'
webroutes.rb
は実行ファイル本体です。設定箇所はHOSTS
変数とCOMMUNITY
変数です。HOSTS
変数で対象ルータを指定しています。COMMUNITY
変数でSNMPのコミュニティ名を指定しています。Rubyのparallelライブラリを使用して、in_threads: 4
を指定することで、同時に4台のルータでSNMPの値を取得します。
require 'sinatra'
require 'sinatra/reloader'
require 'slim'
require 'snmp'
require 'parallel'
require 'ipaddress'
set :bind, '0.0.0.0'
HOSTS = %w(192.168.88.101 192.168.88.102)
COMMUNITY = 'public'
WALK_COLUMNS = [
'1.3.6.1.2.1.4.24.4.1.5', # IP-FORWARD-MIB::ipCidrRouteIfIndex
'1.3.6.1.2.1.4.24.4.1.1', # IP-FORWARD-MIB::ipCidrRouteDest
'1.3.6.1.2.1.4.24.4.1.2', # IP-FORWARD-MIB::ipCidrRouteMask
'1.3.6.1.2.1.4.24.4.1.4', # IP-FORWARD-MIB::ipCidrRouteNextHop
'1.3.6.1.2.1.4.24.4.1.6', # IP-FORWARD-MIB::ipCidrRouteType
'1.3.6.1.2.1.4.24.4.1.8', # IP-FORWARD-MIB::ipCidrRouteAge
]
Route = Struct.new(:ifindex, :dest, :mask, :nexthop, :type, :age, :prefix, :time, :iface)
Host = Struct.new(:name, :routes, :ifaces)
get '/' do
@hosts = []
Parallel.each(HOSTS, in_threads: 4) do |host|
begin
SNMP::Manager.open(host: host, community: COMMUNITY, timeout: 1, retries: 3) do |manager|
ifaces = {}
manager.walk('ifDescr') do |vb|
index = vb.name.last.to_s
value = vb.value.to_s
ifaces[index] = value
end
routes = []
manager.walk(WALK_COLUMNS) do |row|
values = row.map { |vb| vb.value.to_s }
route = Route.new(*values)
route.prefix = IPAddress("#{route.dest}/#{route.mask}").to_string
route.iface = ifaces[route.ifindex]
route.time = Time.at(Time.now.to_i - route.age.to_i)
routes << route
end
@hosts << Host.new(host, routes, ifaces)
end
rescue => e
@hosts << Host.new("#{host} - #{e}", [], {})
end
end
@hosts.sort_by!(&:name)
slim :index
end
views/index.slim
はWeb画面用のHTMLテンプレートファイルです。Bootstrap3を利用して、見た目をちょっとかっこよくしています。Boostrap3やjQueryはCDNのファイルを参照するようにしています。検証環境からインターネットに接続できない場合は、ローカルに各種ファイルをダウンロードしてpublicディレクトリ配下に入れる必要があります。
doctype html
html lang="ja"
head
meta charset="utf8"
meta http-equiv="X-UA-Compatible" content="IE=edge"
meta name="viewport" content="width=device-width, initial-scale=1"
link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" crossorigin="anonymous"
title WebRoutes
body
- @hosts.each do |host|
h3
= host.name
small
| Global Routing Table
table.table.table-condensed.table-striped.table-hover
thead
th #
th Prefix
th Dest
th Mask
th Nexthop
th Port
th Since
tbody
- host.routes.each_with_index do |route, index|
tr
td = index + 1
td = route.prefix
td = route.dest
td = route.mask
td = route.nexthop
td = route.iface
td = route.time
script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"
script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" crossorigin="anonymous"
実行結果
$ bundle
Using backports 3.6.7
Using ipaddress 0.8.0
Using multi_json 1.11.2
Using parallel 1.6.1
Using rack 1.6.4
Using rack-protection 1.5.3
Using rack-test 0.6.3
Using tilt 2.0.1
Using sinatra 1.4.6
Using sinatra-contrib 1.4.6
Using temple 0.7.6
Using slim 3.0.6
Using snmp 1.2.0
Using bundler 1.10.6
Bundle complete! 6 Gemfile dependencies, 14 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
$ ruby webroutes.rb
[2015-12-06 00:08:35] INFO WEBrick 1.3.1
[2015-12-06 00:08:35] INFO ruby 2.2.3 (2015-08-18) [x86_64-linux]
== Sinatra (v1.4.6) has taken the stage on 4567 for development with backup from WEBrick
[2015-12-06 00:08:35] INFO WEBrick::HTTPServer#start: pid=30233 port=4567
Webブラウザからhttp://localhost:4567にアクセスすると、経路の状態を一括して確認できます。
画面を更新すると最新の状態を確認できます。
表の見方
- Prefix : 経路をプレフィックス形式で表しています。
- Dest : 経路のネットワークアドレスを表しています。
- Mask : 経路のサブネットマスクを表しています。
- Nexthop : 経路のネクストホップアドレスを表しています。0.0.0.0の場合はは自分自身を表します。
- Port : 経路のネクストホップのポートを表しています。
- Since : 経路を学習した時刻を表しています。
工夫点
- 経路学習のタイミングもルータ内で保持していますが、現在時刻から何秒前など見難いため、
実際の学習時刻に変換して見やすくしています。SNMPの取得タイミングによって1秒程度誤差がでます。 - 見やすいプレフィックス形式で表示しています。
- 表形式で表示しているため、show ip routeよりも若干見やすくなっています。
- 一度に複数のルータでSNMP取得するため、Parallelを用いて多重化しています。
おわりに
今回は、グローバルルーティングテーブルを表示させる機能を試作しています。実際の検証で作成したものはBGPのVPNv4を表示させるツールでしたが、あまり一般的ではないと思い作りなおし、必要最低限の機能のみ実装しています。
参考
- Rubyで多数のCiscoルータで複数のコマンドを実行する - Qiita
- Ciscoルータのポート一覧をRubyとSNMPで取得する - Qiita
- CiscoルータのポートをRuby/Sinatraで見える化して検証捗らせてみた - Qiita