はじめに
この記事はNetOpsCoding Advent Calendar 2015 の17日目の記事です。ネットワークプログラマビリティ勉強会 #6のルータとチャットしてみましたに触発され、Ruby/XMPPでCiscoルータとチャットしてみました。
概要
インスタント・メッセンジャープロトコルのXMPPを用いて、Ciscoルータ1台毎にアカウントを作成して、チャットしてみました。環境は下記のとおりです。XMPPサーバ、XMPPクライアント、XMPPボットはローカルホスト上で動作させるため、アカウントのドメイン名は@localhostで統一しています。
- XMPPサーバ:Openfire in Ubuntu 15.04
- XMPPクライアント:Empathy(GNOMEのインスタント・メッセンジャー)
- XMPPボット:自作Rubyスクリプト(xmpp4rとexpect4rライブラリを使用)
- Ciscoルータ:Cisco1812J 2台
XMPPサーバ
XMPPサーバはDockerコンテナ上に構築しました。Dockerfile
はsameersbn/openfire:3.10.3-1を利用させていただきました。セットアップ方法については割愛します。利用した機能はUsersとGroupsのみです。今回はGroup Chatは利用していません。
アカウント
アカウントについては下記の通り管理者ユーザのadminと、Ciscoルータを示すr1とr2のアカウントを作成しました。Routersグループを作成し、すべてのアカウントを所属させています。これにより連絡先リストを共有することができます。
- admin@localhost - XMPPクライアントを起動するためのアカウント
- r1@localhost/bot - CISCOルータ R1
- r2@localhost/bot - CISCOルータ R2
XMPPクライアント
XMPPクライアントはUbuntuに標準で付いているインスタント・メッセンジャーアプリのEmpathyを利用しました。下記の通り、adminアカウントでログインすると、2台のルータが連絡先リストに出現します。アカウントのアイコンはEmpathyに手動で登録しました。アイコンはCiscoのアイコンを利用しています。
XMPPボット
XMPPボットはRuby2.2.3で作成しました。ライブラリは下記を使用して実装しています。スクリプトのほとんどは各ライブラリのサンプルを用いて実装しています。
スクリプト
bot.rb
が実行ファイルです。HOSTS
変数に配列でアカウント名とルータのIPアドレスを登録しています。USERNAME
、PASSWORD
、ENABLE_PASSWORD
はCiscoルータにTELNETする際のログイン情報を設定します。BOT_PASSWORD
はXMPPサーバに登録されたアカウントのパスワードを示します。
スクリプトの動作は単純です。ルータごとにBotクラスのstart
メソッドが実行され、XMPPサーバと接続し、XMPPクライアントからのメッセージを待ち受けします。XMPPクライアントからメッセージを受信するとon_message_callback
メソッドが実行されます。メソッド内でshow、ping、traceから始まるメッセージはコマンドと解釈して、ルータでコマンドを実行し、その実行結果をXMPPクライアントに返信しています。reply_cmd
メソッドがルータにTELNETしてコマンドを実行しています。実行した結果はreply
メソッドで、送信元のXMPPクライアントに返信します。
require 'xmpp4r'
require 'expect4r'
require 'logger'
Log = Logger.new(STDOUT)
Log.level = Logger::DEBUG
HOSTS = [
%w(R1 192.168.88.101),
%w(R2 192.168.88.102)
]
USERNAME = 'cisco'
PASSWORD = 'cisco'
ENABLE_PASSWORD = 'cisco'
BOT_PASSWORD = 'cisco'
# Bot for Cisco IOS
class Bot
def initialize(host, ipaddr)
@host = host
jid = Jabber::JID.new("#{host}@localhost/bot")
@client = Jabber::Client.new(jid)
@ios = Expect4r::Ios.new_telnet(
host: ipaddr,
user: USERNAME,
pwd: PASSWORD,
enable_password: ENABLE_PASSWORD
)
end
def start
Log.info "#{@host} starting"
@client.connect
@client.auth(BOT_PASSWORD)
@client.send(Jabber::Presence.new.set_show(:chat).set_type(:available))
@client.add_message_callback(&method(:on_message_callback))
end
def reply(msg, reply_content)
reply_msg = Jabber::Message.new(msg.from, reply_content)
reply_msg.type = msg.type
@client.send(reply_msg)
end
def reply_cmd(msg, cmd)
Thread.new do
begin
Log.debug "#{@host} #{msg.from} [#{cmd}]"
@ios.login
result = @ios.exec(cmd)
reply(msg, result.join)
rescue => e
reply(msg, "コマンド実行失敗 :'( #{e}")
end
end
end
def on_message_callback(msg)
return if msg.body.nil? || msg.type == :error
case msg.body
when /^((show|ping|trace).*)/
cmd = $1
reply(msg, "『#{cmd}』実行!:-)")
reply_cmd(msg, cmd)
else
reply(msg, 'show|ping|traceから始まるコマンドのみサポート:-(')
end
end
end
if __FILE__ == $PROGRAM_NAME
HOSTS.each do |host, ipaddr|
Bot.new(host, ipaddr).start
end
Thread.stop
end
実行結果
XMPP用のボットスクリプトを実行。R1とR2のアカウントでXMPPサーバに接続し、XMPPクライアントからのメッセージを待ち受けします。メッセージを受け取ると、送信元のアカウント名とコマンドを表示します。
$ ruby bot.rb
I, [2015-12-08T23:56:14.884924 #16863] INFO -- : R1 starting
I, [2015-12-08T23:56:15.224579 #16863] INFO -- : R2 starting
D, [2015-12-08T23:56:16.813239 #16863] DEBUG -- : R1 admin@localhost/2a20c564 [show ip route]
D, [2015-12-08T23:56:20.036932 #16863] DEBUG -- : R1 admin@localhost/2a20c564 [show int desc]
D, [2015-12-08T23:56:24.352473 #16863] DEBUG -- : R1 admin@localhost/2a20c564 [show ip route]
D, [2015-12-09T00:00:04.743047 #16863] DEBUG -- : R1 admin@localhost/2a20c564 [show vrf]
XMPPクライアントからルータを選択して、コマンドを送信すると下記のように実行結果が返ってきます。
対応しているコマンドはshow、ping、traceのみとなります。copy running-config startup-configコマンドのようにプロンプトが返ってくるコマンドには未対応です。
おわりに
Ciscoルータとチャットできました。ちょっとしたコマンドの実行をしたい場合など、いちいちログインしなくても、コマンドを実行できるので便利です。ただし、補完機能がないため、CLIの恩恵を受けられません。
参考
- Rubyで多数のCiscoルータで複数のコマンドを実行する - Qiita
- Ciscoルータのポート一覧をRubyとSNMPで取得する - Qiita
- CiscoルータのポートをRuby/Sinatraで見える化して検証捗らせてみた - Qiita
- ルータとチャットしてみました - ネットワークプログラマビリティ勉強会 #6
- xmpp4r - RubyGems.org
- expect4r - RubyGems.org