なぜPython Networker

「ネットワーク管理者はプログラムできる必要があります」-このフレーズは、多くのネットワーク担当者の間でしばしば異議を唱えます。

-なんで? 手はより信頼性があります。
-しかし、典型的な操作を自動化できます。
「そして、何かがうまくいかない場合、たくさんのデバイスを置きますか?」
-手でデバイスの束を置くことができます。

このテーマに関する典型的な議論の要約を聞いたことがあります。 ほとんどの管理者は、テキストエディターで以前にコピーした構成の一部の編集を停止し、コンソールにコピーします。 または、一般的な構成ファイルの準備が、コンソールを介して手動でそれらを機器に追加します。

ネットワーク機器のメーカーを見ると、
同じciscoが、ネットワーク機器との作業を自動化するためのさまざまなオプションを提供してきました:IOSのTCLからNX-OSおよびIOS-XRの Pythonまで。 これはすべてネットワークオートメーションまたはネットワークプログラマビリティと呼ばれ、シスコはこの分野でコースを提供しています。

そして、シスコはここだけではありません。ジュニパーとPyEZ 、HP、Huaweiなど。

多くのツール-Netconf、Restconf、Ansible、Puppet and Python、Python、Python。 特定のツールの分析は後ほど延期し、特定の例に進みます。

2番目の質問は、しばしば激しい議論を引き起こし、通常、相互の完全な誤解につながります。「ネットワークデバイスにはDNSデバイスが必要ですか?」
参加者の位置の詳細な分析は後で行い、PythonとSNMPにつながるタスクを定式化しましょう。 すべてはtracerouteで始まりました。

さまざまな監視システムが存在するにもかかわらず、トラフィックを奇妙な方法で展開するMPLS-TEは、多くの場合、忠実なICMPおよびtracerouteおよびpingユーティリティにより、必要な情報を迅速に提供できます。 ただし、大規模ネットワークでIPアドレスの形式でのみtracerouteを出力するには、パケットの送信元を理解するための追加の作業が必要になります。 たとえば、ユーザーからの直接トラフィックと逆トラフィックは異なるルーターを通過しますが、どのルーターを通過しますか? 解決策は、明らかにDNSにルーターのアドレスを入力することです。 また、企業ネットワークでは、番号が付けられずにコネクタに個別のアドレスを使用することはほとんどないため、DNSにインターフェイスのアドレスを入力すると、ICMPパケットがルーターから送信されたインターフェイスをすばやく理解できます。

ただし、大規模なネットワークでDNSデータベースを手動で保守するには、非常に大きな人件費が必要であり、最も困難な作業ではありません。 ただし、インターフェイスのドメイン名は、インターフェイスの名前、インターフェイスの説明、ルーターのホスト名、およびドメイン名で構成されます。 このすべては、ルーターがその構成で実行します。 主なことは、正しいアドレスに組み立て、正しく接着してバインドすることです。

したがって、このタスクは自動化する必要があります。

最初の考えである構成分析はすぐに消え、ネットワークは大規模でマルチベンダーであり、異なる世代の機器でさえあったため、構成を解析するというアイデアはすぐに人気がなくなりました。

2番目の考え方は、さまざまなベンダーの機器に対する普遍的な要求に必要な答えを与えるものを使用することです。 答えは明らかでした-SNMP。 すべての機能を備えたこのソフトウェアは、あらゆるベンダーのソフトウェアに実装されています。

それでは始めましょう


Pythonを置きます。
sudo apt-get install python3

SNMP、IPアドレスを長期にわたって使用するためのモジュールが必要になります。 しかし、それらをインストールするには、pipをインストールする必要があります。 確かに、今ではpythonにバンドルされています。
sudo apt install python3-pip

そして、モジュールを配置します。
pip3はpysnmpをインストールします

pip3インストール日時

pip3 install ipaddress

ルーターからホスト名を取得してみましょう。 SNMPは、ホストへの要求にOIDを使用します。 OIDでは、ホストはこのOIDに対応する情報を返します。 ホスト名を取得したい-1.3.6.1.2.1.1.5.0を要求する必要があります。

したがって、ホスト名のみを要求する最初のスクリプト。

