コマンドの実行結果によってネットワーク監視灯(パトライト)を点滅させてみた
コマンドを実行した際の終了状態によって、ネットワーク監視灯(通称:パトライト)に操作して、視覚的に通知してみました。 例として、Linux(Ubuntu)のfpingをインストールして、Ping OKなら緑点滅、Ping NGなら赤点滅する動画です。
Ping NGでパトライト🚨を赤点滅させてみた。コマンドの終了状態に応じてパトライトを緑点滅や赤点滅するシェルスクリプトを作成 pic.twitter.com/5lzKJhvSUB
— kooshin (@kooshin) 2020年2月11日
はじめに
コマンドの実行に時間がかかった場合(例えば、rbenvによるRubyのインストールや、ネットワークが遅くてdocker pullが異常に時間がかかる場合など)、終わるまでTwitterなどで時間をつぶすことがあると思います。しかしながら、コマンドの終了に気が付かなく、Twitterで時間を溶かしてしまうことが私は多々あります。 このため、コマンドの実行終了をパトライト経由で通知してみました。
ネットワーク監視表示灯(以下、パトライト)にコマンドの実行が終了した場合に、緑点滅や赤点滅で気づけるように、ラッパーする感じのシェルスクリプトを作成してみました。
何かしらのコマンドを実行して、終了状態(exit status/return code)が、正常であればパトライトを緑点滅、異常であればパトライトを赤点滅させてみます。
パトライトをシェルから操作する方法は、下記を参照してください、
動作環境
動作環境は下記のとおりです。 今回はLinux上で動作させています。
仕組み
自作したシェルスクリプトのpatlite
スクリプトの引数に、コマンドを指定すると、コマンドの終了状態に応じてパトライトを操作します。
コマンドの終了状態によって、パトライトを緑点滅や赤点滅させます。
実行の流れ
大まかに下記のような流れでpatlite
スクリプトは動作しています。
patlite
スクリプトの引数に、終了を通知したいコマンドを指定patlite
スクリプトを実行- 開始時にパトライトを緑点灯。それ以外は消灯
- 引数のコマンドを実行
- コマンドの終了状態(exit status/return code)をもとに判断
- 終了状態が0ならパトライトを緑点滅。0以外ならパトライトを赤点滅
- 割込シグナルで途中終了した場合、パトライトを赤点滅
- 終了
patliteスクリプト
シェルスクリプトのpatlite
スクリプトのコードは下記のとおりです。
変数として定義した、パトライトのIPアドレスIPADD
やTCPポート番号PORT
を変更することで、他の環境でも使えると思います。
終了状態 | パトライトの操作 |
---|---|
正常終了(0) | 緑点滅(点滅パターン1) |
異常終了(0以外) | 赤点滅(点滅パターン2) |
割込終了(シグナル割込) | 赤点滅(点滅パターン1) |
#!/bin/sh # パトライトのIPアドレスとTCPポート番号 IPADDR="192.168.11.101" PORT="10000" # 緑の表示灯のみ点灯 /bin/echo -ne '\x58\x58\x53\x00\x00\x06\x00\x00\x01\x00\x00\x00' | /bin/nc -q0 $IPADDR $PORT # 途中終了の場合、異常と判断し、赤の表示灯を点滅(点滅パターン1) trap "/bin/echo -ne '\x58\x58\x53\x00\x00\x06\x02\x00\x00\x00\x00\x00' | /bin/nc -q0 $IPADDR $PORT; exit 1" 1 2 3 15 # 引数をコマンドとして実行 $* RETURN_CODE=$? case "$RETURN_CODE" in # return codeが0の場合は、正常と判断し、緑の表示灯を点滅(点滅パターン1) 0 ) /bin/echo -ne '\x58\x58\x53\x00\x00\x06\x00\x00\x02\x00\x00\x00' | /bin/nc -q0 $IPADDR $PORT ;; # return codeが0以外の場合は、異常と判断し、赤の表示灯を点滅(点滅パターン2) * ) /bin/echo -ne '\x58\x58\x53\x00\x00\x06\x03\x00\x00\x00\x00\x00' | /bin/nc -q0 $IPADDR $PORT ;; esac exit $RETURN_CODE
実行例
サンプルとして、fpingをインストールして、pingしてみました。
patilite
スクリプトの引数に、各コマンドを指定しています。
# patliteスクリプトに実行権付与 $ chmod +x patlite # fpingをインストール(ちょっと時間がかかる) $ ./patlite sudo apt install fping # Ping OKな例 $ ./patlite fping 192.168.10.1 -c 1 # Ping NGな例 $ ./patlite fping 192.168.10.2 -c 1
実行結果(ターミナル画面)
実行結果は、patlite
スクリプトに実行コマンドを指定する以外、
特に変化はありません。
実行結果(パトライト)
パトライトとターミナル画面を撮影した動画です(Twitterを参照)。
Ping NGでパトライト🚨を赤点滅させてみた。コマンドの終了状態に応じてパトライトを緑点滅や赤点滅するシェルスクリプトを作成 pic.twitter.com/5lzKJhvSUB
— kooshin (@kooshin) 2020年2月11日
おわりに
簡単な仕組みでパトライトを操作できるため、様々なことに応用できると思います。
きっと、ご自宅に必須のアイテムになること間違いなしです。 この機会に皆さんご自宅用に買いましょう。
ネットワーク監視表示灯(パトライト)をシェルから操作してみた
@wakamotojpさんのncコマンドを利用する例をもとにして、ネットワーク監視表示灯(通称:パトライト)をシェルから操作してみました。
パトライトは、こんな感じで使ってる
— wakamotojp (@wakamotojp) 2020年2月10日
% echo -n -e '\x57'${PAC} | nc -q1 ${PAT} 10000
はじめに
ネットワーク監視表示灯は、SOCKET通信による操作をサポートしています。 TCPやUDPで、専用のコマンドを送信することで、パトライトの表示灯を操作できます。
下記ではSNMPを利用してパトライトを操作しましたが、もっと簡単にSOCKET通信を利用して、パトライトの表示灯を操作できます。
ソケット通信(SOCKET通信)
ヤフオクで入手したパトライトのNHシリーズ「NHP-3FB1」は、SOCKET通信をサポートしています。
SOCKET通信で専用コマンドをパトライトに送信することで、パトライトの表示灯を操作できます。 NH-FBシリーズでは、2種類のコマンドをサポートしています。
コマンド | 詳細 |
---|---|
PHNコマンド | PHN-3FBシリーズの制御プロトコル。 対応する表示灯は緑・黄・赤のみ。点滅パターン2が利用できない |
PNSコマンド | NHシリーズの表示灯・ブザーを制御する専用コマンド。すべての表示灯と状態に対応 |
ソケット通信設定
SOCKET通信を利用するために、Webブラウザからパトライトの設定を下記のようにします。 デフォルト値のまま利用します。
PHNコマンド
PHNコマンドは、古いパトライトのPNH-3FBシリーズ用のコマンドです。 2バイトのバイナリデータをTCP/UDPで送信することで、表示灯を操作します。
下図の通り、1バイト目は固定値で、2バイト目のビットで表示灯の状態を指定します。
古い機種用のため、NHシリーズの点滅パターン2などに追従できていません。対応していない表示灯の状態は下記のとおりです。
- 表示灯の赤・黄・緑の点滅パターン2
- 表示灯の青・白の点灯・点滅パターン1・点滅パターン2
- ブザーの鳴動パターン3、鳴動パターン4
表示灯の状態をビットで指定
nc(netcat)
コマンドで、表示灯の状態指定のバイナリデータをTCPで送信します。
ここでは、指定しやすいよう、2バイト目は2進数のビットで指定できるようにしています。
echo -ne '\x57\x'$(echo "obase=16;ibase=2;00000001" | bc) | nc -q1 192.168.11.101 10000
echoコマンドで、バイナリデータをncコマンドにパイプで渡しています。
\x57
は固定値です。
下記の部分で表示灯の状態を2進数のビットで指定しています。
$(echo "obase=16;ibase=2;00000001" | bc)
00000001
部分で、表示灯の状態を2進数のビットを指定しています。この例は、赤の表示灯を点灯する意味となります。
表示灯の制御コマンドを送信
nc
コマンドで、宛先IPアドレスと宛先TCPポート番号を指定しています。
nc -q1 192.168.11.101 10000
192.168.11.101が宛先IPアドレスで、10000が宛先TCPポート番号です。
実行例
下記の例は、表示灯の赤を制御しています。 点灯・消灯・点滅パターン1のみ指定できます。
# 赤点灯、それ以外は消灯 echo -ne '\x57\x'$(echo "obase=16;ibase=2;00000001" | bc) | nc -q1 192.168.11.101 10000 # 赤点滅(パターン1)、それ以外は消灯 echo -ne '\x57\x'$(echo "obase=16;ibase=2;00100000" | bc) | nc -q1 192.168.11.101 10000 # 緑・黄・赤 消灯 echo -ne '\x57\x'$(echo "obase=16;ibase=2;00000000" | bc) | nc -q1 192.168.11.101 10000
PNSコマンド
PNSコマンドは、NHシリーズの表示灯・ブザーを制御するコマンドです。 PNHコマンドと同じポート番号でバイナリデータを送信します。
下図の通り、合計12バイトのバイナリデータで、表示灯の状態指定は後半6バイトを利用します。 表示灯ごとに状態を1バイトで指定できるため、点滅パターン2に対応しています。
表示灯の状態を16進数で指定
echoコマンドで、16進数のバイナリデータをベタ打ちしています。
表示灯の状態指定の場合、前半6バイトx58\x58\x53\x00\x00\x06
は固定値です。
後半6バイトで表示灯ごとの状態を指定しています。
表示灯の状態をクリアするコマンドのみ、全体で6バイトの\x58\x58\x43\x00\x00\x00
となります。
# 赤点灯 echo -ne '\x58\x58\x53\x00\x00\x06\x01\x09\x09\x09\x09\x09' | nc -q1 192.168.11.101 10000 # 赤点滅パターン1 echo -ne '\x58\x58\x53\x00\x00\x06\x02\x09\x09\x09\x09\x09' | nc -q1 192.168.11.101 10000 # 赤点滅パターン2 echo -ne '\x58\x58\x53\x00\x00\x06\x03\x09\x09\x09\x09\x09' | nc -q1 192.168.11.101 10000 # 黄点灯 echo -ne '\x58\x58\x53\x00\x00\x06\x09\x01\x09\x09\x09\x09' | nc -q1 192.168.11.101 10000 # 黄点滅パターン1 echo -ne '\x58\x58\x53\x00\x00\x06\x09\x02\x09\x09\x09\x09' | nc -q1 192.168.11.101 10000 # 黄点滅パターン2 echo -ne '\x58\x58\x53\x00\x00\x06\x09\x03\x09\x09\x09\x09' | nc -q1 192.168.11.101 10000 # 緑点灯 echo -ne '\x58\x58\x53\x00\x00\x06\x09\x09\x01\x09\x09\x09' | nc -q1 192.168.11.101 10000 # 緑点滅パターン1 echo -ne '\x58\x58\x53\x00\x00\x06\x09\x09\x02\x09\x09\x09' | nc -q1 192.168.11.101 10000 # 緑点滅パターン2 echo -ne '\x58\x58\x53\x00\x00\x06\x09\x09\x03\x09\x09\x09' | nc -q1 192.168.11.101 10000 # 表示灯をクリア(リセット) echo -ne '\x58\x58\x43\x00\x00\x00' | nc -q1 192.168.11.101 10000
表示灯の操作結果
表示灯の操作で、下記の状態になるように指定した結果は下記のとおりです。
- 赤:点滅パターン1
- 黄:点滅パターン2
- 緑:点灯
- その他消灯
# 赤点滅パターン1、黄点滅パターン2、緑点灯、その他消灯 echo -ne '\x58\x58\x53\x00\x00\x06\x02\x03\x01\x00\x00\x00' | nc -q1 192.168.11.101 10000
パトライトをSNMPで操作して点滅させてみました🚨 pic.twitter.com/AH02c7ujAS
— kooshin (@kooshin) 2020年2月9日
おわりに
nc(netcat)
コマンドを利用することで、シェルから簡単にパトライトを操作することができます。
長時間かかるコマンドの実行が完了したら、
パトライトを光らせてみたり、
CI/CDの失敗時に光らせてみたり、様々なことに活用できます。
皆さん、ぜひとも自宅でパトライトを活用してみてください。
ネットワーク監視表示灯(パトライト)をSNMPで操作してみた
三色に光るネットワーク監視表示灯(通称:パトライト)を買ったのでSNMPでリモートから操作してみました。パトライトの光は結構眩しいです。
パトライト🚨と戯れる土曜の午後 pic.twitter.com/cXEttqLO1p
— kooshin (@kooshin) 2020年2月8日
はじめに
パトライト社のネットワーク監視表示灯(以下、パトライト)をヤフオクで落札し、自宅で使ってみました。
ヤフオクで入手した「NHP-3FB1」はすでに生産終了品のため新規購入はできません。 説明書やファームウェアは、パトライト社の公式サイトからダウンロード可能です(2020年2月9日時点)。
パトライトの設定
取扱説明書に様々な設定方法や操作方法が載っています。 パトライト社で無料の会員登録をすることで、取扱説明書はダウンロードできます。 一応、個人でも会員登録可能でした。
パトライトの初期化
パトライトに設定が残っていたので、 工場出荷時状態に初期化します。 初期化の手順は下記のとおりです。
- 電源ケーブルを抜く
- ボリューム切り替えスイッチ(VOL.)を「OFF」にする
- CLEARスイッチとTESTスイッチを押しながら、電源ケーブルを差し込む
- 表示灯が全点灯した状態でブザー音が鳴ります。ブザー音が止まったら、2つのスイッチを離す
- 表示灯が消灯すると起動完了
Webブラウザでパトライトにログイン
初期化後のパトライトのIPアドレスは192.168.10.1/24です。
パソコンに192.168.10.2/24など、同じセグメントのIPアドレスを割り当て、LANケーブルを接続します。
Webブラウザから「http://192.168.10.1」にアクセスすると、下記のログイン画面が表示されます。
初期化後のパスワードはpatlite
で、ログインできます。
WebブラウザでSNMP設定
ログイン後は、Webブラウザで「SNMP設定」をします。 今回は動作試験が目的のため、下記の通り、デフォルトの値のまま利用します。
WebブラウザでIPアドレス設定
「システム設定」からIPアドレスが変更できます。 今回は、下記のようにIPアドレスを設定しました。 IPアドレス設定を変更した場合は、設定反映のため再起動します。
Webブラウザで表示灯操作
「表示灯操作」から表示灯の操作ができるため、手軽に動作確認できます。 下記の画面では、下記のように操作しています。
- 本体操作>表示灯操作
- 赤:点滅パターン1
- 黄:点灯
SNMPの準備
SNMPで表示灯を操作してみます。
動作環境は、Windows10上のWSLにインストールしたUbuntu18.04で、net-snmp
を利用します。
net-snmpのインストール
Ubuntu 18.04のnet-snmp
とsnmp-mibs-downloader
を
インストールします。
ついでに、Cisco社のMIBもダウンロードします。
MIBファイルのダウンロードはsnmp-mibs-downloader
を利用します。
# パッケージをインストール $ sudo apt install net-snmp snmp-mibs-downloader # Cisco社のMIBのダウンロード設定 $ cat << END | sudo tee /etc/snmp-mibs-downloader/cisco.conf HOST=ftp://ftp.cisco.com DIR=pub/mibs/v2/ CONF=ciscolist DEST=cisco END # Cisco社のダウンロードするMIBを指定 $ cat << END | sudo tee /etc/snmp-mibs-downloader/ciscolist CISCO-IMAGE-MIB.my CISCO-IMAGE-MIB CISCO-SMI.my CISCO-SMI MPLS-VPN-MIB.my MPLS-VPN-MIB CISCO-BGP4-MIB.my CISCO-BGP4-MIB END # MIBのダウンロード $ sudo download-mibs cisco # エラーを吐くMIBを削除 $ sudo rm /var/lib/snmp/mibs/ietf/IPSEC-SPD-MIB $ sudo rm /var/lib/snmp/mibs/ietf/SNMPv2-PDU $ sudo rm /var/lib/snmp/mibs/ietf/IPATM-IPMC-MIB $ sudo rm /var/lib/snmp/mibs/iana/IANA-IPPM-METRICS-REGISTRY-MIB # SNMPコマンドの共通設定 $ cat << END | sudo tee /etc/snmp/snmp.conf mibdirs /var/lib/snmp/mibs/iana:/var/lib/snmp/mibs/ietf:/var/lib/snmp/mibs/cisco mibs all showMibErrors no mibWarningLevel 0 strictCommentTerm no mibAllowUnderline no END
MIBファイル
SNMP用のMIBファイルは、ファームウェアに同梱されており、パトライト社から会員登録することでダウンロードできます。 「NH-FBシリーズ用ファームウェア」から「バージョン:1.46」をダウンロードしました。
ファームウェアにMIBファイルpatlite_nhSPL_20170105.mib
が同梱されています。
今回はMIBファイルを/var/lib/snmp/mibs/iana/
に保存しました。
SNMPで操作
取扱説明書にSNMPのOID情報が記載されています。
今回はnet-snmp
ツールのsnmpset
コマンドで表示灯を操作してみます。
SNMPオブジェクト
表示灯を制御するSNMPオブジェクトは下記のとおりです。
- 表示灯の状態を変える場合、必ず
controlLightControlState
とcontrolLightControlTimer
の2つの指定が必要です。 - 表示等をクリア(リセット)する場合、
controlLightSnmpClear
の1つのみの指定です。
SNMPオブジェクト名 | OID | 内容 | 操作 |
---|---|---|---|
controlLightControlState | .1.3.6.1.4.1.20440.4.1.5.1.2.1.2 | 各表示灯の状態を設定。遅延時間とセットで利用する必要あり | turn-on、turn-off、blinking-pattern1、blinking-pattern2 |
controlLightControlTimer | .1.3.6.1.4.1.20440.4.1.5.1.2.1.3 | 状態変化の遅延時間を設定 | 秒数を指定 |
controlLightSnmpClear | .1.3.6.1.4.1.20440.4.1.5.1.3 | 状態をクリアする | 値はexecute(1) |
色指定
表示灯の色は下記のパラメータが用意されています。
上記のSNMPのオブジェクト名の末尾に追加することで、
表示灯を指定できます。
赤の表示灯はcontrolLightControlState.red
やcontrolLightControlState.yellow
で指定できます。
パラメータ | 値(Integer) | 表示灯 |
---|---|---|
red | 1 | 赤色の表示灯 |
yellow | 2 | 黄色の表示灯 |
green | 3 | 緑色の表示灯 |
blue | 4 | 青色の表示灯 |
clear | 5 | 白色の表示灯 |
buzzar | 6 | ブザー |
状態指定
controlLightControlState
に指定可能な状態は下記のとおりです。
表示灯とブザーの場合で、動作が異なります。
パラメータ | 値(Integer) | 表示灯の動作 | ブザーの動作 |
---|---|---|---|
turn-on | 1 | 表示灯を点灯 | 消音 |
turn-off | 2 | 表示灯を消灯 | サウンドパターン1 |
blinking-pattern1 | 3 | 表示灯を点滅(点滅パターン1) | サウンドパターン2 |
blinking-pattern2 | 5 | 表示灯を点滅(点滅パターン2) | サウンドパターン3 |
soud-pattern4 | 6 | - | サウンドパターン4 |
nop | 4 | 状態変化なし | 状態変化なし |
snmpsetコマンドで表示灯を操作
snmpset
コマンドで、赤の表示灯を操作してみます。
他の表示灯に変更する場合は、controlLightControlState.red
とcontrolLightControlTimer.red
の末尾の.red
を.yellow
や.green
に変更します。
# 表示灯をすべて消灯(クリア)する $ snmpset -v2c -cprivate 192.168.11.102 controlLightSnmpClear.0 i execute PATLITE-NH-SERIES::controlLightSnmpClear.0 = INTEGER: execute(1) # 赤をすぐに点灯する $ snmpset -v2c -cprivate 192.168.11.102 controlLightControlState.red i turn-on controlLightControlTimer.red i 0 PATLITE-NH-SERIES::controlLightControlState.red = INTEGER: turn-on(2) PATLITE-NH-SERIES::controlLightControlTimer.red = INTEGER: 0 # 赤をすぐに点滅パターン1で点滅する $ snmpset -v2c -cprivate 192.168.11.102 controlLightControlState.red i blinking-pattern1 controlLightControlTimer.red i 0 PATLITE-NH-SERIES::controlLightControlState.red = INTEGER: blinking-pattern1(3) PATLITE-NH-SERIES::controlLightControlTimer.red = INTEGER: 0 # 赤をすぐに点滅パターン2で点滅する $ snmpset -v2c -cprivate 192.168.11.102 controlLightControlState.red i blinking-pattern2 controlLightControlTimer.red i 0 PATLITE-NH-SERIES::controlLightControlState.red = INTEGER: blinking-pattern2(5) PATLITE-NH-SERIES::controlLightControlTimer.red = INTEGER: 0 # 赤をすぐに消灯する $ snmpset -v2c -cprivate 192.168.11.102 controlLightControlState.red i turn-off controlLightControlTimer.red i 0 PATLITE-NH-SERIES::controlLightControlState.red = INTEGER: turn-off(1) PATLITE-NH-SERIES::controlLightControlTimer.red = INTEGER: 0
表示灯の操作結果
表示灯の操作で、下記の状態になるように指定した結果は下記のとおりです。
- 状態クリア
- 赤:点滅パターン1
- 黄:点滅パターン2
- 緑:点灯
# 表示灯をすべて消灯(クリア)する $ snmpset -v2c -cprivate 192.168.11.102 controlLightSnmpClear.0 i execute PATLITE-NH-SERIES::controlLightSnmpClear.0 = INTEGER: execute(1) # 赤をすぐに点滅パターン1で点滅する $ snmpset -v2c -cprivate 192.168.11.102 controlLightControlState.red i blinking-pattern1 controlLightControlTimer.red i 0 PATLITE-NH-SERIES::controlLightControlState.red = INTEGER: blinking-pattern1(3) PATLITE-NH-SERIES::controlLightControlTimer.red = INTEGER: 0 # 黄をすぐに点滅パターン2で点滅する $ snmpset -v2c -cprivate 192.168.11.102 controlLightControlState.yellow i blinking-pattern1 controlLightControlTimer.yellow i 0 PATLITE-NH-SERIES::controlLightControlState.yellow = INTEGER: blinking-pattern1(3) PATLITE-NH-SERIES::controlLightControlTimer.yellow = INTEGER: 0 # 緑をすぐに点灯する $ snmpset -v2c -cprivate 192.168.11.102 controlLightControlState.green i turn-on controlLightControlTimer.green i 0 PATLITE-NH-SERIES::controlLightControlState.green = INTEGER: turn-on(2) PATLITE-NH-SERIES::controlLightControlTimer.green = INTEGER: 0
パトライトをSNMPで操作して点滅させてみました🚨 pic.twitter.com/AH02c7ujAS
— kooshin (@kooshin) 2020年2月9日
おわりに
ネットワーク監視表示灯を利用して、様々なイベントの通知を視覚的にわかりやすく通知することが可能になります。色々使いみちがたくさんあるので、活用できると思います。
ネットワークエンジニアのためのIPアドレス表現 for Python
Pythonとipaddress
ライブラリで、
ネットワークエンジニアがよく使うIPアドレスを表現する方法を例示します。
from ipaddress import ip_interface >>> ip = ip_interface("192.168.1.124/24") >>> ip IPv4Interface('192.168.1.124/24') >>> str(ip) '192.168.1.124/24'
はじめに
PythonでIPアドレスを取り扱う方法を調査してみました。 過去に、Rubyで調査したので、ほぼ同じことをできるか、Pythonで調査しました。
Python3.3から標準モジュールとしてipaddress
ライブラリが追加となっています。
より詳細な解説は、ライブラリのドキュメントを参照してください。
ここでは、Python3.7.4の環境で調査しました。
IPアドレスの表現方法
ipaddress
ライブラリには、IPアドレスを表現するために、3つの関数(ファクトリ関数)が定義されています。
ファクトリ関数 | 生成されるクラス | 用途 |
---|---|---|
ip_address() |
IPv4Address , IPv6Address |
IPv4/IPv6のホストアドレスを表現する。マスク情報は付加できない。 |
ip_network() |
IPv4Network, IPv6Network |
IPv4/IPv6のネットワークアドレスを表現する。IPプレフィックスとしてよく使う表現。ホストアドレスは表現できない。 |
ip_interface() |
IPv4Interface , IPv6Interface |
IPv4/IPv4のインタフェースアドレスを表現する。ルータのIPアドレスを扱うのならこっち。 |
ここでは、取り扱いやすいip_interface()
関数を主で利用します。必要に応じて、他の関数を利用してください。
ip_interface()
関数から生成されるIPv4Interface
クラスから、IPv4Address
クラスとIPv4Network
クラスが生成できます。
from ipaddress import ( ip_network, ip_address, ip_interface ) # ホストアドレス(IPv4Address) >>> ip_address("192.168.1.124") IPv4Address('192.168.1.124') # ネットワークアドレス(IPv4Network) >>> ip_network("192.168.1.0/24") IPv4Network('192.168.1.0/24') # インタフェースアドレス(IPv4Interface) >>> ip_interface("192.168.1.124/24") IPv4Interface('192.168.1.124/24') # ip_network()はネットワークアドレスを指定しないといけない # ホストアドレスを指定すると、デフォルトはエラーとなる >>> ip_network("192.168.1.124/24") ValueError: 192.168.1.124/24 has host bits set # strict=Falseを指定すると、ネットワークアドレスに変換する >>> ip_network("192.168.1.124/24", strict=False) IPv4Network('192.168.1.0/24') # IPv4Interfaceクラスから、IPv4Addressクラスを生成 # ip_interface() => ip_address()に変換 >>> ip = ip_interface("192.168.1.124/24") >>> ip.ip IPv4Address('192.168.1.124') # IPv4Interfaceクラスから、IPv4Networkクラスを生成 # ip_interface() => ip_network()に変換 >>> ip = ip_interface("192.168.1.124/24") >>> ip.network IPv4Network('192.168.1.0/24')
色々なIPアドレスの読み込み
ip_interface()
関数を前提として、IPアドレスを読み込みます。
from ipaddress import ip_interface # プレフィックス長を指定したIPアドレス >>> ip = ip_interface("192.168.1.124/24") >>> ip IPv4Interface('192.168.1.124/24') >>> str(ip) '192.168.1.124/24' # プレフィックス長を省略すると/32になる >>> ip = ip_interface("192.168.1.124") >>> ip IPv4Interface('192.168.1.124/32') >>> str(ip) '192.168.1.124/32' # サブネットマスクで指定も可能 >>> ip = ip_interface("192.168.1.124/255.255.255.0") >>> ip IPv4Interface('192.168.1.124/24') >>> str(ip) '192.168.1.124/24' # 10進数からIPアドレスに変換 >>> ip = ip_interface(0) >>> ip IPv4Interface('0.0.0.0/32') >>> str(ip) '0.0.0.0/32' # 16進数からIPアドレスに変換 >>> ip = ip_interface(0xffffffff) >>> ip IPv4Interface('255.255.255.255/32') >>> str(ip) '255.255.255.255/32'
よく使うIPアドレスの表現
from ipaddress import ip_interface # IPアドレス指定 >>> ip = ip_interface("192.168.1.124/24") >>> ip IPv4Interface('192.168.1.124/24') # プレフィックス表記(1) >>> str(ip) '192.168.1.124/24' # プレフィックス表記(2) >>> ip.with_prefixlen '192.168.1.124/24' # サブネットマスク表記 >>> ip.with_netmask '192.168.1.124/255.255.255.0' # ホストアドレス >>> ip.ip IPv4Address('192.168.1.124') >>> str(ip.ip) '192.168.1.124' # サブネットマスク >>> ip.netmask IPv4Address('255.255.255.0') >>> str(ip.netmask) '255.255.255.0' # プレフィックス長 >>> ip.network.prefixlen 24 # ネットワークアドレス >>> ip.network IPv4Network('192.168.1.0/24') >>> str(ip.network) '192.168.1.0/24' >>> ip.network.network_address IPv4Address('192.168.1.0') >>> str(ip.network.network_address) '192.168.1.0' # ホストマスク/ワイルドカードマスク >>> ip.hostmask IPv4Address('0.0.0.255') >>> str(ip.hostmask) '0.0.0.255' # ブロードキャストアドレス(1) >>> ip.network.broadcast_address IPv4Address('192.168.1.255') >>> str(ip.network.broadcast_address) '192.168.1.255' # ブロードキャストアドレス(2) >>> ip_interface("0.0.0.0/0").network.broadcast_address IPv4Address('255.255.255.255') >>> str(ip_interface("0.0.0.0/0").network.broadcast_address) '255.255.255.255'
その他の表現
from ipaddress import ip_interface # IPアドレス指定 >>> ip = ip_interface("192.168.1.124/24") >>> ip IPv4Interface('192.168.1.124/24') # 数値 >>> int(ip) 3232235900 # DNS逆引きレコード >>> ip.reverse_pointer '124/24.1.168.192.in-addr.arpa' # ホストアドレスの配列(ネットワークアドレスとブロードキャストを除く) >>> ip.network.hosts() <generator object _BaseNetwork.hosts at 0x7f87841d2430> >>> list(ip.network.hosts()) [IPv4Address('192.168.1.1'), IPv4Address('192.168.1.2'), ~略~, IPv4Address('192.168.1.254')] # 192.168.1.254~192.168.2.2までを表現 >>> ip = ip_interface("192.168.1.254/24") >>> ip + 1 IPv4Interface('192.168.1.255/32') >>> ip + 2 IPv4Interface('192.168.2.0/32') >>> for i in range(5): ... ip + i ... IPv4Interface('192.168.1.254/32') IPv4Interface('192.168.1.255/32') IPv4Interface('192.168.2.0/32') IPv4Interface('192.168.2.1/32') IPv4Interface('192.168.2.2/32')
プレフィックスの表記
プレフィックスの計算には、ip_network()
関数が便利なため、
ここでは、ip_network()
を利用します。
サマライズする関数はcollapse_addresses()
関数です。
collapse_addresses
に、IPv4Network形式のアドレスリストを渡すことで、
適時サマライズしてくれます。
プレフィックスの分割は、IPv4Network.subnets()
関数です。
現在のプレフィックス長に値を足す場合はprefixlen_diff=
パラメータを使います。
新しいプレフィックス長にする場合はnew_prefix=
パラメータを使います。
from ipaddress import ( ip_network, # ★ip_network()関数を利用 collapse_addresses, ) # 複数のプレフィックスをサマライズする >>> prefixes = map(ip_network, [ ... "192.168.0.0/25", ... "192.168.0.128/25", ... "192.168.1.0/24", ... "192.168.3.0/24", ... "192.168.4.0/24", ... "192.168.5.0/26", ... "192.168.128.0/22", ... "192.168.132.0/22", ... "192.168.128.0/21", ... ]) >>> summarized_networks = collapse_addresses(prefixes) >>> list(summarized_networks) [IPv4Network('192.168.0.0/23'), IPv4Network('192.168.3.0/24'), IPv4Network('192.168.4.0/24'), IPv4Network('192.168.5.0/26'), IPv4Network('192.168.128.0/21')] # プレフィックスを分割する(プレフィックス長を増やす) >>> prefix = ip_network("192.168.0.0/24") # プレフィックス長に+1して、プレフィックスを分割(/24 => /25) >>> list(prefix.subnets(prefixlen_diff=1)) [IPv4Network('192.168.0.0/25'), IPv4Network('192.168.0.128/25')] # プレフィックス長に+2して、プレフィックスを分割(/24 => /26) >>> list(prefix.subnets(prefixlen_diff=2)) [IPv4Network('192.168.0.0/26'), IPv4Network('192.168.0.64/26'), IPv4Network('192.168.0.128/26'), IPv4Network('192.168.0.192/26')] # 新しいプレフィックス長を27にして、プレフィックスを分割(/24 => /27) >>> list(prefix.subnets(new_prefix=27)) [IPv4Network('192.168.0.0/27'), IPv4Network('192.168.0.32/27'), IPv4Network('192.168.0.64/27'), IPv4Network('192.168.0.96/27'), IPv4Network('192.168.0.128/27'), IPv4Network('192.168.0.160/27'), IPv4Network('192.168.0.192/27'), IPv4Network('192.168.0.224/27')] # プレフィックスのスーパーネットを求める(プレフィックス長を減らす) >>> prefix = ip_network("192.168.0.0/24") # プレフィックス長を-1にする(/24 => /23) >>> prefix.supernet(prefixlen_diff=1) IPv4Network('192.168.0.0/23') # プレフィックス長を-2にする(/24 => /22) >>> prefix.supernet(prefixlen_diff=2) IPv4Network('192.168.0.0/22') # プレフィックス長を/21にする(/24 => /21) >>> prefix.supernet(new_prefix=21) IPv4Network('192.168.0.0/21') # プレフィックスから、特定のプレフィックスを除外する >>> prefix1 = ip_network("192.168.0.0/24") >>> prefix2 = ip_network("192.168.0.32/27") >>> prefix1.address_exclude(prefix2) <generator object _BaseNetwork.address_exclude at 0x00000286C6AD4BC8> >>> list(prefix1.address_exclude(prefix2)) [IPv4Network('192.168.0.128/25'), IPv4Network('192.168.0.64/26'), IPv4Network('192.168.0.0/27')]
IPアドレスの判定
from ipaddress import ip_network # プレフィックスが、サブネットの範囲内か判定(右辺に左辺が含まれるか?) >>> ip1 = ip_network("192.168.0.0/24") >>> ip2 = ip_network("192.168.0.32/27") >>> ip1.subnet_of(ip2) False >>> ip2.subnet_of(ip1) True # プレフィックスが、スパーネットの範囲内か判定(左辺に右辺が含まれるか?) >>> ip1 = ip_network("192.168.0.0/24") >>> ip2 = ip_network("192.168.0.32/27") >>> ip1.supernet_of(ip2) True >>> ip2.supernet_of(ip1) False
各クラスで使える関数一覧
各クラス(IPv4Interface、IPv4Network、IPv4Address)に定義されている変数と関数一覧です。
from ipaddress import ( ip_network, ip_address, ip_interface ) # IPv4Interface >>> ip_interface("192.168.1.124/24") IPv4Interface('192.168.1.124/24') >>> ip = ip_interface("192.168.1.124/24") >>> ip IPv4Interface('192.168.1.124/24') >>> ip. ip.compressed ip.is_global ip.is_private ip.netmask ip.version ip.exploded ip.is_link_local ip.is_reserved ip.network ip.with_hostmask ip.hostmask ip.is_loopback ip.is_unspecified ip.packed ip.with_netmask ip.ip ip.is_multicast ip.max_prefixlen ip.reverse_pointer ip.with_prefixlen # IPv4Network >>> ip = ip_network("192.168.1.124/24", strict=False) >>> ip IPv4Network('192.168.1.0/24') >>> ip. ip.address_exclude( ip.hosts( ip.is_reserved ip.overlaps( ip.supernet_of( ip.broadcast_address ip.is_global ip.is_unspecified ip.prefixlen ip.version ip.compare_networks( ip.is_link_local ip.max_prefixlen ip.reverse_pointer ip.with_hostmask ip.compressed ip.is_loopback ip.netmask ip.subnet_of( ip.with_netmask ip.exploded ip.is_multicast ip.network_address ip.subnets( ip.with_prefixlen ip.hostmask ip.is_private ip.num_addresses ip.supernet( # IPv4Address >>> ip = ip_address("192.168.1.124") >>> ip IPv4Address('192.168.1.124') >>> ip. ip.compressed ip.is_link_local ip.is_private ip.max_prefixlen ip.version ip.exploded ip.is_loopback ip.is_reserved ip.packed ip.is_global ip.is_multicast ip.is_unspecified ip.reverse_pointer
付録:シスコルータのコンフィグを生成する
CSVファイルからコンフィグのパラメータを読み取り、 Jinja2テンプレートで、コンフィグを生成します。 今回は、インタフェースの設定と、OSPFの設定用のシスコルータのコンフィグを生成します。
入力CSVファイル:ospf_interfaces.csv
CSVのカラムは、下記の表のとおりです。
カラム名 | 値 |
---|---|
interface |
インタフェース名、GigabitEthernet0/0 |
ipaddress |
インタフェースに割り当てるIPアドレス(プレフィックス長付き)、192.168.0.1/24 |
ospfarea |
インタフェースが所属するOSPFエリア番号、0 |
interface,ipaddress,ospfarea Loopback0,192.168.0.1/32,0 GigabitEthernet0/0,192.168.101.1/24,0 GigabitEthernet0/1,192.168.102.1/24,0 GigabitEthernet1/0,192.168.201.1/24,100 GigabitEthernet1/1,192.168.202.1/24,100
プログラム:ospf_template.py
import csv from ipaddress import ip_interface from jinja2 import Template # CSVからパラメータ読み込み # Excelで作成したCSVの場合、BOM付きのUTF-8で出力されるため、encoding="utf_8_sig"にする params = [] with open("ospf_interfaces.csv", "r", encoding="utf_8_sig", newline="") as f: reader = csv.DictReader(f) for row in reader: ip = ip_interface(row["ipaddress"]) print(row) param = { "ip": ip, "interface": row["interface"], "ospfarea": row["ospfarea"] } params.append(param) # コンフィグのテンプレート CONFIG_TEMPLATE = """ {%- for p in params %} interface {{ p["interface"] }} ip address {{ p["ip"].ip }} {{ p["ip"].netmask }} {% endfor %} router ospf 1 passive-interface default {%- for p in params %} no passive-interface {{ p["interface"] }} {%- endfor %} {%- for p in params %} network {{ p["ip"].network.network_address }} {{ p["ip"].hostmask }} area {{ p["ospfarea"] }} {%- endfor %} """ # テンプレートにパラメータを渡して、コンフィグに変換する template = Template(CONFIG_TEMPLATE) output = template.render({"params": params}) print("=" * 60) print(output) print("=" * 60)
実行結果
CSVファイルから各パラメータを読み取り、コンフィグが生成されました。
>python ospf_template.py OrderedDict([('interface', 'Loopback0'), ('ipaddress', '192.168.0.1/32'), ('ospfarea', '0')]) OrderedDict([('interface', 'GigabitEthernet0/0'), ('ipaddress', '192.168.101.1/24'), ('ospfarea', '0')]) OrderedDict([('interface', 'GigabitEthernet0/1'), ('ipaddress', '192.168.102.1/24'), ('ospfarea', '0')]) OrderedDict([('interface', 'GigabitEthernet1/0'), ('ipaddress', '192.168.201.1/24'), ('ospfarea', '100')]) OrderedDict([('interface', 'GigabitEthernet1/1'), ('ipaddress', '192.168.202.1/24'), ('ospfarea', '100')]) ============================================================ interface Loopback0 ip address 192.168.0.1 255.255.255.255 interface GigabitEthernet0/0 ip address 192.168.101.1 255.255.255.0 interface GigabitEthernet0/1 ip address 192.168.102.1 255.255.255.0 interface GigabitEthernet1/0 ip address 192.168.201.1 255.255.255.0 interface GigabitEthernet1/1 ip address 192.168.202.1 255.255.255.0 router ospf 1 passive-interface default no passive-interface Loopback0 no passive-interface GigabitEthernet0/0 no passive-interface GigabitEthernet0/1 no passive-interface GigabitEthernet1/0 no passive-interface GigabitEthernet1/1 network 192.168.0.1 0.0.0.0 area 0 network 192.168.101.0 0.0.0.255 area 0 network 192.168.102.0 0.0.0.255 area 0 network 192.168.201.0 0.0.0.255 area 100 network 192.168.202.0 0.0.0.255 area 100 ============================================================
おわりに
ipaddress
ライブラリは、Python3.3から標準ライブラリとなっているので、非常に使いやすいと思います。
下記の参考文献の通り、便利でした!
参考文献
Pythonでルータにpingする
疎通できる構築したルータ一覧を作成する必要があり、 Pythonでルータにpingして、その結果を一覧にしてみました。
はじめに
ある日、Prometheusの監視対象ルータ一覧を作成することにしましたが、 管理台帳上ではルータは120台ぐらいあるように見えます。
しかしながら、よくよく確認すると、構築が延期になったり、そもそも欠番になったりして、 実際に構築したルータが、どれかよくわからない状態になっていました。 構築されていないルータを監視対象に入れるのはもったいないので、 疎通できるルータの一覧を作成することにしました。
疎通確認はpingでしますが、 1回目のpingでは応答しないルータもあるため、必ず2回pingを実行して、 ルータ一覧を作成することにします。
今回は雑に宛先リストを作成して、Pingの結果(OK or NG)を、宛先リストに追記します。
後々、YAML形式に変換して出力したいと考えたので、Pythonでルータにpingして、 その結果を一覧としてCSVに保存することにします。
やりかた
Pythonには、ICMP echo request/reply用のpyping
などのライブラリがあります。
ただし、難点なのが管理者権限(root)が必要なため、踏み台サーバ経由など、お手軽に実行できない場合があります。
このため、Windowsのping.exe
コマンドをsubprocess
ライブラリを
利用して外部プロセスとして起動し、ルータにpingします。
今回は、Win10の環境のため、Win10のping.exe
を実行することにしました。
下記の通り、Win10の環境に、Anacondaをインストールした環境を用意して検証します。
項目 | 詳細 |
---|---|
OS | Windows 10 |
Python | Anaconda 2019.10 (Python 3.7.4) |
pingコマンド | Windows 10 標準のping.exe コマンドを利用 |
下図の通り、宛先リストCSV生成⇒ping実行⇒Prometheus SNMP Exporter用YAMLファイルの生成という段階で作業します。 ping実行については、逐次実行パターンと、並列実行パターンの2種類を用意しました。
PythonでWin10上のping.exeの実行
下記は、subprocess
ライブラリから、ping.exe
コマンドを実行した例になります。
ping.exe
は、何かしらのping応答があった場合、戻り値(return code
/ exit status
)が、0
になる問題があります。
Ping NGでも、ICMP Unreachableが返ってくると、戻り値が0
になります。
このため、戻り値では、Ping OKと判断できません。
参考:【バッチ】戻り値で結果を判定する場合に注意が必要なコマンド(tarやping)【シェル】 - 俺のメモ帖
このため、ping.exe
の標準出力から文字列を抜き出して、Ping NGかPing OKかを判断する必要があります。
今回はpingが成功した場合、下記のような文字列が表示され、必ずTTL=
が含まれます。
TTL=
が含まれていた場合は、Ping OKで、含まれない場合はPing NGとして取り扱います。
>ping -n 2 -w 1000 127.0.0.1 127.0.0.1 に ping を送信しています 32 バイトのデータ: 127.0.0.1 からの応答: バイト数 =32 時間 <1ms TTL=128 127.0.0.1 からの応答: バイト数 =32 時間 <1ms TTL=128 127.0.0.1 の ping 統計: パケット数: 送信 = 2、受信 = 2、損失 = 0 (0% の損失)、 ラウンド トリップの概算時間 (ミリ秒): 最小 = 0ms、最大 = 0ms、平均 = 0ms
プログラム:ping0_test.py
subprocess
ライブラリで、ping.exe
の実行結果を正しく解釈できるか、下記のようなサンプルプログラムを作成しました。
subprocess.run
で、ping.exe -n 2 -w 1000 192.168.10.1
とping.exe -n 2 -w 1000 192.168.10.2
を実行し、
ping.exe
の戻り値(return code)と標準出力を、それぞれ表示します。
import subprocess print("=" * 60) print("PING OKの場合") print("=" * 60) commands = ["ping", "-n", "2", "-w", "1000", "192.168.10.1"] print(" ".join(commands)) proc = subprocess.run( commands, stdout=subprocess.PIPE, # 標準出力は保存 stderr=subprocess.DEVNULL # 標準エラーは捨てる ) print(f"return code : {proc.returncode}") result = proc.stdout.decode("cp932") print(result) print() print("=" * 60) print("PING NGの場合") print("=" * 60) commands = ["ping", "-n", "2", "-w", "1000", "192.168.10.2"] print(" ".join(commands)) proc = subprocess.run( commands, stdout=subprocess.PIPE, # 標準出力は保存 stderr=subprocess.DEVNULL # 標準エラーは捨てる ) print(f"return code : {proc.returncode}") result = proc.stdout.decode("cp932") print(result) print()
実行結果:ping0_test.py
下記の実行結果から、Ping OKとPing NGの場合、戻り値(return code)が0
であることが確認できます。
このため、判定条件は下記のとおりとなります。
状態 | 判定条件 |
---|---|
Ping OK | 標準出力proc.stdout.decode("cp932") にTTL= が含まれる |
Ping NG | 標準出力proc.stdout.decode("cp932") にTTL= が含まれない |
> python ping0_test.py ============================================================ PING OKの場合 ============================================================ ping -n 2 -w 1000 192.168.10.1 return code : 0 192.168.10.1 に ping を送信しています 32 バイトのデータ: 192.168.10.1 からの応答: バイト数 =32 時間 =3ms TTL=255 192.168.10.1 からの応答: バイト数 =32 時間 =3ms TTL=255 192.168.10.1 の ping 統計: パケット数: 送信 = 2、受信 = 2、損失 = 0 (0% の損失)、 ラウンド トリップの概算時間 (ミリ秒): 最小 = 3ms、最大 = 3ms、平均 = 3ms ============================================================ PING NGの場合 ============================================================ ping -n 2 -w 1000 192.168.10.2 return code : 0 192.168.10.2 に ping を送信しています 32 バイトのデータ: 要求がタイムアウトしました。 192.168.10.111 からの応答: 宛先ホストに到達できません。 192.168.10.2 の ping 統計: パケット数: 送信 = 2、受信 = 1、損失 = 1 (50% の損失)、
pingの宛先リスト(CSV)を作成
次に、Pingの宛先リストを作成します。
今回は、192.168.10.1
~192.168.10.9
までのIPアドレスリストを作成します。
IPアドレス以外にも連続性がある場合は、ホスト名に連番をつけて、作成しても良いと思います。
ファイルは編集しやすいようにCSV形式で保存します。 CSV形式は下記のような2つのカラム(列)を定義しました。
カラム名 | 詳細 |
---|---|
description | ルータを識別する名前、愛称など |
target | Pingの宛先。IPアドレスやホスト名を指定 |
プログラム:generate_csv.py
実行すると、target
として、192.168.10.1
~192.168.10.9
までのリストをCSVとして、
ping_targets.csv
ファイルに保存します。
このとき、description
には、host01
~host09
が同時につけられます。
import csv CSV_COLUMNS = ["description", "target"] OUTPUT_CSV = "ping_targets.csv" with open(OUTPUT_CSV, "w", encoding="cp932", newline="") as f: writer = csv.DictWriter(f, fieldnames=CSV_COLUMNS) writer.writeheader() # 192.168.10.1~192.168.10.9までを生成する for i in range(1, 10): description = f"host{i:02}" target = f"192.168.10.{i}" row = { "description": description, "target": target } writer.writerow(row) print(row)
実行結果:generate_csv.py
実行結果は下記のとおりです。宛先が9個出力されています。
同時にCSVファイルping_targets.csv
にも保存されています。
>python generate_targets.py {'description': 'host01', 'target': '192.168.10.1'} {'description': 'host02', 'target': '192.168.10.2'} {'description': 'host03', 'target': '192.168.10.3'} {'description': 'host04', 'target': '192.168.10.4'} {'description': 'host05', 'target': '192.168.10.5'} {'description': 'host06', 'target': '192.168.10.6'} {'description': 'host07', 'target': '192.168.10.7'} {'description': 'host08', 'target': '192.168.10.8'} {'description': 'host09', 'target': '192.168.10.9'}
出力結果:ping_targets.csv
下記のように、宛先リストがCSVで出力されます。
description,target host01,192.168.10.1 host02,192.168.10.2 host03,192.168.10.3 host04,192.168.10.4 host05,192.168.10.5 host06,192.168.10.6 host07,192.168.10.7 host08,192.168.10.8 host09,192.168.10.9
宛先リストにpingする(逐次実行するパターン)
いよいよ、宛先リストにpingします。 まずはじめに、宛先リストから1行ずつ取り出して、pingし、結果をCSVに保存します。
プログラム:ping1_singlethread.py
ping
関数で、ping.exe
を実行します。
引数には宛先リストのCSV行データであるrow
を渡しています。
row["target"]
が含まれていることを期待し、row["target"]
宛にpingします。
実行結果はrow["result"]
にOK
またはNG
として保存します。
import subprocess import csv import time CSV_COLUMNS = ["description", "target", "result"] INPUT_CSV = "ping_targets.csv" OUTPUT_CSV = "ping_results.csv" def ping(row): # windowsのpingコマンドを実行する # ICMP Echo Requestを2回送出 "-n 2" # タイムアウトは1000ミリ秒 "-w 1000" # target宛に送信 # ping -n 2 -w 1000 target proc = subprocess.run( ["ping", "-n", "2", "-w", "1000", row["target"]], stdout=subprocess.PIPE, # 標準出力は判断のため保存 stderr=subprocess.DEVNULL # 標準エラーは捨てる ) # pingコマンドの実行結果(標準出力)に「TTL=」の文字列があれば、PING OKと判断 succeed = proc.stdout.decode("cp932").find("TTL=") > 0 row["result"] = "OK" if succeed else "NG" return row def main(): start = time.time() # 実行時間計測用 with open(INPUT_CSV, "r", encoding="cp932", newline="") as fin: with open(OUTPUT_CSV, "w", encoding="cp932", newline="") as fout: reader = csv.DictReader(fin) writer = csv.DictWriter(fout, fieldnames=CSV_COLUMNS) writer.writeheader() for row in reader: row = ping(row) writer.writerow(row) print(row) print(f"実行時間 {time.time() - start:,.2f} 秒") # 実行時間計測用 if __name__ == "__main__": main()
実行結果:ping1_singlethread.py
実行結果は、下記のとおりです。宛先9個のうち、2個がOKで、その他はNGとなっています。 このときの実行時間は、22秒と実行に時間がかかっています。 これは、Ping NGが多く、タイムアウトを待っていたためです。
>python ping1_singlethread.py OrderedDict([('description', 'host01'), ('target', '192.168.10.1'), ('result', 'OK')]) OrderedDict([('description', 'host02'), ('target', '192.168.10.2'), ('result', 'NG')]) OrderedDict([('description', 'host03'), ('target', '192.168.10.3'), ('result', 'NG')]) OrderedDict([('description', 'host04'), ('target', '192.168.10.4'), ('result', 'OK')]) OrderedDict([('description', 'host05'), ('target', '192.168.10.5'), ('result', 'NG')]) OrderedDict([('description', 'host06'), ('target', '192.168.10.6'), ('result', 'NG')]) OrderedDict([('description', 'host07'), ('target', '192.168.10.7'), ('result', 'NG')]) OrderedDict([('description', 'host08'), ('target', '192.168.10.8'), ('result', 'NG')]) OrderedDict([('description', 'host09'), ('target', '192.168.10.9'), ('result', 'NG')]) 実行時間 22.87 秒
出力結果:ping_results.csv
ping_targets.csv
にresult
カラムが追加され、
Ping OKか、Ping NGかわかるようになりました。
description,target,result host01,192.168.10.1,OK host02,192.168.10.2,NG host03,192.168.10.3,NG host04,192.168.10.4,OK host05,192.168.10.5,NG host06,192.168.10.6,NG host07,192.168.10.7,NG host08,192.168.10.8,NG host09,192.168.10.9,NG
宛先リストにpingする(並行実行するパターン)
逐次実行の場合、タイムアウトが発生すると、実行時間がかなりかかるようになります。
数十秒程度であれば、待てますが、対象が多くなり、Ping NGが多発すると現実的ではありません。
このため、ping.exe
を並列実行するように変更しました。
プログラム:ping2_multithread.py
同時実行のために、
concurrent.futures.ThreadPoolExecutor
を利用しました。
THREAD_MAX_WORKER = 30
で、30個の宛先に同時にpingします。
大まかな動きとして、 CSVの宛先リストを読み込み、pingを実行するスレッドプールにジョブ(ping宛先)を登録し、すべてのジョブが完了したらCSVに結果を出力します。
import subprocess import csv import time from concurrent.futures import ThreadPoolExecutor CSV_COLUMNS = ["description", "target", "result"] INPUT_CSV = "ping_targets.csv" # カラム名にdescription、targetを期待。targetに対してping OUTPUT_CSV = "ping_results.csv" # カラム名はCSV_COLUMNS THREAD_MAX_WORKER = 30 def ping(row): # windowsのpingコマンドを実行する # ICMP Echo Requestを2回送出 "-n 2" # タイムアウトは1000ミリ秒 "-w 1000" # target宛に送信 # ping -n 2 -w 1000 target proc = subprocess.run( ["ping", "-n", "2", "-w", "1000", row["target"]], stdout=subprocess.PIPE, # 標準出力は判断のため保存 stderr=subprocess.DEVNULL # 標準エラーは捨てる ) # pingコマンドの実行結果(標準出力)に「TTL=」の文字列があれば、PING OKと判断 succeed = proc.stdout.decode("cp932").find("TTL=") > 0 row["result"] = "OK" if succeed else "NG" return row def main(): start = time.time() # 実行時間計測用 # THREAD_MAX_WORKER分のスレッドプールを起動してpingコマンドを実行する with ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as executor: # CSVを読み込み、スレッドプールにキューイングする with open(INPUT_CSV, "r", encoding="cp932", newline="") as fin: reader = csv.DictReader(fin) # CSVの1行ずつping関数で実行 results = executor.map(ping, reader) # スレッドプールの実行結果を、CSVに書き込む with open(OUTPUT_CSV, "w", encoding="cp932", newline="") as fout: writer = csv.DictWriter(fout, fieldnames=CSV_COLUMNS) writer.writeheader() for result in results: row = result writer.writerow(row) print(row) print(f"実行時間 {time.time() - start:,.2f} 秒") # 実行時間計測用 if __name__ == "__main__": main()
実行結果:ping2_multithread.py
逐次実行と比べると、実行時間が7倍早くなっていることが確認できます。
>python ping2_multithread.py OrderedDict([('description', 'host01'), ('target', '192.168.10.1'), ('result', 'OK')]) OrderedDict([('description', 'host02'), ('target', '192.168.10.2'), ('result', 'NG')]) OrderedDict([('description', 'host03'), ('target', '192.168.10.3'), ('result', 'NG')]) OrderedDict([('description', 'host04'), ('target', '192.168.10.4'), ('result', 'OK')]) OrderedDict([('description', 'host05'), ('target', '192.168.10.5'), ('result', 'NG')]) OrderedDict([('description', 'host06'), ('target', '192.168.10.6'), ('result', 'NG')]) OrderedDict([('description', 'host07'), ('target', '192.168.10.7'), ('result', 'NG')]) OrderedDict([('description', 'host08'), ('target', '192.168.10.8'), ('result', 'NG')]) OrderedDict([('description', 'host09'), ('target', '192.168.10.9'), ('result', 'NG')]) 実行時間 3.08 秒
出力結果:ping_results.csv
出力結果は、逐次実行と同じ内容となります。
description,target,result host01,192.168.10.1,OK host02,192.168.10.2,NG host03,192.168.10.3,NG host04,192.168.10.4,OK host05,192.168.10.5,NG host06,192.168.10.6,NG host07,192.168.10.7,NG host08,192.168.10.8,NG host09,192.168.10.9,NG
SNMP Exporter用のYAMLファイルを作成
最後に、PrometheusのSNMP Exporter用のYAMLファイルを作成します。
Ping結果が記録されたping_results.csv
を読み込み、Ping OKの行のみ、snmp_targets.yaml
に出力します。
プログラム:generate_snmp_targets.py
今回、YAMLは単純な列挙だけであったため、ライブラリを使わず、直接出力しました。
import csv INPUT_CSV = "ping_results.csv" OUTPUT_YAML = "snmp_targets.yaml" with open(INPUT_CSV, "r", encoding="cp932", newline="") as fin: with open(OUTPUT_YAML, "w", encoding="utf-8", newline="") as fout: reader = csv.DictReader(fin) fout.write("---\n") fout.write("- targets:\n") for row in reader: if row["result"] == "OK": print(row) fout.write(f' - {row["target"]}\n') fout.write(" labels:\n") fout.write(" group: tokyo-routers\n")
実行結果:generate_snmp_targets.py
下記の通り、Ping OKであった宛先のみ、出力されています。
>python generate_snmp_targets.py OrderedDict([('description', 'host01'), ('target', '192.168.10.1'), ('result', 'OK')]) OrderedDict([('description', 'host04'), ('target', '192.168.10.4'), ('result', 'OK')])
出力結果:snmp_targets.yaml
下記の通り、Ping OKであった宛先をtargets
に記載しました。
--- - targets: - 192.168.10.1 - 192.168.10.4 labels: group: tokyo-routers
おわりに
数十台なら、手作業でYAMLファイルを作成したほうが、早くできると思います。 ただし、単純作業で楽しくないです。
今回のように単純な作業から徐々に自動化して、どんどん楽をしていきましょう。 一度、自動化の仕組みを作れば、月1回の棚卸しから、毎日の棚卸しや、1時間に一回の棚卸しができるようになります。 また、もっと作り込めば、監視登録の自動化もできます。 監視登録漏れによる、重大事故を防ぐためにも、皆様、積極的に自動化していきましょう。
Python/Netmiko/TextFSM/NTC-Templatesでシスコルータのshow ip routeをExcel/CSVに変換してみた
Python/Netmiko/TextFSM/NTC-Templatesを利用して、シスコルータのshow ip routeの結果をExcel/CSVに変換してみました。 Netmiko v2.0.0(2017/12/15にリリース)の新機能(Integrate TextFSM to send_command and send_command_timing)を利用しています。
はじめに
ネットワークエンジニアの皆様、ルータのshow ip routeの結果をExcelにコピペする作業をよくやってないでしょうか? 私は経路差分のチェックやお客様に経路の一覧を提出するために、よくやってます。
100経路程度なら、なんとか手作業でもできると思います。 しかし、100経路を超えてくるとどうでしょうか? 非常に退屈な作業で、非常に時間がかかると思います。 さらに、コピペミスもしてしまうかもしれません。 ミスの対策として、ダブルチェックしないといけないかもしれません。
そんな退屈で面倒な作業をツールを使って解決します。 ツールで、show ip routeの結果を、Excel/CSVに変換します。
今回は、PythonライブラリのNetmikoを使います。 2017/12/15にリリースされたv2.0.0で、TextFSMを組み込まれたので使ってみます。
仕組み
show ip route
をExcel/CSVに変換する仕組みは下記のとおりです。
- Netmikoで、シスコルータにTelnet/SSHでログイン
show ip route
コマンド実行- TextFSM+NTC-Templatesで実行結果から抽出
- 抽出したデータをExcel/CSV形式で保存
TextFSM + NTC-Templates=最強
シスコルータのshow ip route
と、TextFSM+NTC-Templatesの相性は抜群です。
少なくとも、サブネットマスク長を補完してくれる機能は非常にありがたい機能です。
サブネットマスク長が同じサブネットがある場合、サブネットマスク長が省略されます。
show ip route
は、同じサブネットマスク長の経路があった場合、自動的にグループ化され、省略されます。
下記のように192.168.0.1/30
は、192.168.0.1
で、サブネットマスク長/30
は省略されています。
NTC-Templatesの場合、省略されたサブネットマスク長を補完してくれます。
csr1000v-1#show ip route ~略~ 192.168.0.0/32 is subnetted, 8 subnets O 192.168.0.1 [110/4] via 10.0.0.34, 1w1d, GigabitEthernet3 O 192.168.0.2 [110/2] via 10.0.0.34, 1w4d, GigabitEthernet3 〜略〜
CSV出力内容
取得できるフィールドは下記のとおりです。
フィールド名 | 内容 |
---|---|
PROTOCOL | 経路情報を学習したプロトコル |
TYPE | プロトコルの付加情報。OSPFならIAやE2など |
NETWORK | ネットワークアドレス |
MASK | サブネットマスク長 |
DISTANCE | AD(アドミニストレーティブディスタンス) |
METRIC | 経路のメトリック |
NEXTHOP_IP | ネクストホップのIPアドレス |
NEXTHOP_IF | 出力インタフェース |
UPTIME | 経路の学習時間 |
環境
今回、試してみた環境は下記のとおりです。
項目 | 詳細 |
---|---|
Ubuntu 16.04 | Win10ProのWSLで、Ubuntu16.04をインストールしました |
Excel 2016 | ネットワークエンジニアの大好きな定番ソフト |
Python 3.6.3 | プログラミング言語と実行環境 |
Anaconda 5.0.1 | Pythonのオールインワンパッケージ |
pandas 0.20.3 | Pythonライブラリ。CSV操作用 |
Netmiko 2.0.0 | Pythonライブラリ。ルータへTelnet/SSH用 |
TextFSM 0.3.2 | Pythonライブラリ。コマンドの実行結果を解析し値を抽出 |
NTC-Templates | TextFSMの抽出テンプレート集 |
Cisco VIRL 1.3.296 | ルータのシミュレータ |
Pythonのインストールは、オールインワンパッケージのAnacondaを利用すると便利なので、お勧めします。
Netmikoの準備
もし、古いバージョンのNetmiko がインストールされている場合、下記のようにv2.0.0にアップグレードしてください。
$ pip install netmiko==2.0.0
現在のバージョンは下記のコマンドで確認できます。 Version: 2.0.0以上であればOKです。
$ pip show netmiko Name: netmiko Version: 2.0.0 Summary: Multi-vendor library to simplify Paramiko SSH connections to network devices Home-page: https://github.com/ktbyers/netmiko Author: Kirk Byers Author-email: ktbyers@twb-tech.com License: MIT Location: /home/kooshin/anaconda3/lib/python3.6/site-packages Requires: textfsm, paramiko, pyserial, scp, pyyaml
NTC-Templatesの準備
TextFSM用のテンプレートNTC-Templatesを事前にホームディレクトリに保存します。 下記のようにGitでクローンするか、ZIPファイルをダウンロードして、ホームディレクトリに展開してください。
$ cd ~ $ git clone https://github.com/networktocode/ntc-templates.git
コード:get_routes.py
シスコルータにSSHでログインして、show ip route
コマンドの実行結果をExcel/CSVに保存するコードは下記のとおりです。
Netmiko に、TextFSM+NTC-Templatesが組み込まれたことで、コードでは、ほとんど意識せずに利用することができます。
import netmiko import pandas as pd # シスコルータにログイン params = { 'device_type': 'cisco_ios', 'ip': '172.16.1.99', 'username': 'cisco', 'password': 'cisco', 'secret': 'cisco', } conn = netmiko.ConnectHandler(**params) conn.enable() # show ip routeの実行結果をTextFSMで抽出する routes = conn.send_command('show ip route', use_textfsm=True) # CSVで保存 df = pd.DataFrame(routes) df.to_csv('routes.csv', index=False)
動作結果
下記の通り、コマンドを実行すると、
ルータにSSHでログインし、
show ip route
コマンドを実行し、
実行結果をCSVで保存します。
$ python get_routes.py
show ip routeコマンドの実行結果
csr1000v-1#show ip route Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2 E1 - OSPF external type 1, E2 - OSPF external type 2 i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2 ia - IS-IS inter area, * - candidate default, U - per-user static route o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP a - application route + - replicated route, % - next hop override, p - overrides from PfR Gateway of last resort is not set 10.0.0.0/8 is variably subnetted, 12 subnets, 2 masks O 10.0.0.4/30 [110/2] via 10.0.0.34, 1w1d, GigabitEthernet3 O 10.0.0.8/30 [110/3] via 10.0.0.34, 1w1d, GigabitEthernet3 O 10.0.0.12/30 [110/2] via 10.0.0.34, 1w4d, GigabitEthernet3 O 10.0.0.16/30 [110/3] via 10.0.0.34, 1w1d, GigabitEthernet3 O 10.0.0.20/30 [110/43] via 10.0.0.34, 1w1d, GigabitEthernet3 O 10.0.0.24/30 [110/4] via 10.0.0.42, 1w4d, GigabitEthernet2 [110/4] via 10.0.0.34, 1w4d, GigabitEthernet3 O 10.0.0.28/30 [110/3] via 10.0.0.42, 1w4d, GigabitEthernet2 [110/3] via 10.0.0.34, 1w4d, GigabitEthernet3 C 10.0.0.32/30 is directly connected, GigabitEthernet3 L 10.0.0.33/32 is directly connected, GigabitEthernet3 O 10.0.0.36/30 [110/2] via 10.0.0.42, 1w4d, GigabitEthernet2 C 10.0.0.40/30 is directly connected, GigabitEthernet2 L 10.0.0.41/32 is directly connected, GigabitEthernet2 192.168.0.0/32 is subnetted, 8 subnets O 192.168.0.1 [110/4] via 10.0.0.34, 1w1d, GigabitEthernet3 O 192.168.0.2 [110/2] via 10.0.0.34, 1w4d, GigabitEthernet3 O 192.168.0.3 [110/3] via 10.0.0.34, 1w1d, GigabitEthernet3 O 192.168.0.4 [110/5] via 10.0.0.42, 1w4d, GigabitEthernet2 [110/5] via 10.0.0.34, 1w4d, GigabitEthernet3 O 192.168.0.5 [110/4] via 10.0.0.42, 1w4d, GigabitEthernet2 [110/4] via 10.0.0.34, 1w4d, GigabitEthernet3 O 192.168.0.6 [110/3] via 10.0.0.42, 1w4d, GigabitEthernet2 [110/3] via 10.0.0.34, 1w4d, GigabitEthernet3 C 192.168.0.7 is directly connected, Loopback0 O 192.168.0.8 [110/2] via 10.0.0.42, 1w4d, GigabitEthernet2
CSV出力結果:routes.csv
下記の通り、Excel/CSVに変換できました。
なお、show ip route
の結果に含まれないフィールドは表示されません。
プロトコルがLocal/Connectedの場合、ADやネクストホップのIPアドレスは表示されません。
protocol,type,network,mask,distance,metric,nexthop_ip,nexthop_if,uptime O,,10.0.0.4,30,110,2,10.0.0.34,GigabitEthernet3,1w1d O,,10.0.0.8,30,110,3,10.0.0.34,GigabitEthernet3,1w1d O,,10.0.0.12,30,110,2,10.0.0.34,GigabitEthernet3,1w4d O,,10.0.0.16,30,110,3,10.0.0.34,GigabitEthernet3,1w1d O,,10.0.0.20,30,110,43,10.0.0.34,GigabitEthernet3,1w1d O,,10.0.0.24,30,110,4,10.0.0.42,GigabitEthernet2,1w4d O,,10.0.0.24,30,110,4,10.0.0.34,GigabitEthernet3,1w4d O,,10.0.0.28,30,110,3,10.0.0.42,GigabitEthernet2,1w4d O,,10.0.0.28,30,110,3,10.0.0.34,GigabitEthernet3,1w4d C,,10.0.0.32,30,,,,GigabitEthernet3, L,,10.0.0.33,32,,,,GigabitEthernet3, O,,10.0.0.36,30,110,2,10.0.0.42,GigabitEthernet2,1w4d C,,10.0.0.40,30,,,,GigabitEthernet2, L,,10.0.0.41,32,,,,GigabitEthernet2, O,,192.168.0.1,32,110,4,10.0.0.34,GigabitEthernet3,1w1d O,,192.168.0.2,32,110,2,10.0.0.34,GigabitEthernet3,1w4d O,,192.168.0.3,32,110,3,10.0.0.34,GigabitEthernet3,1w1d O,,192.168.0.4,32,110,5,10.0.0.42,GigabitEthernet2,1w4d O,,192.168.0.4,32,110,5,10.0.0.34,GigabitEthernet3,1w4d O,,192.168.0.5,32,110,4,10.0.0.42,GigabitEthernet2,1w4d O,,192.168.0.5,32,110,4,10.0.0.34,GigabitEthernet3,1w4d O,,192.168.0.6,32,110,3,10.0.0.42,GigabitEthernet2,1w4d O,,192.168.0.6,32,110,3,10.0.0.34,GigabitEthernet3,1w4d C,,192.168.0.7,32,,,,Loopback0, O,,192.168.0.8,32,110,2,10.0.0.42,GigabitEthernet2,1w4d
おわりに
簡単なコードを書くことで、面倒な作業から解放されます。 ぜひ、ネットワークエンジニアの皆様はコードを書いて、作業の自動化、省力化しましょう!