はじめに
Netmikoとは、ネットワーク機器へのログイン、ログ取得、設定変更等を行うための、マルチベンダー対応のPythonライブラリです。
今回は、ヤマハのルータ(RTX、NVR)やファイアウォール(FWX)のプロンプト方式に対応したNetmikoクラスを作成してみました。
GitHub - netmiko
作成にあたり、公式ドキュメントに加え、以下記事も参考にさせて頂きました。ありがとうございますm(_ _)m
netmiko と TextFSM を利用してネットワーク機器の show コマンド結果をパースする
netmikoでネットワーク機器の種類を自動検出してコマンドを実行する
netmikoをYamahaに対応させてみた
以降で、実装時に考慮した点と、簡単なサンプルコードを書き残しておきます。
1. 考慮した点
1-1. コマンドモード
Netmikoのドキュメントを見ると、新規ベンダーを追加する時は「可能な限り、既存のCiscoSSHConnectionやBaseConnectionから継承したメソッドを再利用するべき。」と記載されています。
Steps for adding a new vendor
Cisco機器の設定変更では、下表の通りenable()
メソッドで特権EXECモードへ移行した後、send_config_set()
、send_config_from_file()
メソッドでグローバルコンフィグレーションモードへの移行と設定変更を行います。
一方Yamaha機器の設定変更では、enable()
メソッドで管理モードへの移行、send_config_set()
、send_config_from_file()
メソッドで管理モードへの移行と設定変更を行います。
enable()
メソッドは他ベンダー機器との統一性を図るために実装していますが、設定変更時の記述は必須ではないです。
Ciscoモード名 | プロンプト | Yamahaモード名 | プロンプト | モード移行のためのメソッド |
---|---|---|---|---|
ユーザEXEC | > | 一般 | > | メソッド不要 |
特権EXEC ※ | # | 管理 ※ | # | enable() |
グローバルコンフィグレーション | (config)# | 管理 ※ | # | send_config_set()、send_config_from_file() |
※モードに入る前に、enable or 管理パスワード入力が必要。 |
1-2. Config保存
管理モードで設定変更を行った後、exit(もしくはquit)で一般モードに移行する際、以下の通りConfigを保存するか聞かれます。勝手に保存されると困るケースもあると思うので、「Y/Nという文字列があったらNを返す」ようにして、一律保存しないようにしています。
保存したい場合は、設定Config内にsave
コマンドを含めるか、Netmikoのsave_config()
メソッドを使うようにします。
yamaha1# ip lan1 address 10.1.1.100/24
yamaha1# exit
Save new configuration ? (Y/N)
2. 環境準備
テストを実施するにあたり、RTX側で以下設定を行っています。
- ユーザパスワード、管理パスワードの設定
- コンソールの文字コードをSJISからASCIIに変更
- Telnet、SSHの有効化
yamaha1> show config
# RTX810 Rev.11.01.33 (Fri Apr 20 08:44:58 2018)
administrator password *
login user yamaha *
timezone +09:00
console character ascii
console lines infinity
console prompt yamaha1
ip route default gateway 192.168.100.1
ip lan1 address 10.1.1.40/24
ip lan2 address 192.168.100.100/24
telnetd service on
sshd service on
sshd host key generate *
3. サンプルコード
3-1. SSHログイン、設定変更、ログ取得、Config保存
RTX810にSSHログインし、LAN1のIPアドレスを変更してみます。確認のため、事前事後でshow status lan1
コマンドを取得し、アドレスを確認します。最後にConfig保存も行います。
from netmiko import ConnectHandler
device = {
"host": "192.168.100.100",
"username": "yamaha",
"password": "yamaha",
"secret": "yamaha", # administrator password
"device_type": "yamaha", # device type for yamaha SSH login
}
# connect
net_connect = ConnectHandler(**device)
# send show command (before)
output = net_connect.send_command("show status lan1")
print('---------------before log---------------', output, sep='\n')
# change ip address
config_commands = ['ip lan1 address 10.1.1.100/24']
output = net_connect.send_config_set(config_commands)
print('---------------change config---------------', output, sep='\n')
# send show command (after)
output = net_connect.send_command("show status lan1")
print('---------------after log---------------', output, sep='\n')
# save config
output = net_connect.save_config()
print('---------------save config---------------', output, sep='\n')
# disconnect
net_connect.disconnect()
実行結果を見ると、事前事後でIPアドレスが10.1.1.40/24から10.1.1.100/24に変更されているのが分かります。また、設定変更後、問題なくNが入力され、一般モードに移行できているのが分かります。Configも問題なくconfig0に保存されています。
$ python send_config_telnet.py
---------------before log---------------
LAN1
Description:
IP Address: 10.1.1.40/24
Ethernet Address: 00:a0:de:XX:XX:XX
Operation mode setting: Type (Link status)
PORT1: Auto Negotiation (Link Down)
PORT2: Auto Negotiation (Link Down)
PORT3: Auto Negotiation (Link Down)
PORT4: Auto Negotiation (Link Down)
Maximum Transmission Unit(MTU): 1500 octets
Promiscuous mode: OFF
Transmitted: 0 packet (0 octet)
IPv4(all/fastpath): 0 packet / 0 packet
IPv6(all/fastpath): 0 packet / 0 packet
Received: 0 packet (0 octet)
IPv4: 0 packet
IPv6: 0 packet
---------------change config---------------
administrator
Password:
yamaha1# ip lan1 address 10.1.1.100/24
yamaha1# exit
Save new configuration ? (Y/N)N
yamaha1>
yamaha1>
yamaha1>
---------------after log---------------
LAN1
Description:
IP Address: 10.1.1.100/24
Ethernet Address: 00:a0:de:XX:XX:XX
Operation mode setting: Type (Link status)
PORT1: Auto Negotiation (Link Down)
PORT2: Auto Negotiation (Link Down)
PORT3: Auto Negotiation (Link Down)
PORT4: Auto Negotiation (Link Down)
Maximum Transmission Unit(MTU): 1500 octets
Promiscuous mode: OFF
Transmitted: 0 packet (0 octet)
IPv4(all/fastpath): 0 packet / 0 packet
IPv6(all/fastpath): 0 packet / 0 packet
Received: 0 packet (0 octet)
IPv4: 0 packet
IPv6: 0 packet
---------------save config---------------
Saving ... CONFIG0 Done .
3-2. Telnetログイン、設定変更(Configファイル)、ログ取得
先ほどはSSHログインでしたが、device_typeをyamaha_telnet
とすることで、Telnetログインも可能です。
また、先ほどはsend_config_set()
メソッドで、リスト形式のデータを読み込んで設定変更しましたが、send_config_from_file()
でConfigファイルを流し込むことも可能です。今回は、以下のyamaha_config.txtでスタティックルート2行の追加とConfig保存(save)を行い、事前事後でConfig確認を行います。
ip route 10.1.2.0/24 gateway 10.1.1.1
ip route 10.1.3.0/24 gateway 10.1.1.1
save
from netmiko import ConnectHandler
device = {
"host": "192.168.100.100",
"username": "yamaha",
"password": "yamaha",
"secret": "yamaha",
"device_type": "yamaha_telnet", # device type for yamaha telnet login
}
# connect
net_connect = ConnectHandler(**device)
# send show command (before)
output = net_connect.send_command("show config | grep route")
print('---------------before log---------------', output, sep='\n')
# add static routes
output = net_connect.send_config_from_file(config_file='yamaha_config.txt')
print('---------------change config---------------', output, sep='\n')
# send show command (after)
output = net_connect.send_command("show config | grep route")
print('---------------after log---------------', output, sep='\n')
# disconnect
net_connect.disconnect()
実行結果を見ると、スタティックルートが問題なく追加されています。事前にConfig保存を行ったため、管理モードから抜ける時にY/Nを聞かれませんでしたが、後続処理もできています。
$ python send_config_file.py
---------------before log---------------
Searching ...
ip route default gateway 192.168.100.1
---------------change config---------------
administrator
Password:
yamaha1# ip route 10.1.2.0/24 gateway 10.1.1.1
yamaha1# ip route 10.1.3.0/24 gateway 10.1.1.1
yamaha1# save
Saving ... CONFIG0 Done .
yamaha1#
yamaha1# exit
yamaha1>
yamaha1>
yamaha1>
yamaha1>
yamaha1>
---------------after log---------------
Searching ...
ip route default gateway 192.168.100.1
ip route 10.1.2.0/24 gateway 10.1.1.1
ip route 10.1.3.0/24 gateway 10.1.1.1
##3-3. 自動検出、TextFSMによるパース
最後に、Netmikoの自動検出機能を使ったdevice_typeの判定を行います。判定方法は、show copyright
コマンドを取得し、返ってきた値の中に「Yamaha Corporation」が含まれる場合、device_typeを「yamaha」としています。
合わせて、show environment
コマンドのTextFSMによるパースも行います。出力結果をパースする場合、send_command()
に引数use_textfsm=True
を追加します。(TextFSMのTemplateファイルは自作したものを用意。)
from netmiko.ssh_autodetect import SSHDetect
from netmiko import ConnectHandler
from pprint import pprint
device = {
"host": "192.168.100.100",
"username": "yamaha",
"password": "yamaha",
"secret": "yamaha",
"device_type": "autodetect", # set device type to "autodetect"
}
# autodetect device_type
guesser = SSHDetect(**device)
best_match = guesser.autodetect()
# display detected device_type
print("device_type: " + best_match)
# set device_type and connect
device['device_type'] = best_match
connection = ConnectHandler(**device)
# send show command and display output
output = connection.send_command('show environment')
print('---------------show environment---------------', output, sep='\n')
# send show command and display parsed output with textfsm
output = connection.send_command('show environment', use_textfsm=True)
print('---------------show environment (parsed)---------------')
pprint(output)
# disconnect
connection.disconnect()
自動検出機能により、「yamaha」と判定されています。パースも問題なくできました。
$ python auto_detect.py
device_type: yamaha
---------------show environment---------------
RTX810 BootROM Ver. 1.00
RTX810 FlashROM Table Ver. 1.00
RTX810 Rev.11.01.33 (Fri Apr 20 08:44:58 2018)
main: RTX810 ver=00 serial=S3KXXXXXX MAC-Address=00:a0:de:00:00:00 MAC-Addre
ss=00:a0:de:aa:aa:aa
CPU: 8%(5sec) 1%(1min) 0%(5min) Memory: 7% used
Packet-buffer: 0%(small) 0%(middle) 5%(large) 0%(huge) used
Firmware: internal
Config. file: config0 Default config. file: config0
Boot time: 2019/10/01 22:00:17 +09:00
Current time: 2019/10/05 21:06:54 +09:00
Elapsed time from boot: 3days 23:06:37
Security Class: 1, FORGET: ON, TELNET: OFF
---------------show environment (parsed)---------------
[{'buffer_huge': '0',
'buffer_large': '5',
'buffer_middle': '0',
'buffer_small': '0',
'cpu_1_min': '1',
'cpu_5_min': '0',
'cpu_5_sec': '1',
'date': '2019/10/05',
'forget': 'ON',
'hardware': 'RTX810',
'mac': ['00:a0:de:00:00:00', '00:a0:de:aa:aa:aa'],
'mem': '7',
'sec_class': '1',
'serial': 'S3KXXXXXX',
'telnet': 'OFF',
'temperature': '',
'time': '21:06:55',
'timezone': '+09:00',
'uptime': '3days 23:06:38',
'version': '11.01.33'}]
最後に
今回開発したものは、Netmikoにプルリクエストを出しマージされています。(ただし、Telnetログインの不具合があり、2021/1/1に再度プルリクエストを出しています。)
TextFSMのTemplateファイルは、後日プルリクエストを出したいと思います。