# import section from pysnmp.hlapi import * from ipaddress import * from datetime import datetime # var section #snmp community_string = 'derfnutfo' # From file ip_address_host = '192.168.88.1' # From file port_snmp = 161 OID_sysName = '1.3.6.1.2.1.1.5.0' # From SNMPv2-MIB hostname/sysname # function section def snmp_getcmd(community, ip, port, OID): return (getCmd(SnmpEngine(), CommunityData(community), UdpTransportTarget((ip, port)), ContextData(), ObjectType(ObjectIdentity(OID)))) def snmp_get_next(community, ip, port, OID): errorIndication, errorStatus, errorIndex, varBinds = next(snmp_getcmd(community, ip, port, OID)) for name, val in varBinds: return (val.prettyPrint()) #code section sysname = (snmp_get_next(community_string, ip_address_host, port_snmp, OID_sysName)) print('hostname= ' + sysname) 

実行して取得:
ホスト名= MikroTik

スクリプトをさらに詳しく分析してみましょう。

まず、必要なモジュールをインポートします。

1. pysnmp-SNMP経由でホストにスクリプトを提供します

2. ipaddress-アドレスの処理を提供します。 アドレスの正確性の確認、ネットワークアドレス内のアドレスの出現の確認など。

3.日時-現在の時刻を取得します。 このタスクでは、ログを整理する必要があります。

次に、4つの変数を開始します。

1.コミュニティ
2.ホストアドレス
3. SNMPポート
4. OID値

2つの機能:

1. snmp_getcmd
2. snmp_get_next

最初の関数は、指定されたポート上の指定されたホストに、指定されたコミュニティとOIDでGET要求を送信します。
2番目の機能はsnmp_getcmdジェネレーターです。 おそらく2つの関数に分割することは適切ではありませんでしたが、実際にそうなったのです。

このスクリプトにはいくつか欠けているものがあります。

1.スクリプトで、ホストのIPアドレスをダウンロードする必要があります。 たとえば、テキストファイルから。 ロード時に、ロードされたアドレスが正しいかどうかを確認する必要があります。そうしないと、pysnmpが非常に驚いて、スクリプトがトレースバックで停止します。 ファイル、データベースからアドレスを取得する場所は原則ではありませんが、受信したアドレスが正しいことを確認する必要があります。 したがって、アドレスのソースはテキストファイル、1行-10進形式の1アドレスです。

2.ポーリング時にネットワーク機器がオフになっている可能性があり、正しく構成されていない可能性があります。その結果、この場合、pysnmpは私たちが待っているものではなく、受信した情報をさらに処理すると、トレースバックでスクリプトが停止します。 SNMPの相互作用にはエラーハンドラが必要です。

3.処理されたエラーが記録されるログファイルが必要です。

アドレスをダウンロードしてログファイルを作成する


ファイル名に変数を導入します。
アドレスの正確さを確認するために、check_ip関数を作成します。
アドレスをロードするために関数get_from_fileを作成します。この関数は、各アドレスの正確性をチェックし、そうでない場合は、それに関するメッセージをログに書き込みます。
リストにデータをロードしています。

 filename_of_ip = 'ip.txt' #    Ip  #log filename_log = 'zone_gen.log' # def check_ip(ip): #  ip   try: ip_address(ip) except ValueError: return False else: return True def get_from_file(file, filelog): #  ip   .   -      fd = open(file,'r') list_ip = [] for line in fd: line=line.rstrip('\n') if check_ip(line): list_ip.append(line) else: filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ': Error    ip  ' + line) print('Error    ip  ' + line) fd.close() return list_ip #code section #   filed = open(filename_log,'w') #    filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + '\n') ip_from_file = get_from_file(filename_of_ip, filed) for ip_address_host in ip_from_file: sysname = (snmp_get_next(community_string, ip_address_host, port_snmp, OID_sysName)) print('hostname= ' + sysname) filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + '\n') filed.close() 

ip.txtファイルを作成する
192.168.88.1
172.1.1.1
12.43.dsds.f4
192.168.88.1

このリストの2番目のアドレスはsnmpに応答しません。 スクリプトを実行し、SNMPのエラーハンドラーの必要性を確認します。
エラーip 12.43.dsds.f4
ホスト名= MikroTik
トレースバック(最後の最後の呼び出し):
ファイル「/snmp/snmp_read3.py」、77行目、印刷( 'hostname =' + sysname)
TypeError: 'NoneType'オブジェクトを暗黙的にstrに変換できません

プロセスは終了コード1で終了しました

トレースバックの内容から、アクセスできないホストが障害の原因であったことは明らかではありません。 スクリプトが停止してすべての情報をログに書き込む可能性のある理由をインターセプトしてみましょう。

pysnmpのエラーハンドラーを作成する


snmp_get_next関数には、すでにエラー出力errorIndication、errorStatus、errorIndex、varBindsがあります。 varBindsでは、受信したデータがアンロードされ、エラーで始まる変数では、エラーに関する情報がアンロードされます。 正しく処理する必要があるだけです。 将来的には、スクリプト内でsnmpを操作するための関数がいくつか追加されるため、エラー処理を別の関数に入れるのが理にかなっています。

 def errors(errorIndication, errorStatus, errorIndex, ip, file): #      False     if errorIndication: print(errorIndication, 'ip address ', ip) file.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + str(errorIndication) + ' = ip address = ' + ip + '\n') return False elif errorStatus: print(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + '%s at %s' % (errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex) - 1][0] or '?')) file.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + '%s at %s' % (errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex) - 1][0] or '?' + '\n')) return False else: return True 

次に、エラー処理とログファイルへの書き込みをsnmp_get_next関数に追加します。 関数は、データだけでなく、エラーがあったかどうかに関するメッセージも返すようになりました。

 def snmp_get_next(community, ip, port, OID, file): errorIndication, errorStatus, errorIndex, varBinds = next(snmp_getcmd(community, ip, port, OID)) if errors(errorIndication, errorStatus, errorIndex, ip, file): for name, val in varBinds: return (val.prettyPrint(), True) else: file.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : Error snmp_get_next ip = ' + ip + ' OID = ' + OID + '\n') return ('Error', False) 

ここで、リクエストの成功に関するメッセージがあることを考慮して、コードセクションを少し書き換える必要があります。 さらに、いくつかのチェックを追加します。

1. Sysnameが3文字未満です。 ログファイルに書き込み、後で詳しく調べることができるようにします。

2.一部のHuaweiとCatosがリクエストにホスト名のみを送信することがわかります。 OIDを個別に検索したくないので(それが存在するという事実ではなく、ソフトウェアエラーである可能性があります)、これらのホストにドメインを手動で追加します。

3.誤ったコミュニティを持つホストは異なる動作をし、ほとんどがエラーハンドラーをトリガーし、何らかの理由でスクリプトが通常の状況として認識していると応答することがわかります。

4.後でスクリプト全体で不要なメッセージを選択しないように、デバッグ期間に異なるレベルのログを追加します。

 for ip_address_host in ip_from_file: #  sysname hostname+domainname,   sysname, flag_snmp_get = (snmp_get_next(community_string, ip_address_host, port_snmp, OID_sysName, filed)) if flag_snmp_get: #  ,    snmp if sysname == 'No Such Object currently exists at this OID': #  community .  ,   traceback.     ,    community,     hostname,     print('ERROR community', sysname, ' ', ip_address_host) filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + 'ERROR community sysname = ' + sysname + ' ip = ' + ip_address_host + '\n') else: if log_level == 'debug': filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + ' sysname ' + sysname + ' type ' + str(type(sysname)) + ' len ' + str(len(sysname)) + ' ip ' + ip_address_host + '\n') if len(sysname) < 3 if log_level == 'debug' or log_level == 'normal': filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + 'Error sysname 3 = ' + sysname + ' ip = ' + ip_address_host + '\n') if sysname.find(domain) == -1: # -  hostname  ,  Huawei  Catos sysname = sysname + '.' + domain if log_level == 'debug' or log_level == 'normal': filed.write("check domain : " + sysname + " " + ip_address_host + " " + "\n") print('hostname= ' + sysname) 

同じip.txtファイルでこのスクリプトを確認しましょう
IPアドレス12.43.dsds.f4のソースのエラーゴミ箱
ホスト名= MikroTik.mydomain.ru
タイムアウトIPアドレス172.1.1.1の前にSNMP応答を受信しませんでした
ホスト名= MikroTik.mydomain.ru

すべてが正常に機能し、すべてのエラーをキャッチしました。スクリプトはエラーのあるホストをスキップしました。 これで、このスクリプトを使用して、snmpに応答するすべてのデバイスからホスト名を収集できます。

スクリプトの全文をネタバレの下に隠します。

スクリプト
 # import section from pysnmp.hlapi import * from ipaddress import * from datetime import datetime # var section #snmp community_string = 'derfnutfo' ip_address_host = '192.168.88.1' port_snmp = 161 OID_sysName = '1.3.6.1.2.1.1.5.0' # From SNMPv2-MIB hostname/sysname filename_of_ip = 'ip.txt' # Ip #log filename_log = 'zone_gen.log' #    log_level = 'debug' domain='mydomain.ru' # function section def snmp_getcmd(community, ip, port, OID): # type class 'generator' errorIndication, errorStatus, errorIndex, result[3] -  #  get       SNMP   OID return (getCmd(SnmpEngine(), CommunityData(community), UdpTransportTarget((ip, port)), ContextData(), ObjectType(ObjectIdentity(OID)))) def snmp_get_next(community, ip, port, OID, file): #   class generator  def snmp_get #  errors,   class 'pysnmp.smi.rfc1902.ObjectType'  OID ( name)   ( val) #     errorIndication, errorStatus, errorIndex, varBinds = next(snmp_getcmd(community, ip, port, OID)) if errors(errorIndication, errorStatus, errorIndex, ip, file): for name, val in varBinds: return (val.prettyPrint(), True) else: file.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : Error snmp_get_next ip = ' + ip + ' OID = ' + OID + '\n') return ('Error', False) def get_from_file(file, filelog): # ip    file,    filelog fd = open(file, 'r') list_ip = [] for line in fd: line=line.rstrip('\n') if check_ip(line): list_ip.append(line) else: filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ': Error ip ' + line) print('Error ip ' + line) fd.close() return list_ip def check_ip(ip): #  ip   . False   . try: ip_address(ip) except ValueError: return False else: return True def errors(errorIndication, errorStatus, errorIndex, ip, file): #       False     file if errorIndication: print(errorIndication, 'ip address ', ip) file.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + str( errorIndication) + ' = ip address = ' + ip + '\n') return False elif errorStatus: print(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + '%s at %s' % ( errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex) - 1][0] or '?' )) file.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + '%s at %s' % ( errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex) - 1][0] or '?' + '\n')) return False else: return True #code section #   filed = open(filename_log,'w') #    filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + '\n') ip_from_file = get_from_file(filename_of_ip, filed) for ip_address_host in ip_from_file: #  sysname hostname+domainname,   sysname, flag_snmp_get = (snmp_get_next(community_string, ip_address_host, port_snmp, OID_sysName, filed)) if flag_snmp_get: #  ,    snmp if sysname == 'No Such Object currently exists at this OID': #  community .  ,   traceback.     ,    community,     hostname,     print('ERROR community', sysname, ' ', ip_address_host) filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + 'ERROR community sysname = ' + sysname + ' ip = ' + ip_address_host + '\n') else: if log_level == 'debug': filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + ' sysname ' + sysname + ' type ' + str( type(sysname)) + ' len ' + str(len(sysname)) + ' ip ' + ip_address_host + '\n') if len(sysname) < 3: sysname = 'None_sysname' if log_level == 'debug' or log_level == 'normal': filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + 'Error sysname 3 = ' + sysname + ' ip = ' + ip_address_host + '\n') if sysname.find(domain) == -1: # -  hostname  ,  Huawei  Catos sysname = sysname + '.' + domain if log_level == 'debug' or log_level == 'normal': filed.write("check domain : " + sysname + " " + ip_address_host + " " + "\n") print('hostname= ' + sysname) filed.close() 

インターフェースの名前、インターフェースの説明、インターフェースのアドレスを収集し、バインドを構成ファイルに正しく分解することが残っています。 しかし、それについては第2部で詳しく説明します。

PS:ログファイルへのメッセージは、良い方法で別の方法で作成する必要があることに注意してください。
例:時間特殊文字error_code特殊文字error_description特殊文字additional_information。 これは、後でログの自動処理を構成するのに役立ちます。
UPD:バグ修正。

Source: https://habr.com/ru/post/J333614/


All Articles