十四、Python 网络自动化实验室:SSH paramiko和netmiko
在本章中,您将使用 Python 的 SSH 库paramiko和netmiko来控制您的网络设备。paramiko是 Ansible 对网络设备的 SSH 连接管理所依赖的,而netmiko是paramiko的工程师友好版本,因为netmiko也依赖于paramiko。通过研究这些网络模块如何工作,您可以了解依赖这些网络模块的其他应用的内部工作方式。在本章的前半部分,您将学习使用 Python 脚本和paramiko库来替换基本的网络工程师手工任务。在本章的后半部分,你将学习使用netmiko库编写 Python 脚本。一旦您掌握了如何使用这些 SSH 模块,您就可以立即将它们应用到您的工作中。这些 SSH 实验将作为本书末尾开发 IOS XE 升级应用的基础。

使用 paramiko 和 netmiko 库的 Python 网络自动化实验室
在前一章中,我们使用 Telnet 实验室重点介绍了基本的 Python 网络概念。无论您是使用 Telnet 还是 SSH,都可以应用相同的 Python 网络概念。当然,SSH 是比 Telnet 更安全的远程登录协议,它也使用不同的库来登录网络设备。如您所知,Telnet 是一种纯文本网络协议,与 SSH 相比不安全。在大多数安全网络中,通常禁止使用 Telnet,只允许加密的 SSH 连接。要使用 Python 连接到使用 SSH 的 Cisco 设备,必须首先安装 Python paramiko库,并且必须在 Cisco 设备上配置加密的 RSA 密钥。为了开始这一章,让我们首先安装paramiko库来开始我们的 SSH 实验。
Python SSH 实验室:paramiko
在您的 Ubuntu Python automation 服务器上,您可以使用下面显示的pip命令安装paramiko:
pynetauto@ubuntu20s1:~$ pip3 install paramiko
完成paramiko安装后,快速运行 Python 解释器会话,并运行import paramiko来检查库是否可以正确导入。准备好之后,开始第一个 SSH 实验。
pynetauto@ubuntu20s1:~$ python
Python 3.8.2 (default, Jul 16 2020, 14:00:26)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import paramiko
>>>
paramiko 实验 1:在 Python 解释器中交互配置所有设备的时钟和时区
在本实验中,您将登录到ubunsu20s1 Python 自动化服务器上的 Python 解释器,并编写一个实时脚本来与所有路由器和交换机交互,以更改它们的时间和时区设置。您必须打开所有三台路由器和两台交换机的电源。如果您的 PC 使用较旧的低性能 CPU 或更少的内存,您只需打开一台路由器和一台交换机的电源;这将使您在本实验中避免高 CPU 利用率和内存争用问题。见图 14-1 。

图 14-1。
SSH paramiko 实验室 1,正在使用的设备
您将练习一个交互式 SSH 会话,以便从 Python 解释器 shell 管理您的网络设备。在通过 SSH 连接到 Cisco 设备之前,我们必须首先为每台设备生成 RSA 密钥。由于我们在实验室中没有使用 AAA RADIUS 服务器进行身份认证,因此我们将为每台设备创建本地证书。按照以下任务为第一个 SSH 实验做准备:
||
工作
|
| — | — |
| 1 | 使用以下 Cisco IOS 命令在每台设备上创建 1,024 位本地 RSA 密钥,并仅启用 SSH 连接来管理每台设备。以下示例仅显示了LAB-R1路由器上的配置,但相同的配置必须应用于所有其它设备,以便在您的网络上完全禁用 Telnet。LAB-R1# configure terminal |
| | Enter configuration commands, one per line. End with CNTL/Z. |
| | LAB-R1(config)# ip domain-name pynetauto.local |
| | LAB-R1(config)# crypto key generate rsa |
| | The name for the keys will be: LAB-R1.pynetauto.local |
| | Choose the size of the key modulus in the range of 360 to 4096 for your |
| | General Purpose Keys. Choosing a key modulus greater than 512 may take |
| | a few minutes . |
| | How many bits in the modulus [512]: 1024 |
| | % Generating 1024 bit RSA keys, keys will be non-exportable... |
| | [OK] (elapsed time was 2 seconds) |
| | LAB-R1(config)# |
| | *Aug 9 13:04:41.381: %SSH-5-ENABLED: SSH 1.99 has been enabled |
| | LAB-R1(config)# line vty 0 15 |
| | LAB-R1(config-line)# transport input all |
| | LAB-R1(config-line)# end |
| | LAB-R1# write memory |
| | 现在在lab-r2、LAB-SW1、lab-sw2和R1上应用先前的配置。您可以一次复制以下命令,并将其粘贴到每个设备的控制台中: |
| | configure terminal |
| | ip domain-name pynetauto.local |
| | crypto key generate rsa |
| | 1024 |
| | line vty 0 15 |
| | transport input all |
| | do write memory |
| 2 | 您可以使用ssh –l user_name IP_address命令 SSH 到任意两台 Cisco 设备之间的另一台设备。从R1中,使用以下命令 SSH 到所有其他路由器和交换机: |
| | R1# ssh -l pynetauto 192.168.183.10 |
| | ************************************************************************** |
| | * IOSv is strictly limited to use for evaluation, demonstration and IOS * |
| | * education. IOSv is provided as-is and is not supported by Cisco's * |
| | * Technical Advisory Center. Any use or disclosure, in whole or in part, * |
| | * of the IOSv Software or Documentation to any third party for any * |
| | * purposes is expressly prohibited except as otherwise authorized by * |
| | * Cisco in writing. * |
| | ************************************************************************** |
| | Password:******* |
| | [...omitted for brevity] |
| | LAB-R1# exit |
| | [Connection to 192.168.183.10 closed by foreign host] |
| | R1# |
| | 尝试 ssh 到其他设备和 vise vesa。 |
| | R1# ssh -l pynetauto 192.168.183.20 |
| | R1# ssh -l pynetauto 192.168.183.101 |
| | R1# ssh -l pynetauto 192.168.183.102 |
| 3 | 如果想从 Linux 服务器测试 SSH 连接,可以使用ssh user_name IP_address命令。但是,由于遗留的 SSH 密钥交换问题,您可能必须指定加密密钥类型,以便从 Linux Python 服务器 SSH 到 Cisco 设备。 |
| | Ubuntu SSH 登录到 R1 实验室(192.168.183.10)示例: |
| | pynetauto@ubuntu20s1:~$ ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 -c 3des-cbc pynetauto@192.168.183.10 |
| | The authenticity of host '192.168.183.10 (192.168.183.10)' can't be established. |
| | RSA key fingerprint is SHA256:D9HqPuccjTD+WAGLMOYkyZ3KooHqYqe+5n7Hs2AI67I. |
| | Are you sure you want to continue connecting (yes/no/[fingerprint])? yes |
| | Warning: Permanently added '192.168.183.10' (RSA) to the list of known hosts . |
| | [...omitted for brevity] |
| | Password: ********* |
| | [...omitted for brevity] |
| | LAB-R1# exit |
| | Connection to 192.168.183.10 closed. |
| | pynetauto@ubuntu20s1:~$ |
| | Ubuntu SSH login to LAB-SW1 (192.168.183.101) example: |
| | pynetauto@ubuntu20s1:~$ ssh -l pynetauto 192.168.183.101 -c aes256-cbc -oKexAlgorithms=+diffie-hellman-group1-sha1 |
| | The authenticity of host '192.168.183.101 (192.168.183.101)' can't be established. |
| | RSA key fingerprint is SHA256:0sbazxGQ82A3KAC26l6msWR3BrklVXnW0sHChkij23g. |
| | Are you sure you want to continue connecting (yes/no/[fingerprint])? yes |
| | Warning: Permanently added '192.168.183.101' (RSA) to the list of known hosts. |
| | [...omitted for brevity] |
| | Password: ********* |
| | [...omitted for brevity] |
| | LAB-SW1# exit |
| | Connection to 192.168.183.101 closed. |
| | pynetauto@ubuntu20s1:~$ |
| 4 | 假设您想更进一步,简化从 Linux 服务器的 SSH 登录。在这种情况下,首先必须检查 Linux 服务器上使用的 SSH 版本,以避免 Linux 服务器和 Cisco 设备之间可能出现的 Diffie-Hellman 密钥交换问题。通过修改ubuntu20s1上的.ssh/config文件,在您的 Linux 服务器上永久使用diffie-hellman-group1-sha1密钥交换方法。 |
| | 要检查 Python 服务器上的 SSH 版本,请使用以下命令: |
| | pynetauto@ubuntu20s1:~$ ssh -V |
| | OpenSSH_8.2p1 Ubuntu-4, OpenSSL 1.1.1f 31 Mar 2020 |
| | 要对密钥交换方法进行硬编码,请打开/home/user_name/.ssh/config文件,并将以下信息添加到文件中。这将在 SSH 客户端(ubuntu20s1)上启用旧的算法,允许它连接到旧类型的 SSH 服务器(在本例中是 Cisco 设备)。 |
| | pynetauto@ubuntu20s1:~$ nano /home/pynetauto/.ssh/config |
| | GNU nano 4.8 /home/pynetauto/.ssh/config |
| | Host 192.168.183.10 |
| | KexAlgorithms +diffie-hellman-group1-sha1 |
| | Host 192.168.183.20 |
| | KexAlgorithms +diffie-hellman-group1-sha1 |
| | Host 192.168.183.101 |
| | KexAlgorithms +diffie-hellman-group1-sha1 |
| | Host 192.168.183.102 |
| | KexAlgorithms +diffie-hellman-group1-sha1 |
| | Host 192.168.183.133 |
| | KexAlgorithms +diffie-hellman-group1-sha1 |
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos |
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line |

此外,如果您由于这个问题遇到了已知主机问题,您可以按照下面论坛中的说明从您的 Linux 服务器中删除记录,并尝试重新建立到 Cisco 网络设备的 SSH 连接。
https://superuser.com/questions/30087/remove-key-from-known-hosts
此外,如果您在 Cisco 路由器或交换机上遇到 RSA 密钥问题,您可以“归零”RSA 以重新创建 RSA 密钥。您可以使用以下命令清除上一个密钥:
R1 (config)# crypto key zeroize rsa
Configure SSH Configuration again
R1(config)# hostname <name>
R1(config)# ip domain-name <domain>
R1(config)# crypto key generate rsa
R1(config)# ip ssh version 2
如果 GNS3 中的设备遇到 Diffie-Hellman 密钥交换问题,并且您无法找到解决方案,则保存运行配置,从拓扑中删除节点,然后重新配置您的设备;这可能与 GNS3、VMware 和 Cisco 映像文件不兼容问题有关。
||
工作
|
| — | — |
| 5 | 现在,让我们在ubuntu20s1服务器上打开 Python 解释器,输入以下内容,通过 SSH 实时连接配置正确的时间和时区。请将时区更新为您自己的当地时间。是的,你需要在交互式会话中逐字键入以下内容来练习编写代码。Python 开发工程师每天都要编写成百上千行代码,这可不是在公园里散步。 |
| | 同样,如果您使用不同的 IP 地址范围,请更新 IP 地址。此外,每个冒号后的四个前导空格要精确,并注意任何特殊方法中的大写。运行 Python 解释器会话的命令时,请确保将时间更改为当前时间和时区。 |
| | >>>``import paramiko |
| | >>>``import time |
| | >>> |
| | >>>``ip_addresses = ["192.168.183.10", "192.168.183.20", "192.168.183.101", "192.168.183.102", "192.168.183.133"] |
| | >>> |
| | > > >username = "pynetauto" # username variable |
| | >>>``password = "cisco123" |
| | >>> |
| | >>>``ssh_client = paramiko.SSHClient() |
| | >>>``ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) |
| | >>>``for ip in ip_addresses: |
| | ...``ssh_client.connect(hostname=ip, username=username, password=password) |
| | ...``print("Connected to " + ip + "\n") |
| | ...``remote_connection = ssh_client.invoke_shell() |
| | ... remote_connection.send("terminal length 0\n") |
| | ...``remote_connection.send("configure terminal\n") |
| | ...``remote_connection.send("clock timezone AEST +10\n") |
| | ...``remote_connection.send("clock summer-time AEST recurring\n") |
| | ...``remote_connection.send("exit\n") |
| | ...``time.sleep(1) |
| | ...``remote_connection.send("clock set 15:15:00 12 Jan 2021\n") |
| | ...``remote_connection.send("copy running-config startup-config\n") |
| | ...``remote_connection.send("end\n") |
| | ...``output = remote_connection.recv(6000) |
| | ...``print((output).decode('ascii')) |
| | ...``time.sleep(2) |
| | ...``ssh_client.close() |
| 6 | 如果您的交互式会话进展顺利,它会在登录到每台设备并配置每台设备上的时钟和时区时打印出会话。在本实验结束时,您的所有设备都应该配置为符合您当地时区的正确时间。为了检查配置,让我们配置另一个运行show命令并显示它的快速脚本。在 Ubuntu Python automation server 上执行这些任务。您不再需要登录每台设备来运行相同的命令五次。只需从您的服务器上运行它们。 |
| | pynetauto@ubuntu20s1:~$ mkdir ssh_labs |
| | pynetauto@ubuntu20s1:~$ cd ssh_labs |
| | pynetauto@ubuntu20s1:~/ssh_labs$ nano display_show.py |
| | GNU nano 4.8 /home/pynetauto/ssh_labs/ display_show.py |
| | import time |
| | import paramiko |
| | ip_addresses = ["192.168.183.10", "192.168.183.20", "192.168.183.101", "192.168.183.102", "192.168.183.133"] |
| | username = "pynetauto" |
| | password = "cisco123" |
| | for ip in ip_addresses : |
| | ssh_client = paramiko.SSHClient() |
| | ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) |
| | ssh_client.connect(hostname=ip, username=username, password=password) |
| | print("Connected to " + ip + "\n") |
| | remote_connection = ssh_client.invoke_shell() |
| | output1 = remote_connection.recv(3000) # Catches and removes the login prompt output |
| | # print(output1.decode('ascii')) # remove hash to print the login prompt message |
| | # Now send the commands you want to run and display on the screen |
| | remote_connection.send("show clock detail\n") |
| | time.sleep(2) |
| | output2 = remote_connection.recv(6000) |
| | print((output2).decode('ascii')) |
| | print("-"*80) |
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos |
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line |
| | 之前的 Python 脚本就像是一个针对多个网络设备的show命令工具。现在运行脚本来检查所有设备上的时钟和时区更改。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python3 display_show.py |
| | Connected to 192.168.183.10 |
| | show clock detail |
| | 15:15:23.609 AEST Tue Jan 12 2021 |
| | Time source is user configuration |
| | Summer time starts 02:00:00 AEST Sun Mar 14 2021 |
| | Summer time ends 02:00:00 AEST Sun Nov 7 2021 |
| | LAB-R1# |
| | -------------------------------------------------------------------------------- |
| | Connected to 192.168.183.20 |
| | show clock detail |
| | 15:15:23.615 AEST Tue Jan 12 2021 |
| | Time source is user configuration |
| | Summer time starts 02:00:00 AEST Sun Mar 14 2021 |
| | Summer time ends 02:00:00 AEST Sun Nov 7 2021 |
| | lab-r2# |
| | -------------------------------------------------------------------------------- |
| | [...omitted for brevity] |
| 7 | 当您处于交互模式时,您可以在导入一个库后使用dir(library_name)显示每个库可用的模块。在前面的例子中,您已经使用了paramiko库中的SSHClient和AutoAddPolicy模块。 |
| | >>> import paramiko |
| | >>> dir(paramiko) |
| | ['AUTH_FAILED', 'AUTH_PARTIALLY_SUCCESSFUL', 'AUTH_SUCCESSFUL', 'Agent', 'AgentKey', 'AuthHandler', 'AuthenticationException' , 'AutoAddPolicy', 'BadAuthenticationType', |
| | [...omitted for brevity] |
| | 'SFTP_NO_CONNECTION', 'SFTP_NO_SUCH_FILE', 'SFTP_OK', 'SFTP_O P_UNSUPPORTED', 'SFTP_PERMISSION_DENIED', 'SSHClient', 'SSHConfig', 'SSHException', 'SecurityOptions', 'ServerInterface', 'Su bsystemHandler', 'Transport', 'WarningPolicy', '__all__', '__author__', '__builtins__', '__cached__', |
| | [...omitted for brevity] |
| | 'sftp_client', 'sftp_file', 'sftp_handle', 'sftp_server', 'sftp_si', 'ssh_exception', 'ssh_gss', 'sys', 'transport', 'util'] |
| 8 | 要在paramiko中查看每个模块的包,您可以首先导入每个模块,然后再次使用dir(module_name)查看该模块的一些细节。 |
| | >>> from paramiko import SSHClient |
| | >>> dir(SSHClient) |
| | ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__ getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__' , '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_aut h', '_families_and_addresses', '_key_from_filepath', '_log', 'close', 'connect', 'exec_command', 'get_host_keys', 'get_transp ort', 'invoke_shell', 'load_host_keys', 'load_system_host_keys', 'open_sftp', 'save_host_keys', 'set_log_channel', 'set_missi ng_host_key_policy'] |
| | >>> from paramiko import AutoAddPolicy |
| | >>> dir(AutoAddPolicy) |
| | ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__' , '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce _ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'missing_host_key'] |
paramiko 实验 2:在没有用户交互的情况下在 Cisco 设备上配置 NTP 服务器(NTP 实验)
在本实验中,您将配置LAB-R1和lab-r2,从两个外部文件中读取信息。第一个文件每行包含每个路由器的 IP 地址,另一个文件包含用户名和密码。当您使用外部文件时,网络管理员不必坐在设备的远程控制台前以交互方式键入信息。在下一章,你将学习如何使用 Linux cron作为一个调度器来运行你的 Python 应用,而不需要你的交互,这样你的应用就变成了机器人。与 Cisco ACI 和 Red Hat Ansible Tower 提供的花哨功能相比,使用 Python 脚本作为应用和使用 Linux cron作为默认调度程序可能看起来相当简陋(对您的公司没有成本)和原始(没有美化的 GUI)。尽管如此,你必须了解cron是如何工作的,以及如何定制你的环境,因为每个客户都有不同的网络环境。没有哪两个网络在规模和复杂程度上是相同的。毕竟,计划者就是计划者。见图 14-2 。

图 14-2。
SSH paramiko 实验 2,使用中的设备
您需要打开 IP 服务服务器centos8s1的电源,为路由器提供 NTP 服务。对于本实验,您需要打开ubuntu20s1、centos8s1、LAN-R1和lab-r2的电源,并打开LAB-SW1和lab-sw2的电源,以连接两台路由器。在这个实验室中,您必须完成许多任务,所以让我们编写脚本并在实验室中运行它。
|
工作
|
| — | — |
| 1 | 如果您还没有创建ssh_labs目录,在ubuntu20s1服务器上,按照以下步骤为 SSH labs 创建一个新目录,然后在同一个目录中创建两个外部文件: |
| | pynetauto@ubuntu20s1:~/telnet_labs$``cd |
| | pynetauto@ubuntu20s1:~$ mkdir ssh_labs |
| | pynetauto@ubuntu20s1:~$ cd ssh_labs |
| | pynetauto@ubuntu20s1:~/ssh_labs$ nano routerlist |
| | pynetauto@ubuntu20s1:~/ssh_labs$ cat routerlist |
| | 192.168.183.10 |
| | 192.168.183.20 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ nano adminpass |
| | pynetauto@ubuntu20s1:~/ssh_labs$ cat adminpass |
| | pynetauto |
| | cisco123 |
| 2 | 我已经做了谷歌搜索,复制了一个示例ssh_client.py脚本,并修改了内容以适应我的需要。您可以到下面的站点获取基本模板 SSH 脚本,开始编写 SSH 脚本。 |
| | URL: https://gist.github.com/ghawkgu/944017 |
| | ssh _ client . py |
| | #!/usr/bin/env python |
| | import paramiko |
| | hostname = 'localhost' |
| | port = 22 |
| | username = 'foo' |
| | password = 'xxxYYYxxx' |
| | if __name__ == "__main__": |
| | paramiko.util.log_to_file('paramiko.log') |
| | s = paramiko.SSHClient() |
| | s.load_system_host_keys() |
| | s.connect(hostname, port, username, password) |
| | stdin, stdout, stderr = s.exec_command('ifconfig') |
| | print stdout.read() |
| | s.close() |
| | 该脚本将在两台路由器上配置 NTP 服务器,并在网络上启用时间同步。NTP 在企业网络中起着重要作用,它可以保持所有设备的时间同步,因此系统日志和报告服务器上的时间戳精确到秒。因为这不是一本网络书籍,所以我不会对 stratum 和 NTP 特性进行过多的描述,但是在这里要注意一些事情。首先,思科和许多厂商的网络设备不信任运行在 Windows 机器上的微软 W32tm 服务。第二,某些 Cisco 网络和服务器设备的时间必须设置为最低层,以作为可靠的时间源。通常,该值等于或小于第五层,下层是更值得信任的时间。 |
| | 修改基本paramiko登录配置。完成后,它应该类似于以下脚本: |
| | pynetauto@ubuntu20s1:~/ssh_labs$ nano ssh_ntp_lab.py |
| | GNU nano 4.8/home/pynetato/ssh _ labs/ssh _ NTP _ lab . py |
| | #!/usr/bin/env python3 |
| | import paramiko # import paramiko library |
| | import time # import time module |
| | from datetime import datetime # import datetime module from datetime library |
| | t_ref = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") # Time reference in desired time format |
| | file1 = open("routerlist") # open routerlist as file1 |
| | for line in file1: # for loop for router ip address |
| | print(t_ref) # print time reference |
| | print ("Now logging into " + (line)) # print statement |
| | ip_address = line.strip() # remove any white spaces |
| | file2= open("adminpass") # open adminpass as file2 |
| | for line1 in file2: # read the first line(admin ID) in file 2 |
| | username = line1.strip() # remove any white spaces |
| | for line2 in file2 : # read the second line (password) in file2 |
| | password = line2.strip() # remove any white spaces |
| | ssh_client = paramiko.SSHClient() # Create paramiko SSH client object |
| | ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # Automatically accept host key policy |
| | ssh_client.connect(hostname=ip_address,username=username,password=password) # SSH connection objects |
| | print ("Successful connection to " + (ip_address) +"\n") # print statement |
| | print ("Now completing following tasks : " + "\n") # print statement |
| | remote_connection = ssh_client.invoke_shell() # invoke shell session |
| | output1 = remote_connection.recv(3000) # Catches and removes the login prompt output |
| | # print(output1.decode('ascii')) # remove hash to print the login prompt message |
| | remote_connection.send("configure terminal\n") # Move to configuration mode |
| | print ("Configuring NTP Server") # print statement |
| | remote_connection.send("ntp server 192.168.183.130\n") # configure NTP server |
| | remote_connection.send("end\n") # go back to exec privilege mode |
| | remote_connection.send("copy running-config start-config\n") # send save command |
| | print () # print remote_connections |
| | time.sleep(2) # |
| | output2 = remote_connection.recv(6000) # capture session in output variable |
| | print((output2).decode('ascii')) # print output using ASCII decoding |
| | print (("Successfully configured your device & Disconnecting from ") + (ip_address)) # Print statement |
| | ssh_client.close # Close SSH connection |
| | time.sleep(2) # Pause for 2 seconds |
| | file1.close() # Close file1 |
| | file2.close() # Close file2 |
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos |
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line |
| 3 | 由于我们只有两台设备需要检查,这次您可以手动检查端口。登录LAB-R1和lab-r2,然后运行show control-plane host open-ports命令,检查 SSH 端口(22)是否正常工作并处于LISTEN状态。 |
| | LAB-R1# show control-plane host open-ports |
| | Active internet connections (servers and established) |
| | Prot Local Address Foreign Address Service State |
| | tcp *:22 *:0 SSH-Server LISTEN |
| | tcp *:23 *:0 Telnet LISTEN |
| | udp *:56827 *:0 udp_transport Server LISTEN |
| | lab-r2# show control-plane host open-ports |
| | Active internet connections (servers and established) |
| | Prot Local Address Foreign Address Service State |
| | tcp *:22 *:0 SSH-Server LISTEN |
| | tcp *:23 *:0 Telnet LISTEN |
| | udp *:18999 *:0 udp_transport Server LISTEN |
| | 或者,您可以修改display_show.py文件并从 Python 文件中获取信息。下载chapter 14_codes.zip中包含的display_show_control_plane.py,使用它可以得到相同的结果。 |
| | 源代码下载网址: https://github.com/pynetauto/apress_pynetauto |
| 4 | 现在,在运行脚本之前,检查 NTP 服务是否在centos8s1上正确运行。如果您发现 NTP 服务没有运行,您必须在运行脚本之前解决该问题。在 CentOS 8.1 上,我们之前在第八章安装了chronyd,作为 NTP 服务器。以下是解决chronyd常见问题的命令。下一步,我们必须通过修改chrony.conf文件来完成服务器配置,使其成为一个功能服务器。 |
| | [pynetauto@centos8s1 ~]$ systemctl status chronyd |
| | [pynetauto@centos8s1 ~]$ systemctl restart chronyd |
| | [pynetauto@centos8s1 ~]$ systemctl enable chronyd |
| | [pynetauto@centos8s1 ~]$ sudo firewall-cmd --add-port=123/udp --permanent |
| | [pynetauto@centos8s1 ~]$ sudo firewall-cmd --reload |
| | 如果您忘记安装chrony,请使用dnf install命令进行安装。 |
| | [pynetauto@centos8s1 ~]$ dnf install chrony |
| | 另外,检查 NTP 服务器上的当前时间。 |
| | [pynetauto@centos8s1 ~]$ date |
| | Tue Jan 12 17:13:16 AEDT 2021 |
| | 对于chronyd,您必须从这里指定的 URL 获取您的本地区域附近的 NTP 服务器列表,并且您必须更新 NTP 服务器池,如图 14-1 所示。确保用散列(#)注释掉第一个默认行。请确保从以下站点选择离您最近的公共 NTP 服务器: |
| | URL: https://www.pool.ntp.org/en/ |
| | 在sudo模式下打开文件,修改/etc/下的chrony.conf文件。 |
| | [pynetauto@centos8s1 ~]$ sudo nano /etc/chrony.conf |
| | GNU nano 4.8 /etc/chrony.conf |
| | # Use public servers from the pool.ntp.org project. |
| | # Please consider joining the pool ( http://www.pool.ntp.org/join.html). |
| | # pool 2.centos.pool.ntp.org iburst |
| | server 0.oceania.pool.ntp.org |
| | server 1.oceania.pool.ntp.org |
| | server 2.oceania.pool.ntp.org |
| | server 3.oceania.pool.ntp.org |
| | # Record the rate at which the system clock gains/losses time. |
| | driftfile /var/lib/chrony/drift |
| | [...omitted for brevity] |
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos |
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line |
| | 当您在同一个配置文件上时,转到该行的末尾,确认您的网络已被允许从本地网络访问此 NTP 服务器。如果您已经在第八章中对此进行了配置,它应该已经在那里了。 |
| | [... omitted for brevity] |
| | # Allow NTP client access from the local network. |
| | allow 192.168.183.0/24 |
| 5 | 如果一切看起来都井然有序,返回到ubuntu8s1 Python 服务器并运行ssh_ntp_lab.py脚本。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python ssh_ntp_lab.py |
| | 2021-01-12_17-31-59 |
| | Now logging into 192.168.183.10 |
| | Successful connection to 192.168.183.10 |
| | Now completing following tasks : |
| | Configuring NTP Server |
| | configure terminal |
| | Enter configuration commands, one per line. End with CNTL/Z. |
| | LAB-R1(config)#ntp server 192.168.183.130 |
| | LAB-R1(config)#end |
| | LAB-R1#copy running-config start-config |
| | Destination filename [start-config]? |
| | Successfully configured your device & Disconnecting from 192.168.183.10 |
| | 2021-01-12_17-31-59 |
| | Now logging into 192.168.183.20 |
| | Successful connection to 192.168.183.20 |
| | Now completing following tasks : |
| | Configuring NTP Server |
| | configure terminal |
| | Enter configuration commands, one per line. End with CNTL/Z. |
| | lab-r2(config)#ntp server 192.168.183.130 |
| | lab-r2(config)#end |
| | lab-r2#copy running-config start-config |
| | Destination filename [start-config]? |
| | Successfully configured your device & Disconnecting from 192.168.183.20 |
| 6 | 一旦 NTP 脚本成功运行,使用新的show命令修改display_show.py脚本并运行它。在这个实验室中,我将新的show命令脚本命名为display_show_ntp.py。要检查 NTP 状态,请在脚本中包含show ntp status命令。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ cp display_show.py display_show_ntp.py |
| | 您可以随时在 CLI 中检查状态,以便快速验证。 |
| | LAB-R1# show ntp status |
| | lab-r2 # show ntp status |
| | 如果您看到如下所示的类似 NTP 状态消息,您的路由器的时间已经与 Linux NTP 服务器的时间(192.168.183.130)同步。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python3 display_show_ntp.py |
| | Connected to 192.168.183.10 |
| | show ntp status |
| | Clock is synchronized, stratum 5, reference is 192.168.183.130 |
| | nominal freq is 1000.0003 Hz, actual freq is 1000.0003 Hz, precision is 2**15 |
| | ntp uptime is 191100 (1/100 of seconds), resolution is 1000 |
| | reference time is E3A7C697.1089E8E5 (06:56:23.064 UTC Tue Jan 12 2021) |
| | clock offset is 9948.5451 msec, root delay is 7.75 msec |
| | root dispersion is 16599.57 msec, peer dispersion is 2.51 msec |
| | loopfilter state is 'SPIK' (Spike), drift is 0.000499999 s/s |
| | system poll interval is 64, last update was 375 sec ago. |
| | -------------------------------------------------------------------------------- |
| | Connected to 192.168.183.20 |
| | show ntp status |
| | Clock is synchronized, stratum 5, reference is 192.168.183.130 |
| | nominal freq is 1000.0003 Hz, actual freq is 1000.0003 Hz, precision is 2**15 |
| | ntp uptime is 190000 (1/100 of seconds), resolution is 1000 |
| | reference time is E3A7C6A4.121D0A5D (06:56:36.070 UTC Tue Jan 12 2021) |
| | clock offset is 2100.9149 msec, root delay is 12.98 msec |
| | root dispersion is 7958.81 msec, peer dispersion is 4.38 msec |
| | loopfilter state is 'CTRL' (Normal Controlled Loop), drift is 0.000499999 s/s |
| | system poll interval is 64, last update was 736sec ago. |
| | -------------------------------------------------------------------------------- |
您已经成功地在LAB-R1和lab-r2路由器上配置了 NTP 服务器,并与网络中的 Linux NTP 服务器同步了时间。此外,不需要一次输入一个 IP 地址和管理员凭证;在运行脚本之前,您将它们预加载到两个独立的文件中。这将在第八章的cron实验室派上用场。密码安全和密码库是重要的主题;然而,它们超出了本书的范围。现在,让我们快速进入下一个 SSH 实验,将我们的设备备份到我们的 TFTP 服务器。
paramiko 实验 3:创建一个交互式 paramiko SSH 脚本,将正在运行的配置保存到 TFTP 服务器
在本实验中,您将使用 Python 的input函数和getpass模块制作一个交互式工具来获取网络管理员的 ID 和密码。为了简化实验,脚本将使用一个包含所有 IP 地址的列表,备份文件将保存到运行在centos8s1 (192.168.183.130)上的 TFTP 服务器。通过简单的修改,您可以定制和制作操作工具,以节省时间和资源。在本实验中,您只对五台设备进行备份,但是想象一下,如果像在一些客户的网络中一样,需要在 500 台设备上完成这项工作。此外,如果您想使用自动调度程序每天运行这样的脚本,工程师们不再需要坐在他们的计算机前进行备份。当然,也有企业级网络设备备份解决方案,但我们希望通过在我们的实验室中重新创建并运行这些工具,来深入了解这个主题,并了解这些工具的工作原理。见图 14-3 。

图 14-3。
SSH paramiko 实验 3,使用中的设备
开箱即用的供应商解决方案具有卓越的功能,适合大多数规模庞大的标准化客户环境。尽管如此,您几乎总是需要一些定制,以使解决方案在真实的生产环境中更加通用和有用。没有进一步的讨论,让我们写一个交互式工具 SSH 到我们的路由器和交换机,并采取一些备份。
||
工作
|
| — | — |
| 1 | 作为第一步,检查是否可以从 Python 自动化服务器 ping 所有网络设备。这一次,您将再次使用fping并通过一个命令检查连通性。如果您仍然没有安装fping,现在使用以下命令进行安装: |
| | pynetauto@ubuntu20s1:~/ssh_labs$ sudo apt-get install fping |
| | 成功安装fping后,运行如下命令来确认您的服务器的网络连接: |
| | pynetauto@ubuntu20s1:~/ssh_labs$ fping 192.168.183.10 192.168.183.20 192.168.183.101 192.168.183.102 192.168.183.133 |
| | 192.168.183.10 is alive |
| | 192.168.183.20 is alive |
| | 192.168.183.133 is alive |
| | 192.168.183.102 is alive |
| | 192.168.183.101 is alive |
| 2 | 您已经确认了网络连接,现在让我们来确认我们之前安装的 TFTP 服务是否在centos8s1多 IP 服务服务器上正常工作。使用以下命令检查 Linux 服务器上 TFTP 服务的运行状态。 |
| | TFTP ( xinetd)处于活动状态,并且在监听模式下与端口 69 一起正常工作。 |
| | [pynetauto@centos8s1 ~]$ systemctl is-active xinetd.service |
| | active |
| | [pynetauto@centos8s1 ~]$ sudo netstat -anp | grep 69 |
| | [sudo] password for pynetauto: |
| | udp 0 0 0.0.0.0:69 0.0.0.0:* 1241/xinetd |
| | udp6 0 0 :::69 :::* |
| | [... omitted for brevity] |

如果您的服务器的端口 69 没有列出,您必须使用以下命令使用ufw命令来允许您的服务器上的端口:
[pynetauto@centos8s1 ~]$ sudo ufw allow 69/udp
|
工作
|
| — | — |
| 3 | 现在,为了让这个实验更加有趣,我们想测量端到端任务从开始到结束需要多长时间,这样我们就可以测量脚本的速度,并根据需要调整睡眠计时器。正如您已经在paramiko和telnet脚本中观察到的,已经添加了time.sleep(s)代码,以允许虚拟路由器和交换机有足够的响应时间,并且通过netmiko脚本,许多计时器都内置到库中。然而,它并不总是 100%准确,所以有时,你仍然需要使用睡眠定时器来定制不同的设备。 |
| | 当你创建应用时,你必须开发一些小创意和小工具来使你的 Python 脚本更有价值。下面显示的计时器收费脚本是一个建议,这个工具测量从 1 到 1000 万计数所需的时间。你可以调整数字,看看你的电脑计算数字的速度有多快。您还可以将您的函数或任务放在脚本中间,以测量脚本运行的速度。 |
| | 在 Python 中,一个函数处理数据的速度比另一个函数快得多,花时间测试脚本的速度是值得的。毕竟,Python 并不以其速度著称,但通常是 Python 编码人员选择了错误的模块或工具,并给脚本增加了延迟。此外,在网络自动化领域,速度很重要,但不是 Python 应用最关键的部分。通常精度比速度更重要。如果你真的需要速度,总有一种最接近机器语言的 C 编程语言。无论如何,在您的服务器上编写以下代码,并检查这个简单的脚本是如何工作的。虽然这是一个简单的脚本,但是许多简单的脚本构成了您的应用的基础。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ nano timer_tool.py |
| | GNU nano 4.8 /home/pynetauto/ssh_labs/timer_tool.py |
| | import time # Import time module |
| | start_timer = time.mktime(time.localtime()) # Start time |
| | ########## REPLACE ME! ######### |
| | big_number = range(10000000) # Put some function or task to run here |
| | for i in big_number: |
| | print(i,end=" ") |
| | # print(" ".join(str(i) for i in big_number)) # This is a join method to print the number, can replace above two lines of code |
| | ############################## |
| | total_time = time.mktime(time.localtime()) - start_timer # End time minus start time |
| | print("Total time : ", total_time, "seconds") # Total time format |
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos |
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line |
| 4 | 猜猜你的电脑从 1 数到 1000 万需要多长时间?在您的ubuntu20s1 Python 服务器上,通过键入python timer_tool.py运行计时器工具。对于测试计算机来说,从 1 数到 1000 万用了整整 22 秒。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python timer_tool.py |
| | [...omitted for brevity] |
| | 9999974 9999975 9999976 9999977 9999978 9999979 9999980 9999981 9999982 9999983 9999984 9999985 9999986 9999987 9999988 9999989 9999990 9999991 9999992 9999993 9999994 9999995 9999996 9999997 9999998 9999999 |
| | Total time : 22.0 seconds |
| 5 | 为了使我们的脚本能够与网络管理员交互,让我们使用getpass库的getpass模块制作一个用户名和密码收集器工具。使用getpass模块,我们可以隐藏输入的密码,并且通过一些修改,我们可以让用户再次输入密码,以验证第一个密码。密码经常输入错误,我们可以通过将if语句合并到我们的密码脚本中来解决这个问题。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ nano password_tool.py |
| | GNU nano 4.8 /home/pynetauto/ssh_labs/password_tool.py |
| | from getpass import getpass # Import getpass module from getpass library |
| | def get_credentials() :# Create get_credentials function |
| | #Prompts for and returns a username and password # Comment |
| | username = input("*Enter Network Admin ID : ") # Prompt user for username |
| | password = None # Set original password to None |
| | while not password: # Keep prompting til password is entered |
| | password = getpass("*Enter Network Admin PWD : ") # Prompt for password |
| | password_verify = getpass("**Confirm Network Admin PWD : ") # Verify password again |
| | if password != password_verify: # If password fails to verify, run the following script |
| | print("! Network Admin Passwords do not match. Please try again.") # Inform the user of mismatch |
| | password = None # Reset password to None and ask for the password again |
| | print(username, password) # For testing purpose only, remove this when applied to the script |
| | return username, password #returns username and password |
| | get_credentials() # Run get_credentials() function |
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos |
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line |
| 6 | 请创建一个密码工具脚本,并从您的ubuntu20s1服务器运行它。出于测试目的,我们将在这里打印用户名和密码,但您必须从功能中删除print ( username , password )命令,并在按下回车键后进入静默状态。根据需要注释掉print语句。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python password_tool.py |
| | *Enter Network Admin ID : pynetauto |
| | *Enter Network Admin PWD :******** |
| | **Confirm Network Admin PWD : ******** |
| | pynetauto cisco123 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ |
| 7 | 现在,您已经开发了两个迷你工具,让我们编写交互代码来备份我们设备的实验室网络。完成paramiko脚本后,您的脚本应该类似于下面这样。详细的解释嵌入在代码中,所以在完整地编写脚本之前,先研究一下代码。如果你遇到问题,参考源代码,但只有在必要的时候。通常大多数代码是由你在键盘上输入的,而不是剪切和粘贴的! |
| | pynetauto@ubuntu20s1:~/ssh_labs$ nano interactive_backup.py |
| | GNU nano 4.8 /home/pynetauto/ssh_labs/interactive_backup.py |
| | #!/usr/bin/env python3 # for Linux system to run this script as python3 file |
| | import re # import regular expression module |
| | import time # import time module |
| | import paramiko # import paramiko library |
| | from datetime import datetime # import datetime module from datetime library |
| | from getpass import getpass # Import getpass module from getpass library |
| | t_ref = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") # Time reference to be used for file name |
| | device_list = ["192.168.183.10", "192.168.183.20", "192.168.183.101", "192.168.183.102", "192.168.183.133"] # Device list |
| | start_timer = time.mktime(time.localtime()) # Start time |
| | def get_credentials():# Define a function called get_credentials |
| | #Prompts for, and returns a username and password # comment |
| | global username # Make username global, so it can be used throughout this script |
| | global password # Make password global, so it can be used throughout this script |
| | username = input("*Enter Network Admin ID : ") # Enter username |
| | password = None # set password default value to None |
| | while not password: # Until password is entered |
| | password = getpass("*Enter Network Admin PWD : ") # Enter the password first time |
| | password_verify = getpass("**Confirm Network Admin PWD : ") # Get password for verification, second time |
| | if password != password_verify: # Password verification |
| | print("! Network Admin Passwords do not match. Please try again.") # Informational |
| | password = None # Set password to None |
| | return username, password # Optional, returns username and password |
| | get_credentials()# Run get_credentials function, to save username and password |
| | for ip in device_list: # for loop to grab device IP addresses |
| | print(t_ref) # Comment for user information |
| | print("Now logging into " + (ip)) # Informational |
| | ssh_client = paramiko.SSHClient()# Initiate paramiko SSH client session as ssh_client |
| | ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # Accept missing host key policy |
| | ssh_client.connect(hostname=ip,username=username,password=password) # SSH connection with credentials |
| | print("Successful connection to " + (ip) +"\n") # Informational |
| | print("Now making running-config backup of " + (ip) + "\n") # Comment for user information |
| | remote_connection = ssh_client.invoke_shell() # Invoke shell |
| | time.sleep(3) # Pause 3 seconds to invoke shell |
| | remote_connection.send("copy running-config tftp\n") # Send copy command |
| | remote_connection.send("192.168.183.130\n") # Respond to TFTP server IP request, TFTP IP |
| | remote_connection.send((ip)+ ".bak@" + (t_ref)+ "\n") # Respond to backup file name request |
| | time.sleep(3) ## Pause for 3 seconds, give device to respond |
| | print() # Print screen |
| | time.sleep(3) # Pause for 3 seconds, give time to process data |
| | output = remote_connection.recv(65535) # Receive output up to 65535 lines |
| | print((output).decode('ascii')) #Print output using ASCII decoding method |
| | print(("Successfully backed-up running-config to TFTP & Disconnecting from ") + (ip) + "\n") # Print statement to provide an update to the user |
| | print("-"*80) # Print ~ 80 times |
| | ssh_client.close # Close SSH session |
| | time.sleep(1) # Pause for 1 second |
| | total_time = time.mktime(time.localtime()) - start_timer # Start time minus current time |
| | print("Total time : ", total_time, "seconds") # Print the total time to run the script in seconds |
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos |
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line |
| 8 | 在第八章中,你在centos8s1服务器上创建了一个tftpdir目录用于 TFTP 文件的共享和存储。作为运行前面脚本的预检查,检查/var/tftpdir目录中的文件。在 Linux 中,改变大小的文件通常放在var(变量)目录下。 |
| | [pynetauto@centos8s1 ~]$ cd /var/tftpdir |
| | [pynetauto@centos8s1 tftpdir]$ pwd |
| | /var/tftpdir |
| | [pynetauto@centos8s1 tftpdir]$ ls -lh |
| | total 45M |
| | -rw-r--r--. 1 pynetauto pynetauto 45M Jul 18 04:59 c3725-adventerprisek9-mz.124-15.T14.bin |
| | -rw-rw-r--. 1 tftpuser tftpuser 1.2K Jan 9 00:07 running-config |
| | -rw-rw-r--. 1 tftpuser tftpuser 100 Jun 8 2020 transfer_file01 |
| | -rw-r--r--. 1 root root 100 Jun 8 2020 transfer_file77 |
| 9 | 回到 Python 自动化服务器(ubuntu20s1)。好了,现在让我们运行交互式备份脚本,通过 SSH 访问每台设备,并在 TFTP 服务器上备份每台设备的运行配置。运行python interactive_backup.py命令,输入网络管理员凭证,然后坐下来观察。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python interactive_backup.py |
| | *Enter Network Admin ID : pynetauto |
| | *Enter Network Admin PWD : ******** |
| | **Confirm Network Admin PWD : ******** |
| | 2021-01-12_18-42-23 |
| | Now logging into 192.168.183.10 |
| | Successful connection to 192.168.183.10 |
| | Now making running-config backup of 192.168.183.10 |
| | [...omitted for brevity] |
| | Destination filename [lab-r1-confg]? 192.168.183.10.bak@2021-01-12_18-42-23 |
| | !! |
| | 3953 bytes copied in 2.972 secs (1330 bytes/sec) |
| | LAB-R1# |
| | Successfully backed-up running-config to TFTP & Disconnecting from 192.168.183.10 |
| | -------------------------------------------------------------------------------- |
| | 2021-01-12_18-42-23 |
| | Now logging into 192.168.183.20 |
| | Successful connection to 192.168.183.20 |
| | [...omitted for brevity] |
| | lab-r2# |
| | Successfully backed-up running-config to TFTP & Disconnecting from 192.168.183.20 |
| | -------------------------------------------------------------------------------- |
| | 2021-01-12_18-42-23 |
| | Now logging into 192.168.183.101 |
| | Successful connection to 192.168.183.101 |
| | [...omitted for brevity] |
| | LAB-SW1# |
| | Successfully backed-up running-config to TFTP & Disconnecting from 192.168.183.101 |
| | -------------------------------------------------------------------------------- |
| | 2021-01-12_18-42-23 |
| | Now logging into 192.168.183.102 |
| | Successful connection to 192.168.183.102 |
| | [...omitted for brevity] |
| | lab-sw2# |
| | Successfully backed-up running-config to TFTP & Disconnecting from 192.168.183.102 |
| | -------------------------------------------------------------------------------- |
| | 2021-01-12_18-42-23 |
| | Now logging into 192.168.183.133 |
| | Successful connection to 192.168.183.133 |
| | [...omitted for brevity] |
| | R1# |
| | Successfully backed-up running-config to TFTP & Disconnecting from 192.168.183.133 |
| | -------------------------------------------------------------------------------- |
| | Total time : 78.0 seconds |
| | 将五台实验室设备备份到 TFTP 服务器需要 78 秒。在接下来的netmiko实验中,我们还将了解 FTP 的文件管理,但是我们到目前为止学到的概念可以在 FTP 或 SFTP 场景中重用。 |
| 10 | 现在重新登录到您的centos8s1服务器;然后将您的目录更改为/var/tftpdir并运行ls –lh命令。您应该可以找到所有带有我们代码中指定的日期和时间戳的设备备份。现在,您已经完成了捕获所有设备的运行配置的 SSH 交互式脚本。我们在这里只使用了五个设备,但是设备的数量将会成倍增加,甚至在生产中您都不必担心。太好了。 |
| | [pynetauto@centos8s1 tftpdir]$ ls -lh 192.* |
| | -rw-rw-r--. 1 tftpuser tftpuser 4.5K Jan 12 18:43 192.168.183.101.bak@2021-01-12_18-42-23 |
| | -rw-rw-r--. 1 tftpuser tftpuser 3.8K Jan 12 18:43 192.168.183.102.bak@2021-01-12_18-42-23 |
| | -rw-rw-r--. 1 tftpuser tftpuser 3.5K Jan 12 18:43 192.168.183.10.bak@2021-01-12_18-42-23 |
| | -rw-rw-r--. 1 tftpuser tftpuser 1.4K Jan 12 18:43 192.168.183.133.bak@2021-01-12_18-42-23 |
| | 听着… |
现在,您已经通过 SSH 连接和 TFTP 文件传输完成了正在运行的配置备份,从而完成了paramiko实验。您可以轻松地调整配置,使用 FTP 或 SFTP 文件传输进行进一步的测试和开发。在本章的后半部分,你将在不同的场景中使用通用的netmiko库。
Python SSH 实验室:netmiko
是一个优秀的 SSH 库,用于通过 SSH 连接管理网络设备。尽管如此,你必须努力工作来微调你的脚本部分,使其顺利运行。例如,通过 SSH 连接和运行不同命令的时间需要特别注意时间和提示。幸运的是,一位 CCIE 退休工程师已经努力工作,并考虑了我们在使用paramiko时可能遇到的许多问题。他开发了一个令人难以置信的叫做netmiko的 Python 库。netmiko是一个多供应商库,用于简化paramiko到网络设备的 SSH 连接;Kirk Byers 开发它是为了支持各种厂商的网络设备。在 Python 中,世界上许多隐藏的技术大师并不害怕分享他们的知识和智慧。在使用了大约两年的netmiko之后,我无法感谢 Kirk Byers 将我从paramiko的许多陷阱中拯救出来。他已经想到了paramiko的 SSH 连接问题。我们不得不享受一个工程师的艺术和汗水的成果,开拓 Python 网络自动化。这里是 Kirk 的官方netmiko链接和 GitHub 网站供进一步阅读。还有,nornir项目是netmiko的扩展;它具有 Ansible 的相同特性,但是是用 Python 编写的。nornir超出了本书的范围,但是当你有时间的时候,请上网查一下。
网址: https://pynet.twb-tech.com/ (Kirk Byers 的《网络工程师的 Python》)
网址:【with Netmiko 入门)
网址: https://github.com/ktbyers/netmiko/blob/master/netmiko/ssh_dispatcher.py (支持的设备列表)
现在,在编写第一个netmiko Python 脚本之前,确保已经使用sudo pip3 install netmiko安装了netmiko库,如下所示:
pynetauto@ubuntu20s1:~$ sudo pip3 install netmiko
[sudo] password for pynetauto:
Collecting netmiko
Downloading netmiko-3.2.0-py2.py3-none-any.whl (157 kB)
|████████████████████████████| 157 kB 4.8 MB/s
Requirement already satisfied: pyserial in /usr/lib/python3/dist-packages (from netmiko) (3.4)
[... omitted for brevity]
Successfully installed netmiko-3.2.0 scp-0.13.2 textfsm-1.1.0
在安装完netmiko之后,启动 Python 解释器并导入netmiko模块。您可以使用pip3 freeze命令来检查已安装的库,但是有时即使它被列出,您也无法在系统上运行多个 Python 版本的环境中使用该模块。如果解释器上没有返回错误,那么它已经成功安装,您可以开始编写下面的代码了:
pynetauto@ubuntu20s1:~/ssh_labs$ pip3 freeze
...
netmiko==3.3.2
...
pynetauto@ubuntu20s1:~$ python3
Python 3.8.2 (default, Jul 16 2020, 14:00:26)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import netmiko
>>>
验证后,按 Ctrl+Z 或键入exit()或quit()关闭 Python 3 解释器。
netmiko 实验 1: netmiko 使用字典来存储设备信息,而不是 JSON 对象
使用 netmiko,您必须以字典格式提供设备信息,其中至少包含基本的 SSH 登录信息,例如设备类型、IP(主机名)、管理员 ID 和密码。您还可以提供其他可选信息,如密码、端口号和登录状态。乍一看,这种字典格式类似于 JavaScript Object Notation (JSON)格式的数据集。netmiko设备信息是内置的 Python 字典,不是浏览器和服务器用来发送数据的 JSON 数据集。典型的netmiko字典通常是这样的:
cisco_3850 = {
'device_type': 'cisco_ios',
'host': '10.20.30.40',
'username': 'cisco',
'password': 'password123',
'port' : 8022, # optional, if not used defaults to 22
'secret': 'secret123', # optional, if not used defaults to ''
}
如果你想像前面的paramiko例子一样把前面的字典变平,你可以这样写字典。尽管如此,以前的结构化信息看起来更好看。
cisco_csr1k = {"device_type":"cisco_xe", "ip":"192.168.183.1", "username":"pynetauto", "password":"cisco123"}
我们还可以通过混合一些输入语句和getpass函数来请求用户输入,从而增强netmiko字典,如下所示:
from netmiko import ConnectHandler
from getpass import getpass
device1 = {
'device_type': 'cisco_ios',
'ip': input('IP Address : '),
'username': input('Enter username : '),
'password': getpass('SSH password : '),
}
在进入netmiko lab 1 之前,我们将确认netmiko字典不是 JSON 对象。让我们快速地在 Python 解释器中输入这段代码来确认这个事实。

打开 Python 解释器,在解释器窗口中输入以下内容。确保在创建字典时使用双引号,device1。Python 会自动把它们转换成单引号,你就知道为什么了。首先,检查一个netmiko字典的属性。
>>> device1 = {"device_type":"cisco_ios", "ip":"192.168.183.10", "username":"pynetauto", "password":"cisco123"}
>>> print(device1)
{'device_type': 'cisco_ios', 'ip': '192.168.183.10', 'username': 'pynetauto', 'password': 'cisco123'}
>>> print(type(device1))
<class 'dict'>
>>> dir(device1)
['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__',
(omitted for brevity)
'update', 'values']
使用dumps方法将device1字典转换成 JSON 格式。当我们打印转换后的 JSON 对象时,它们看起来像一个 Python 字典,但是关键的区别是所有的键和值集都用双引号括起来。
>>> import json
>>> device2 = json.dumps(device1)
>>> print(device2)
{"device_type": "cisco_ios", "ip": "192.168.183.10", "username": "pynetauto", "password": "cisco123"}
>>> print(type(device2))
<class 'str'>
乍一看,您可能会认为 JSON 对象与netmiko dictionary 对象相同,但它是一个字符串对象,正如dir()方法所揭示的那样。JSON 对象属于 Python 内置的str类,当然netmiko字典属于内置的dict类。
>>> dir(device2)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', (omitted for brevity)
, 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
# Quickly check if device1 is the same as device2
>>> if device1 == device2:
... print(True)
... else:
... print(False)
...
False
在netmiko实验 1 中,您将学习如何在 Cisco 路由器上使用 TCL shell 命令创建目录和文件,以便您可以创建虚拟文件来练习从路由器的flash:中删除文件。您将使用 IOSv 路由器LAB-R1,以保持简单,因为您将编写的实际脚本开始变得复杂,尤其是如果您来自非思科网络背景。尝试将注意力集中在概念上,而不是 IOSv 映像有多旧。正如我在第一章中解释的那样,最新和最棒的工具是好的,但是它们不需要学习网络或 Python 网络自动化的基本概念。见图 14-4 。

图 14-4。
SSH netmiko 实验 1,正在使用的设备
本实验的目的是让您练习使用 Python 脚本从 Cisco 设备的flash :中删除文件,而不是直接连接到 Cisco 设备。您将首先格式化LAB-R1的闪存,使用mkdir命令创建一个目录,并使用传统的 TCL shell 命令创建一些要删除的文件。我们应该能够编写 Python 代码,允许我们删除旧文件,还允许我们导航到一个文件夹来定位我们正在寻找的文件,以便 Python 应用可以根据需要删除这些文件。虽然您可能看不出与您的工作有任何关系,但这个特性在 IOS 升级应用开发中非常有用。
|
工作
|
| — | — |
| 1 | 打开LAB-R1的控制台窗口,格式化磁盘 0,这样就没有文件了。 |
| | LAB-R1#``format flash |
| | Format operation may take a while. Continue? [confirm] |
| | Format operation will destroy all data in "flash:". Continue? [confirm] |
| | Format: All system sectors written. OK... |
| | Format: Total sectors in formatted partition: 4193217 |
| | Format: Total bytes in formatted partition: 2146927104 |
| | Format: Operation completed successfully. |
| | Format of flash0: complete |
| | LAB-R1#show flash: |
| | -#- --length-- -----date/time------ path |
| | 2142711808 bytes available (8192 bytes used) |
| 2 | 使用mkdir flash:/directory_name在flash :下创建一个目录。 |
| | LAB-R1# mkdir flash:/old_files |
| | Create directory filename [old_files]? |
| | Created dir flash0:/old_files |
| | LAB-R1#dir |
| | Directory of flash0:/ |
| | 4 drw- 0 Jan 12 2021 08:09:26 +00:00 old_files |
| | 2142720000 bytes total (2142707712 bytes free) |
| 3 | 现在使用传统的 TCL shell 创建四个文件,如下所示。两个文件将在flash :下创建,另外两个文件将在flash:old_files/下创建。 |
| | LAB-R1# tclsh |
| | LAB-R1(tcl)# puts [open "flash:Delete_me_1.bin" w+] |
| | file0 |
| | LAB-R1(tcl)# puts [open "flash:Don't_delete_me_1.txt" w+] |
| | file1 |
| | LAB-R1(tcl)# puts [open "flash:old_files/Delete_me_2.bin" w+] |
| | file2 |
| | LAB-R1(tcl)# puts [open "flash:old_files/Don't_delete_me_2.txt" w+] |
| | file3 |
| | LAB-R1(tcl)# dir |
| | Directory of flash0:/ |
| | 4 drw- 0 Jan 12 2021 08:09:26 +00:00 old_files |
| | 5 -rw- 0 Jan 12 2021 08:11:16 +00:00 Delete_me_1.bin |
| | 6 -rw- 0 Jan 12 2021 08:11:24 +00:00 Don't_delete_me_1.txt |
| | 2142720000 bytes total (2142707712 bytes free) |
| | LAB-R1# dir flash:/old_files/ |
| | Directory of flash0:/old_files/ |
| | 7 -rw- 0 Jan 12 2021 08:11:32 +00:00 Delete_me_2.bin |
| | 8 -rw- 0 Jan 12 2021 08:11:40 +00:00 Don't_delete_me_2.txt |
| | 2142720000 bytes total (2142707712 bytes free) |
| 4 | 我们已经在LAB-R1的flash:上创建了一个虚拟目录和虚拟 IOS ( .bin)文件。让我们编写一个很酷的 SSH 脚本,使用netmiko库登录到LAB-R1,然后使用 Python 脚本删除一些.bin文件。是的,您可以登录到路由器的 CLI 并删除该文件,但是您可能会问,为什么要这么麻烦地用 Python 脚本创建它呢?第一,作为一个想为网络自动化写代码的读者,你必须做艰苦的工作来享受你的劳动成果。第二,这个剧本可以跟你顶嘴,可以团队合作。最后,想象一下,如果您要处理 100 台路由器或交换机,而不是一台设备。另外,想象一下你必须定期做这种训练,作为你工作的一部分。Python network automation 的真正威力在于它的循环(for循环和while循环),在下一个实验中,让我们将这个脚本修改为较新的版本,并在多个设备上使用它。 |
| | 查看引用嵌入描述的脚本。编写完代码后,转到第 5 步运行脚本。 |
| | GNU nano 4.8 /home/pynetauto/ssh_labs/netmiko_delete_me.py |
| | #!/usr/bin/env python3 |
| | import re |
| | from netmiko import ConnectHandler |
| | from getpass import getpass |
| | import time |
| | device1 = { #Netmiko dictionary for device1 |
| | 'device_type': 'cisco_ios', |
| | 'ip': input('IP Address : '), |
| | 'username': input('Enter username : '), |
| | 'password': getpass('SSH password : '), |
| | } |
| | net_connect = ConnectHandler(**device1) # Netmiko ConnectHandler object |
| | net_connect.send_command("terminal length 0\n") # Make terminal length to 0 to display all |
| | time.sleep(1) # Pause for 1 second |
| | dir_flash = net_connect.send_command("dir flash:\n") # Send "dir flash:" command |
| | print(dir_flash) # Display content of dir flash output |
| | p30 = re.compile(r'D[0-9a-zA-Z]{4}.*.bin') # Regular Expression to capture any file starting with "D", ends with "bin" |
| | m30 = p30.search(dir_flash) # Search for first match of p30 |
| | time.sleep(1) # Pause for 1 second |
| | # net_connect1.enable() (Optional, not required with privilege 15 access) |
| | print("!!! WARNING - You cannot reverse this step.") # Message to user |
| | # If dir_flash contains a string which satisfies p30 (True), |
| | # then run this script to delete a file. |
| | if bool(m30) == True: # If m30 is true |
| | print("If you can see 'Delete_me.bin' file, select it and press Enter.") # Informational |
| | del_cmd = "del flash:/" # Partial command 1 |
| | old_ios = input("*Old IOS (bin) file to delete : ") # Partial command 2, select a file under flash: |
| | while not p30.match(old_ios) or old_ios not in dir_flash: # User input request until correct file name is given |
| | old_ios = input("**Old IOS (bin) file to delete : ") |
| | command = del_cmd + old_ios # Complete command (1 + 2) |
| | output = net_connect.send_command_timing ( # Special netmiko send_command_timing command with timer |
| | command_string=command , |
| | strip_prompt=False , |
| | strip_command=False |
| | ) |
| | if "Delete filename" in output: # if the returned output contains "Delete filename", send "Enter" (change line) |
| | output += net_connect.send_command_timing( |
| | command_string="\n", |
| | strip_prompt=False, |
| | strip_command=False |
| | ) |
| | if "confirm" in output: # if the returned output contains "confirm", send "y" |
| | output += net_connect.send_command_timing( |
| | command_string="y", |
| | strip_prompt=False, |
| | strip_command=False |
| | ) |
| | net_connect.disconnect # Disconnect from SSH session |
| | print(output) # Informational |
| | # If None (False), then run this script to search directory for .bin file to delete |
| | elif bool(m30) == False:``# If no .bin file starting with D is found under "flash:", run this |
| | print("No IOS file under 'flash:/', select the directory to view.") # Informational |
| | open_dir = input("*Enter Directory name : ") # Partial command 1 |
| | while not open_dir in dir_flash: # Ask until correct response is received |
| | open_dir = input("** Enter Directory name : ") # If file does not exist, request user input again |
| | open_dir_cmd = (r"dir flash:/" + open_dir) # Completed command |
| | send_open_dir = net_connect.send_command(open_dir_cmd) # Send the command |
| | print(send_open_dir) # Informational |
| | p31 = re.compile(r'D[0-9a-zA-Z]{4}.*.bin') # Regular Expression to capture any file starting with "D", ends with "bin" |
| | m31 = p31.search(send_open_dir) # Send completed command |
| | if bool(m31) == True: # If there is a file with the string satisfy p31 expression |
| | print("If you see old IOS (bin) in the directory. Select it and press Enter.") # Informational |
| | del_cmd = "del flash:/" + open_dir + "/" # Completed command |
| | old_ios = input("*Old IOS (bin) file to delete : ") # Enter the .bin file to delete |
| | while not p30.match(old_ios) or old_ios not in send_open_dir: # User input request until correct file name is given |
| | old_ios = input("**Old IOS (bin) file to delete : ") |
| | command = del_cmd + old_ios # Complete command |
| | output = net_connect.send_command_timing( # Special netmiko send_command_timing command with timer |
| | command_string=command , |
| | strip_prompt=False, |
| | strip_command=False |
| | ) |
| | if "Delete filename" in output: # if the returned output contains "Delete filename", send "Enter" (change line) |
| | output += net_connect.send_command_timing ( |
| | command_string="\n", |
| | strip_prompt=False, |
| | strip_command=False |
| | ) |
| | if "confirm" in output: # if the returned output contains "confirm", send "y" |
| | output += net_connect.send_command_timing( |
| | command_string="y", |
| | strip_prompt=False, |
| | strip_command=False |
| | ) |
| | net_connect.disconnect # Disconnect from SSH session |
| | print(output) # Print content of output |
| | else : # Both conditions failed to satisfy, exit the script |
| | ("No IOS found.") |
| | exit() |
| | net_connect.disconnect # Disconnect from SSH session |
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos |
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line |
| 5 | 现在,导航回您的ubuntu20s1 (192.168.183.132) Python 服务器并 ping LAB-R1 (192.168.183.10)以确认设备的连接性。您仍然在使用 SSH 协议,netmiko实验室仍然是 SSH 实验室的一部分,所以您应该在/home/pynetauto/ssh_labs目录中工作。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ ping 192.168.183.10 -c 3 |
| | PING 192.168.183.10 (192.168.183.10) 56(84) bytes of data. |
| | 64 bytes from 192.168.183.10: icmp_seq=1 ttl=255 time=7.37 ms |
| | 64 bytes from 192.168.183.10: icmp_seq=2 ttl=255 time=6.73 ms |
| | 64 bytes from 192.168.183.10: icmp_seq=3 ttl=255 time=12.9 ms |
| | --- 192.168.183.10 ping statistics --- |
| | 3 packets transmitted, 3 received, 0% packet loss, time 2004ms |
| | rtt min/avg/max/mdev = 6.734/9.016/12.948/2.791 ms |
| | pynetauto@ubuntu20s1:~/ssh_labs$ ls netmiko* |
| | netmiko_delete_me.py |
| 6 | 现在使用python netmiko_delete_me.py运行脚本。当脚本返回输出时,选择.bin文件,并按 Enter 键将其从LAB-R1的闪存中删除。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python netmiko_delete_me.py |
| | IP Address : 192.168.183.10 |
| | Enter username : pynetauto |
| | SSH password : |
| | Directory of flash0:/ |
| | 4 drw- 0 Jan 12 2021 08:09:26 +00:00 old_files |
| | 5 -rw- 0 Jan 12 2021 08:11:16 +00:00 Delete_me_1.bin |
| | 6 -rw- 0 Jan 12 2021 08:11:24 +00:00 Don't_delete_me_1.txt |
| | 2142720000 bytes total (2142707712 bytes free) |
| | !!! WARNING - You cannot reverse this step. |
| | If you can see 'Delete_me.bin' file, select it and press Enter. |
| | *Old IOS (bin) file to delete : Delete_me_1.bin |
| | del flash:/Delete_me_1.bin |
| | Delete filename [Delete_me_1.bin]? |
| | Delete flash0:/Delete_me_1.bin? [confirm]y |
| | LAB-R1# |
| 7 | 现在打开LAB-R1的控制台,检查文件Delete_me_1.bin是否已经从flash的:中删除。如果您在这里只看到一个文件和一个文件夹,那么您已经完成了第一项任务。转到步骤 9,重新运行相同的脚本。 |
| | LAB-R1# dir |
| | Directory of flash0:/ |
| | 4 drw- 0 Jan 12 2021 08:09:26 +00:00 old_files |
| | 6 -rw- 0 Jan 12 2021 08:11:24 +00:00 Don't_delete_me_1.txt |
| | 2142720000 bytes total (2142707712 bytes free) |
| 8 | 第二次运行相同的脚本;这一次,由于flash :下没有.bin文件,所以会提示不同的信息,要求您选择一个目录来搜索.bin文件。当你剪切并粘贴目录名时,它会运行脚本来寻找难以捉摸的.bin文件并显示结果。当脚本找到.bin文件时,继续删除Delete_me_2.bin文件。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python netmiko_delete_me.py |
| | IP Address : 192.168.183.10 |
| | Enter username : pynetauto |
| | SSH password : ******** |
| | Directory of flash0:/ |
| | 4 drw- 0 Jan 12 2021 08:09:26 +00:00 old_files |
| | 6 -rw- 0 Jan 12 2021 08:11:24 +00:00 Don't_delete_me_1.txt |
| | 2142720000 bytes total (2142707712 bytes free) |
| | !!! WARNING - You cannot reverse this step. |
| | No IOS file under 'flash:/', select the directory to view. |
| | *Enter Directory name :``old_files |
| | Directory of flash0:/old_files/ |
| | 7 -rw- 0 Jan 12 2021 08:11:32 +00:00 Delete_me_2.bin |
| | 8 -rw- 0 Jan 12 2021 08:11:40 +00:00 Don't_delete_me_2.txt |
| | 2142720000 bytes total (2142707712 bytes free) |
| | If you see old IOS (bin) in the directory. Select it and press Enter. |
| | *Old IOS (bin) file to delete :``Delete_me_2.bin |
| | del flash:/old_files/Delete_me_2.bin |
| | Delete filename [/old_files/Delete_me_2.bin]? |
| | Delete flash0:/old_files/Delete_me_2.bin? [confirm]y |
| | LAB-R1# |
| 9 | 返回到LAB-R1并运行dir flash:old_files/命令来查看目录内容。如果您在目录下只看到一个文件,则您已经完成了本实验的第二项任务。 |
| | LAB-R1# dir flash:/old_files/ |
| | Directory of flash0:/old_files/ |
| | 8 -rw- 0 Jan 12 2021 08:11:40 +00:00 Don't_delete_me_2.txt |
| | 2142720000 bytes total (2142707712 bytes free) |
| 10 | 现在删除工作目录和文件,因为我们已经完成了本实验。 |
| | LAB-R1# delete /recursive /force flash:old_files |
| | LAB-R1#delete flash: Don't_delete_me_1.txt |
| | Delete filename [Don't_delete_me_1.txt]? |
| | Delete flash0:/Don't_delete_me_1.txt? [confirm] |
| | LAB-R1# dir |
| | Directory of flash0:/ |
| | No files in directory |
| | 2142720000 bytes total (2142711808 bytes free) |
您现在已经学习了如何编写 Python 代码来删除和搜索路由器的闪存文件。你可以把你的任务变成 Python 脚本,这是 Python 网络自动化的开始。接下来,我们将开发一个端口扫描工具来检查开放的端口,并将其应用到我们的工作中。
netmiko 实验 2:使用套接字模块开发一个简单的端口扫描器,然后开发一个 nemiko Disable Telnet 脚本
在本实验中,您将学习如何使用套接字模块开发一个简单的端口扫描工具,然后在您的netmiko实验 2 脚本(netmiko_disable_telnet.py)中使用该扫描工具来检查打开的端口 23,并使用netmiko库禁用这些端口。早些时候,我们在line vty 0 15下配置了transport input all,允许 Telnet 和 SSH 连接用于所有设备管理。要禁用 Telnet,您必须用transport input ssh命令重新配置虚拟电传(vty)线路。本实验需要打开所有路由器和交换机的电源。
在本实验结束时,您将能够扫描网络设备以查找任何开放的端口;您将检查端口 23(或 22 或任何其他感兴趣的端口)是否在使用中。通过禁用所有设备上的 Telnet 端口 23 来保护您的设备,并且只允许 SSH 连接用于设备管理。再次强调,尝试将注意力集中在概念上,并开发实现本实验目标所需的工具。见图 14-5 。

图 14-5。
SSH netmiko 实验 2,使用中的设备
让我们首先开发一个迷你扫描工具,并验证我们的路由器和交换机上的开放端口。然后将扫描工具合并到我们的脚本中,netmiko_disable_telnet.py。
|
工作
|
| — | — |
| 1 | 首先,您必须使用socket.socket(family, type)创建一个 socket 对象,将 family 设置为socket.AF_INET,type 设置为socket.SOCK_STREAM。根据 Python 套接字编程,socket.AF_INET为 IPv4 指定 IP 地址族,socket.SOCK_STREAM为 TCP 指定套接字类型。要检查端口状态,使用socket.connect_ex(dest),将套接字作为套接字对象,将dest作为包含 IP 地址和所需端口号的元组。如果端口是打开的,socket.connect_ex()返回 0,但是如果端口是关闭的,会根据被扫描的端口返回不同的数字。在端口扫描结束时,您必须使用socket.close()关闭套接字。 |
| | 我们将要使用的简单端口扫描仪模板如下所示,它允许我们只扫描单个端口的单个设备。如果您将它放到您的 Linux 服务器上,创建一个名为scan_open_port.py的新文件,并运行代码,您将能够检查单个目的地的端口状态。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ nano scan_open_port.py |
| | GNU nano 4.8 /home/pynetauto/ssh_labs/scan_open_port.py |
| | import socket # Import socket module |
| | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Create a socket object |
| | dest = ("192.168.183.10", 22) # IP and Port to scan |
| | port_open = sock.connect_ex(dest) # Create socket connect object |
| | # open returns int 0, closed returns an integer based on port number |
| | if port_open == 0: # If port is opened, it returns 0 |
| | print(port_open) # print result = 0 |
| | print("On ", {dest[0]}, "port is open.") # Informational |
| | else: |
| | print(port_open) # print result, an integer other than 0 |
| | print("On ", {dest[0]}, "port is closed.") # Informational |
| | sock.close() # close socket object |
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos |
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line |
| | 当您在实验室的LAB-R1 (192.168.183.10)路由器上运行该脚本时,结果将如下所示: |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python3 scan_open_port.py |
| | 0 |
| | On {'192.168.183.10'} port is open. |
| | 当您将端口 22 (SSH)更改为 80 (HTTP)并再次扫描端口时,它会返回 111 和“端口已关闭”消息。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python3 scan_open_port.py |
| | 111 |
| | On {'192.168.183.10'} port is closed. |
| 2 | 前面的脚本工作得很好,但不能很好地适应我们的用例,我们必须修改脚本,使它在我们的环境中更具可伸缩性和通用性。使用前面的脚本作为我们的模板,让我们重写脚本来一次扫描多个设备的端口。我的脚本看起来如下,但是没有正确或错误的编码方式,所以你也可以通过修改来增加你的天赋。由于这个脚本扫描多个端口,我将其命名为scan_open_ports.py(末尾有复数“s”)。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ nano scan_open_ports.py |
| | GNU nano 4.8 /home/pynetauto/ssh_labs/scan_open_ports.py |
| | import socket # Import socket module |
| | ip_addresses = ["192.168.183.10", "192.168.183.20", "192.168.183.101", "192.168.183.102", "192.168.183.133"] # IP address list |
| | for ip in ip_addresses: # get ip from the list, ip_addresses |
| | for port in range (22, 24): # port range, ports 22-23, always n-1 for the last digit |
| | dest = (ip, port) # combine both ip and port number into one object as socket method takes 1 attribute |
| | try : # Use try except method |
| | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: # s as a socket object |
| | sock.settimeout(3) # |
| | connection = sock.connect(dest) # connect to destination on specified port |
| | print(f"On {ip}, port {port} is open!") # Informational |
| | except : |
| | print(f"On {ip}, port {port} is closed.") # Informational |
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos |
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line |
| | 如图所示完成脚本后,运行脚本,如果您在网络设备上同时打开了 Telnet (22)和 SSH (23 ),结果应该类似于以下内容。您也可以为其他端口扫描任务指定不同的端口范围。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python3 scan_open_ports.py |
| | On 192.168.183.10, port 22 is open! |
| | On 192.168.183.10, port 23 is open! |
| | On 192.168.183.20, port 22 is open! |
| | On 192.168.183.20, port 23 is open! |
| | On 192.168.183.101, port 22 is open! |
| | On 192.168.183.101, port 23 is open! |
| | On 192.168.183.102, port 22 is open! |
| | On 192.168.183.102, port 23 is open! |
| | On 192.168.183.133, port 22 is open! |
| | On 192.168.183.133, port 23 is open! |
| 3 | 在两个步骤中,您已经开发了一个 Python 端口扫描器,可以用于您的工作。 |
Warning!
注意在哪里以及如何使用这种工具;您不希望在工作场所或客户的网站上违反公司的安全政策。如果您的工作不建议在没有适当的更改控制和批准的情况下执行端口扫描任务,请不要运行端口扫描程序。
||
工作
|
| — | — |
| | 好了,现在你有了另一个工具;基于前面的脚本,让我们继续编写一些代码来检查端口,如果我们的设备上启用了 Telnet,让 Python 应用 SSH 进入并修改设置;然后保存更改后的配置。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ nano netmiko_disable_telnet.py |
| | GNU nano 4.8 / home/pynetauto/ssh_labs/netmiko_disable_telnet.py |
| | #!/usr/bin/env python3 |
| | import re |
| | from netmiko import ConnectHandler |
| | from getpass import getpass |
| | import time |
| | import socket |
| | def get_credentials(): # Enhanced User ID and password collection tool |
| | #Prompts for, and returns a username and password |
| | global username # Make username as a global variable to be used throughout this script |
| | global password # Make password as a global variable to be used throughout this script |
| | username = input("Enter your username : ") |
| | password = None |
| | while not password: |
| | password = getpass() |
| | password_verify = getpass("Retype your password : ") # Verify the password is correctly typed |
| | if password != password_verify: |
| | print("Passwords do not match. Please try again.") |
| | password = None |
| | return username, password |
| | get_credentials() # Run this function first to collect the username and password |
| | device1 = { # Netmiko dictionary for device1 |
| | 'device_type': 'cisco_ios', |
| | 'ip': '192.168.183.10', |
| | 'username': username , |
| | 'password': password, |
| | } |
| | device2 = { # Netmiko dictionary for device2 |
| | 'device_type': 'cisco_ios', |
| | 'ip': '192.168.183.20', |
| | 'username': username, |
| | 'password': password, |
| | } |
| | device3 = { # Netmiko dictionary for device3 |
| | 'device_type': 'cisco_ios', |
| | 'ip': '192.168.183.101', |
| | 'username': username, |
| | 'password': password, |
| | } |
| | device4 = { # Netmiko dictionary for device4 |
| | 'device_type': 'cisco_ios', |
| | 'ip': '192.168.183.102', |
| | 'username': username, |
| | 'password': password, |
| | } |
| | device5 = { # Netmiko dictionary for device5 |
| | 'device_type': 'cisco_ios', |
| | 'ip': '192.168.183.133', |
| | 'username': username, |
| | 'password': password, |
| | } |
| | devices = [device1, device2, device3, device4, device5] # List of netmiko devices |
| | for device in devices: # Loop through devices |
| | ip = device.get("ip", "") # get value of the key "ip" |
| | for port in range (23, 24): # only port 23 |
| | dest = (ip, port) # Combine ip and port number to form dest object |
| | try: |
| | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: # port scanner tool |
| | sock.settimeout(3) # add 3 seconds pause to socket application |
| | connection = sock.connect(dest) # |
| | print(f"On {ip}, port {port} is open!") # Informational |
| | net_connect = ConnectHandler(**device) # create a netmiko ConnectHandler object |
| | show_clock = net_connect.send_command("show clock\n") # Send "show clock" command |
| | print(show_clock) # Display time |
| | config_commands = ['line vty 0 15', 'transport input ssh'] # netmiko config_commands |
| | net_connect.send_config_set(config_commands) # send netmiko send_config_set |
| | output = net_connect.send_command("show run | b line vty") # send a show command to``check vty 0 15 |
| | print() # Informational |
| | print('-' * 80) # Displayed information separator line |
| | print(output) # Informational |
| | print('-' * 80) # Displayed information separator line |
| | print() # Informational |
| | net_connect.disconnect() # close netmiko connection |
| | except: |
| | print(f"On {ip}, port {port} is closed.") |
| | # #``This is for saving the configuration after a successful configuration change |
| | # net_connect = ConnectHandler(**device) # Commented out for third run |
| | # write_mem = net_connect.send_command("write mem\n") # Commented out for third run |
| | # print() |
| | # print('-' * 80) |
| | # print(write_mem) # Commented out for third run |
| | # print('-' * 80) |
| | # print() |
| | # net_connect.disconnect() |
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos |
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line |
| 4 | 运行netmiko_disable_telnet.py脚本并更新配置以禁用 Telnet 登录。 |
Warning!
如果您在生产中使用类似的脚本,请确保您的主要远程控制台管理协议是 SSH。您遵循了公司的策略,使用自动脚本删除 Telnet 服务。
||
工作
|
| — | — |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python netmiko_disable_telnet.py |
| | Enter your username : pynetauto |
| | Password: ******** |
| | Retype your password : ******** |
| | On 192.168.183.10, port 23 is open! |
| | *09:48:17.800 UTC Tue Jan 12 2021 |
| | -------------------------------------------------------------------------------- |
| | line vty 0 4 |
| | logging synchronous |
| | login local |
| | transport input ssh |
| | line vty 5 15 |
| | logging synchronous |
| | login local |
| | transport input ssh |
| | ! |
| | no scheduler allocate |
| | ntp server 192.168.183.130 |
| | ! |
| | end |
| | -------------------------------------------------------------------------------- |
| | [... omitted for brevity] |
| | -------------------------------------------------------------------------------- |
| | On 192.168.183.133, port 23 is open! |
| | *01:40:37.823 UTC Fri Mar 1 2002 |
| | -------------------------------------------------------------------------------- |
| | line vty 0 4 |
| | exec-timeout 0 0 |
| | logging synchronous |
| | login local |
| | transport input ssh |
| | line vty 5 15 |
| | exec-timeout 0 0 |
| | logging synchronous |
| | login local |
| | transport input ssh |
| | ! |
| | ! |
| | end |
| | -------------------------------------------------------------------------------- |
| 5 | 第二次运行脚本进行验证。现在不允许对设备进行远程登录,我们应该会收到如下关闭消息: |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python3 netmiko_disable_telnet.py |
| | Enter your username : pynetauto |
| | Password:******** |
| | Retype your password : ******** |
| | On 192.168.183.10, port 23 is closed. |
| | On 192.168.183.20, port 23 is closed. |
| | On 192.168.183.101, port 23 is closed. |
| | On 192.168.183.102, port 23 is closed. |
| | On 192.168.183.133, port 23 is closed. |
| 6 | 一旦前面的任务成功完成,从相同的代码中删除except:行下的#(取消注释)。然后重新运行 Python 代码来保存所有五个设备的配置。这里,我复制了原始脚本,并取消了脚本末尾的注释行: |
| | pynetauto@ubuntu20s1:~/ssh_labs$ cp netmiko_disable_telnet.py netmiko_disable_telnet_uncommented.py |
| | pynetauto@ubuntu20s1:~/ssh_labs$ nano netmiko_disable_telnet_uncommented.py |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python netmiko_disable_telnet_uncommented.py |
| | 取消注释后,最后几行代码应该如下所示: |
| | except: |
| | print(f"On {ip}, port {port} is closed.") |
| | # This is for saving the configuration after a successful configuration change. |
| | net_connect = ConnectHandler(**device) |
| | write_mem = net_connect.send_command(“write mem\n”) |
| | print() |
| | print('-' * 80) |
| | print(write_mem) |
| | print('-' * 80) |
| | print() |
| | net_connect.disconnect() |
| 7 | 再次运行脚本以保存当前运行的配置。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python netmiko_disable_telnet_uncommented.py |
| | Enter your username : pynetauto |
| | Password: |
| | Retype your password : |
| | On 192.168.183.10, port 23 is closed. |
| | -------------------------------------------------------------------------------- |
| | Building configuration... |
| | [OK] |
| | -------------------------------------------------------------------------------- |
| | [... omitted for brevity] |
| | -------------------------------------------------------------------------------- |
| | On 192.168.183.133, port 23 is closed. |
| | -------------------------------------------------------------------------------- |
| | Building configuration... |
| | [OK] |
| | -------------------------------------------------------------------------------- |
您刚刚开发了一个迷你端口扫描工具,然后使用它作为开发 SSH ( netmiko)工具的模板来关闭所有实验室设备上的 Telnet 端口。现在,您的实验室拓扑更加安全,您只能使用 SSH 连接访问路由器和交换机进行设备管理。
netmiko 实验 3:配置比较
在这个netmiko实验中,您将使用difflib库开发一个快速设备配置比较工具。该脚本将借用上一个实验的部分脚本,并通过检查 SSH 连接端口 22 的开放端口状态,将端口扫描器用作预检查通信验证工具。本实验将并排比较两台相似设备的运行配置。如果你做过一段时间的网络工程师,你应该熟悉 Notepad++中的比较模块和类似的工具。您必须手动登录到每个设备,运行show命令,将配置作为两个独立的日志或两个文本文件删除,然后在 Notepad++上打开它们,并运行比较工具。一旦进行了比较,您必须手动找出差异,将缺失的配置应用到第二个设备。这里我们正在开发一个工具,用于通过 SSH 连接访问遗留设备;然而,对于支持 REST API 的网络设备,同样的比较可以用于通过 API 调用收集运行配置。相同的脚本可以应用于收集的数据集。另外,假设您想进一步使用这个脚本。在这种情况下,我们可以利用强大的数据模块,如pandas和xlsxwriter来读取作为数据帧的行,并只提取两种配置之间的差异,这样许多手动任务就可以自动化。
对于本实验,您需要访问Lab-R1 (192.168.183.10)和lab-r2 (192.168.183.20)。此外,交换机和路由器的传输设备都需要通电。您将使用 WinSCP 来访问在ubuntu20s1 Python 服务器上收集的数据。所以如果你能提前下载并安装 WinSCP 或 FileZilla 进行文件检索,你就做好了充分的准备。见图 14-6 。

图 14-6。
SSH netmiko 实验 3,使用中的设备
成功完成实验后,您将在脚本的运行目录中创建三个文件:两个包含运行配置的文本文件和一个包含比较结果的 XML 文件。按照以下步骤完成本实验:
||
工作
|
| — | — |
| 1 | 首先,复制上一个实验中的端口扫描工具,并进行必要的修改,以便 SSH 端口 22 的打开状态可以被该工具探测到。进行更改后,该脚本应类似于以下内容: |
| | for device in devices: # Loop through netmiko devices list |
| | ip = device.get("ip", "") # Get value of the key "ip" from device dictionary |
| | for port in range (22, 23): # For only port 22 |
| | dest = (ip, port) # Combine ip and port number to form dest object |
| | try: |
| | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: |
| | sock.settimeout(3) |
| | connection = sock.connect(dest) # Send socket request |
| | print (f"on {ip}, port {port} is open!")``# Informational, pass object using format |
| | except: |
| | print (f"On {ip}, port {port} is closed. Check the connectivity to {ip} again.") |
| | exit() |
| 2 | 现在这里是netmiko_compare_config.py脚本的完整源代码。这个应用使用getpass模块获取密码,使用时间模块添加暂停,使用套接字模块探测开放端口,difflib比较配置文件,当然,netmiko的ConnectHandler通过 SSH 协议连接到两个设备。尝试在键盘上键入代码,感受一下编写 Python 代码的感觉。不可避免的是,当你在编码的时候,你不得不在你的屏幕和键盘前呆上几个小时。有关进一步的解释,请参考代码中嵌入的解释。 |
| | GNU nano 4.8 /home/pynetauto/ssh_labs/netmiko_compare_config.py |
| | #!/usr/bin/env python3 |
| | #---------------------------------------------------# Import required modules |
| | import time |
| | import socket |
| | import difflib |
| | from getpass import getpass |
| | from netmiko import ConnectHandler |
| | #---------------------------------------------------# Borrowed from previous labs |
| | # Functions to collect credentials and IP addresses of devices |
| | def get_input(prompt=''): |
| | try: |
| | line = input(prompt) |
| | except NameError: |
| | line = input(prompt) |
| | return line |
| | def get_credentials(): |
| | #Prompts for, and returns a username and password |
| | username= get_input("Enter Network Admin ID : ") |
| | password = None |
| | while not password: |
| | password = getpass("Enter Network Admin PWD : ") |
| | password_verify = getpass("Confirm Network Admin PWD : ") |
| | if password != password_verify: |
| | print("Passwords do not match. Please try again.") |
| | password = None |
| | return username, password |
| | # For IP addresses of comparing devices |
| | def get_device_ip(): |
| | #Prompts for, and returns a first_ip and second_ip |
| | first_ip = get_input("Enter primary device IP : ") |
| | while not first_ip: |
| | first_ip = get_input("* Enter primary device IP : ") |
| | second_ip = get_input("Enter secondary device IP : ") |
| | while not second_ip: |
| | second_ip = get_input("* Enter secondary device IP : ") |
| | return first_ip, second_ip |
| | #-------------------------------------------------------------------------------- |
| | # Run the functions to collect credentials and ip addresses |
| | print("-"*40) |
| | username, password = get_credentials() |
| | first_ip, second_ip = get_device_ip() |
| | print("-"*40) |
| | #-------------------------------------------------------------------------------- |
| | # Netmiko device dictionaries |
| | device1 = { # Netmiko dictionary for device1 |
| | 'device_type': 'cisco_ios', |
| | 'ip': first_ip, |
| | 'username': username, |
| | 'password': password, |
| | } |
| | device2 = { # Netmiko dictionary for device2 |
| | 'device_type': 'cisco_ios', |
| | 'ip': second_ip , |
| | 'username': username, |
| | 'password': password, |
| | } |
| | devices = [device1, device2] |
| | #-------------------------------------------------------------------------------- |
| | # Re-use port scanner as a pre-check tool for reachability verification tools. |
| | # If an IP is not reachable, the application will exit due to a communication problem. |
| | for device in devices: # Loop through devices |
| | ip = device.get("ip", "") # get value of the key "ip" |
| | for port in range (22, 23): # only port 22 |
| | dest = (ip, port) # Combine ip and port number to form dest object |
| | try: |
| | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: |
| | sock.settimeout(3) |
| | connection = sock.connect(dest) |
| | print(f"on {ip}, port {port} is open!") |
| | except: |
| | print(f"On {ip}, port {port} is closed. Check the connectivity to {ip} again.") |
| | exit() |
| | # Prompt the user to make a decision to run the tool |
| | response = input(f"Make a comparison of {first_ip} and {second_ip} now? [Yes/No]") |
| | response = response.lower() |
| | if response == 'yes': |
| | print(f"* Now making a comparison : {first_ip} vs {second_ip}") # Informational |
| | for device in devices: # Loop through devices |
| | ip = device.get("ip", "") # Get value of the key "ip" |
| | try: |
| | net_connect = ConnectHandler(**device) # Create netmiko connection object |
| | net_connect.send_command("terminal length 0\n") |
| | output = net_connect.send_command("show running-config\n") # Run show running config |
| | show_run_file = open(f"{ip}_show_run.txt", "w+") # Create a file |
| | show_run_file.write(output) # Write output to file |
| | show_run_file.close() # Close-out the file |
| | time.sleep(1) |
| | net_connect.disconnect() # Disconnect SSH connection |
| | except KeyboardInterrupt: # Keyboard Interrupt |
| | print("-"*80) |
| | else: |
| | print("You have selected No. Exiting the application.") # Informational |
| | exit() |
| | #-------------------------------------------------------------------------------- |
| | # Compare the two show running config files and display it in html file format. # Informational |
| | # Prepare for comparison of the text files |
| | device1_run = f"./{first_ip}_show_run.txt" # Create device1_run object, ./ is present working folder |
| | device2_run = f"./{second_ip}_show_run.txt" # Create device2_run object |
| | device1_run_lines = open(device1_run).readlines() # Convert into strings first for comparison |
| | time.sleep(1) |
| | device2_run_lines = open(device2_run).readlines() # Convert into strings first for comparison |
| | time.sleep(1) |
| | # Four arguments required in HtmlDiff function |
| | difference = difflib.HtmlDiff(wrapcolumn=60).make_file(device1_run_lines, device2_run_lines, device1_run, device2_run) |
| | difference_report = open(first_ip + "_vs_" + second_ip + "_compared.html", "w") # Create html file to write the difference |
| | difference_report.write(difference) # Writes the differences to the difference_report |
| | difference_report.close() |
| | print("** Device configuration comparison completed. Please Check the html file to check the differences.") |
| | print("-"*80) |
| | time.sleep(1) |
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos |
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line |
| 3 | 写完前面的脚本后,使用 Python 命令在ssh_labs目录下运行它。运行这个交互式应用后,您应该在 Linux Python 服务器的当前工作目录中自动创建了三个文件(pwd)。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ python netmiko_compare_config.py |
| | ---------------------------------------- |
| | Enter Network Admin ID : pynetauto |
| | Enter Network Admin PWD :******** |
| | Confirm Network Admin PWD : ******** |
| | Enter primary device IP : 192.168.183.10 |
| | Enter secondary device IP : 192.168.183.20 |
| | ---------------------------------------- |
| | on 192.168.183.10, port 22 is open! |
| | on 192.168.183.20, port 22 is open! |
| | Make a comparison of 192.168.183.10 and 192.168.183.20 now? [Yes/No]yes |
| | * Now making a comparison : 192.168.183.10 vs 192.168.183.20 |
| | ** Device configuration comparison completed. Please Check the html file to check the differences. |
| | - |
| | 如果您遇到错误或问题,请从我的 GitHub 库下载源代码,并仔细重新检查您的代码和设置。 |
| | 源代码下载网址: https://github.com/pynetauto/apress_pynetauto |
| | 在 Python 网络自动化服务器上运行ls命令,您应该会在当前工作目录下找到这三个文件。 |
| | pynetauto@ubuntu20s1:~/ssh_labs$ ls -lh 192.168* |
| | -rw-rw-r-- 1 pynetauto pynetauto 3.6K Jan 12 21:45 192.168.183.10_show_run.txt |
| | -rw-rw-r-- 1 pynetauto pynetauto 56K Jan 12 21:46 192.168.183.10_vs_192.168.183.20_compared.html |
| | -rw-rw-r-- 1 pynetauto pynetauto 3.7K Jan 12 21:46 192.168.183.20_show_run.txt |
| 4 | 现在,使用 WinSCP 或 FileZilla,使用您的凭证登录到 Ubuntu Python automation 服务器,并将文件移动到您的 Windows 主机。如果您尚未安装该软件,请在登录前在此安装。我在端口 22 上使用 SCP 协议。 |
| | URL: https://winscp.net/eng/download.php |
| | Figure 14-7 shows a WinSCP example with an SCP connection (port 22) to the server. Locate the three files under /home/pynetauto/ssh_labs/ and drag and drop them to your Windows host PC folder.
图 14-7。Win-SCP,将比较备份和运行配置备份复制到 Windows 主机 |
| 12 | Now, go to the downloaded folder and open the .html file to review the script’s result. Now you can compare any similar devices using this interactive tool. This tool can be modified slightly to compare the difference between firewall rules and configuration, and one practical example is to use such a tool to compare Palo Alto firewalls. Using its simple XML API call feature, you can compare the primary to standby configurations. The same script can also be used to compare the configuration before and after a change has been performed on a device. How you want to use the tool is totally up to your imagination. See Figure 14-8.
图 14-8。Web 浏览器,打开 HTML 文件进行审阅 |
大多数新的网络设备都支持 REST/XML API。然而,许多公司投资了数百万美元购买只支持 Telnet、SSH 和 SNMP 的企业网络设备,这些设备多年来仍处于有效的技术支持之下。网络工程师通常在通过 SSH 或 Telnet 连接的黑色命令行控制台前开始他们的生活。现在将您的知识扩展到网络编程。你首先要研究不同网络工程师的行为,理解他们坐在黑暗的 CLI 屏幕前盯着屏幕的决策过程。网络自动化不是让机器人或机器人或软件努力工作。它是关于理解我们作为网络工程师的行为,并看到自动化的重要性以及作为技术人员可以帮助其他人的地方。几乎所有的企业网络设备都支持 SSH,因此能够使用 Python、Python 模块和 SSH 的强大功能来自动化工程师的日常任务在这个过渡时期仍然是一个强有力的工具。在这个新的软件定义网络(SDN)和“一切皆云”的时代,更重要的是了解为我们的托管网络增加更多弹性和稳定性的真正基础,以及 Python 等软件编程如何将我们带到下一个级别。
摘要
本章重点介绍了使用两个 SSH 库paramiko和netmiko的路由器和交换机的 Python 网络自动化。您已经了解了如何开发更小的工具,然后将它们合并到一个更重要的工具中,以创建能够增强您的工作和团队,并最终增强您的公司的工具。您学习了如何通过 SSH 进行配置更改,对 TFTP 服务器进行running-config备份,开发一个简单的套接字端口扫描工具,将其应用到您的工作中,并比较两个相似的设备来定位差异。接下来,您将学习如何安排 Python 应用在指定的时间运行,并尝试 Python SNMPv3。将向您介绍cron和 SNMP 探索实验室,让您更多地了解如何在工作中使用 Python 实现网络自动化。
十五、Python 网络自动化实验室:cron和 SNMPv3
最终,您希望学习 Python,这样您就可以编写代码来自动化工作中的日常任务。在编写代码和开发脚本化应用之后,您可能希望在没有人在场的情况下运行脚本。在 Windows 系统上有 Windows 任务调度器,在 Linux 上,你需要cron来帮助你安排你的脚本在凌晨 2 点运行,或者定期运行,直到你告诉你的 Linux 系统停止。一旦熟悉 Python 和 Linux,您就可以编写脚本并安排脚本自动运行。您可能还想进一步了解 Python 如何与 SNMP 一起工作,以便您的公司可以考虑使用内部脚本进行 SNMP 监控;您可能想了解 Python 通常如何与 SNMP 交互。本章结束时,您将能够克隆 GNS3 项目,使用cron在 Linux 上调度任务,并使用 SNMPv3 与路由器和交换机交互。在这个过程中,我们将借用 Python 社区成员公开共享的代码,用 Python 探索 SNMPv3,了解它能为我们做什么。本章旨在让您更多地接触不同的场景,同时将 Python 实际应用于网络自动化场景。

Cron 和 SNMPv3 实验室
你已经学完了这本书的前 14 章。您可以为特定的网络任务编写简单而实用的 Python 脚本。您将很快意识到,您需要找到一种方法,在没有实际用户交互或用户干预的情况下运行您的脚本化应用,可能需要使用任务调度程序。您可能意识到,您可能能够利用 Python 自动化的力量,并将其应用于更传统的网络概念,如 SNMP 监控;可能性是无限的。首先,在为下一章做准备时,您将学习如何制作 GNS3 项目的真实克隆。
为下一个实验克隆 GNS3 项目
为了准备我们的实验,你将从第十四章中复制一个 GNS3 项目。GNS3 项目导出克隆方法帮助我们制作了一个新项目,但它破坏了原始实验室的配置文件,因此从这个意义上说,它不是 GNS3 项目的真正克隆。有时,这可能不是您想要的实验室结果。您可能希望保持原始文件的完整性,并创建一个旧 GNS3 项目的真实克隆。使用克隆的项目导出方法,原始实验室变得不可用,因为 Dynamips 文件也会随着新项目的创建而导出。创建新项目而不影响原始项目的更好方法是制作原始项目文件夹的真实副本,然后用另一个名称重新导入它。您希望将cmllab-basic项目作为一个完整的工作项目,但同时,您不希望从头开始创建一个新的 GNS3 项目。让我们了解一下如何实现这一目标。
|
工作
|
| — | — |
| 1 | 如果您仍在使用cmllab-basic项目,首先使用copy running-config startup-config命令保存所有 Cisco 设备的运行配置。保存三台路由器和两台交换机的配置后,正常关闭所有设备的电源。 |
| 2 | Once all routers and switches are powered off, exit GNS3 so both GNS3 and GNS3 VM are closed completely. See Figure 15-1.
图 15-1。CML lab-基本 GNS3 项目,退出 GNS3 和 GNS3 虚拟机 |
| 3 | Go to the C:\Users\brendan\GNS3\projects folder and make a copy of the cmllab-basic folder so it looks like Figure 15-2.
图 15-2。GNS3 项目,制作现有项目文件夹的副本 |
| 4 | Go to the desktop of your Windows host PC and double-click the GNS3 icon on the desktop . Wait until both GNS3 and GNS3 VM become fully operational. See Figure 15-3.
图 15-3。GNS3 开始桌面图标 |
| 5 | When the project window opens, select “Open a project from disk,” open the C:\Users\brendan\GNS3\projects\cmllab-basic – Copy folder, select the cmllab-basic GNS3 project file , and then click the Open button. See Figure 15-4.
图 15-4。GNS3,选择“从磁盘打开项目” |
| 6 | Once the copy of the cmllab-basic project is fully launched, wait for the lab to open correctly and then select GNS3’s File ➤ Save project as . See Figure 15-5.
图 15-5。GNS3,“项目另存为”菜单项 |
| 7 | Name your project and save the new project as cmllab-devops . See Figure 15-6.
图 15-6。GNS3,另存为新项目 |
| 8 | Now, go back to the C:\Users\brendan\GNS3\projects folder, and you will find a new project folder called cmllab-devops. The temporary folder has served its purpose and has now become redundant; you can go ahead and delete the cmllab-basic – Copy folder permanently . See Figure 15-7.
图 15-7。GNS3,删除冗余副本 |
| 9 | Open the cmllab-devops folder, and you will find your new GNS3 project file with the correct name. Now power up all devices and check their running configurations. You have successfully made a true clone of the last GNS3 project. See Figure 15-8.
图 15-8。GNS3,完全克隆的 cmllab-devops 项目 |
| 10 | To prepare you for the next part of our exploration, you will now add two more switches , as shown in Figure 15-9. First, delete PC1 (VPCS) and replace it with Lab-sw3 with a random IP address of 192.168.183.153/24 and with Lab-SW4 with a random IP address 192.168.183.244/24. Power on new switches and let the CPU and memory settle down. Also shown is a Cisco CSR 1000v router to be installed on VMware for a later IOS upgrade lab; this icon is only a placeholder, so you do not have to worry about the csr1000v router until the next chapter.
图 15-9。GNS3,添加两个新交换机 |
| 11 | 在配置新交换机时,从LAB-R1和lab-r2 : ping ip 192.168.183.153 repeat counter 10000" and "ping ip 192.168.183.244 repeat counter 10000运行扩展 ping。让 ping 在后台连续运行。LAB-R1# ping ip 192.168.183.153 repeat 10000``Type escape sequence to abort.``Sending 10000, 100-byte ICMP Echos to 192.168.183.153, timeout is 2 seconds:``.....................................................................................``lab-r2# ping ip 192.168.183.244 repeat 10000``Type escape sequence to abort.``Sending 10000, 100-byte ICMP Echos to 192.168.183.244, timeout is 2 seconds :``......................................... |
| 12 | 交换机通电后,使用以下配置来配置两台交换机。确保在两台交换机上禁用 IP 路由,并配置最后一个网关。对于LAB-sw3,出站流量将通过LAB-R1,对于Lab-SW4,未知流量将首先通过R1。Lab-sw3 配置:Switch> en``Switch# configure terminal``Switch(config)# hostname Lab-sw3``Lab- sw3 (config)# no ip routing``Lab-sw3(config)#``ip default-gateway 192.168.183.10``Lab- sw3 (config)# enable password cisco123``Lab-sw3(config)# username pynetauto privilege 15 secret cisco123``Lab-sw3(config)# line vty 0 15``Lab-sw3(config-line)# login local``Lab-sw3(config-line)# transport input all``Lab-sw3(config-line)# exit``Lab-sw3(config)# ip domain name pynetauto. local``Lab-sw3(config)# crypto key generate rsa !use 1024 bits``Lab-sw3(config)# interface vlan 1``Lab-sw3(config-if)#``description "Native interface"``Lab-sw3(config-if)# ip address 192.168.183.153 255.255.255.0``Lab-sw3(config-if)# no shut``Lab-sw3(config-if)# interface GigabitEthernet0/0``Lab-sw3(config-line)#``description``Lab-sw3(config-if)# end``Lab-sw3# copy running-config startup-configLab-SW4 配置:Switch> en``Switch# configure terminal``Switch(config)# hostname Lab-SW4``Lab-SW4(config)# no ip routing``Lab-SW4(config)#``ip default-gateway 192.168.183.133``Lab-SW4(config)# enable password cisco123``Lab-SW4(config)# username pynetauto privilege 15 secret cisco123``Lab-SW4(config)# line vty 0 15``Lab-SW4(config-line)# login local``Lab-SW4(config-line)# transport input all``Lab-SW4(config-line)# exit``Lab-SW4(config)# ip domain name pynetauto. local``Lab-SW4(config)#``crypto key generate rsa``Lab-SW4(config)# interface vlan 1``Lab-SW4(config-if)# ip address 192.168.183.244 255.255.255.0``Lab-SW4(config-if)# no shut``Lab-SW4(config-if)# end``Lab-SW4# copy running-config startup-config |
| 13 | 配置完成后,您将获得新交换机的 ping 响应。要停止并退出连续(扩展)ping(或 traceroute)请求,请使用 Ctrl+^或 Ctrl+Shift+6 键。LAB-R1# ping ip 192.168.183.153 repeat 10000``Type escape sequence to abort.``Sending 10000, 100-byte ICMP Echos to 192.168.183.153, timeout is 2 seconds:``......................................................................``..........................................!!!!!!!!!!!!!!!!!!!!``Success rate is 85 percent (102/115), round-trip min/avg/max = 17/25/55 ms``lab-r1# ping ip 192.168.183.244 repeat 10000``Type escape sequence to abort .``Sending 10000, 100-byte ICMP Echos to 192.168.183.244, timeout is 2 seconds:``......................................................................``.... !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!``Success rate is 88 percent (102/115), round-trip min/avg/max = 12/21/44 ms |
|
记住 Ctrl+Shift+6 键。如果您的路由器或交换机挂起或您想要中断操作,您可以使用此组合键退出操作。 |
| 14 | 为了确认来自新交换机的所有未知出站目的地分别通过LAB-R1和R1路由器路由,尝试在谷歌的公共 DNS、8.8.8.8 或 8.8.4.4 上使用clear arp和traceroute。 |
|
如果您在 ping Google DNS 时遇到问题,可能是您的互联网 DNS 有问题,或者您没有正确配置您的设备。另一种可能性是 Windows 的最新更新导致 GNS3 出现故障,并破坏了 GNS3 使用的环回接口。即使您无法 ping 通 DNS IP 地址,现在也不要管它,继续下一部分。 |
| | Lab-sw3# clear arp``Lab-sw3# traceroute 8.8.8.8``Type escape sequence to abort.``Tracing the route to 8.8.8.8``VRF info: (vrf in name/id, vrf out name/id)``1 192.168.183.10 36 msec 22 msec 16 msec``2 192.168.183.2 43 msec * *``3 * * *``[omitted for brevity]``30 * * *``Lab-SW4# clear arp``Lab-SW4# traceroute 8.8.4.4``Type escape sequence to abort.``Tracing the route to 8.8.4.4``VRF info: (vrf in name/id, vrf out name/id)``1 192.168.183.133 1020 msec 41 msec 6 msec``2 192.168.183.2 38 msec * *``3 * * *``[omitted for brevity]``30 * * * |
如果您已经完成了前面的任务,那么您可以做更多的实验。上一任务结束时,您的拓扑中应该还有两台 IOSvL2 交换机。让我们登录您的 Linux 服务器,掌握如何使用 Linux 任务调度器,cron。
快速启动 Linux 调度程序 cron
在本书的前面,您已经了解了一些 Linux 基础知识,并且熟悉了如何构建一个 Linux 服务器来作为多个 IP 服务的服务器。随着您对 Linux 的了解,您将很快意识到,许多您在 Windows 操作系统上不能或不被允许做的事情突然在 Linux 系统上变得可能了。它允许您快速安装功能和服务,您的 IP 服务可以立即启动并运行。如果你是一个 Windows 的人,你会记得好老的 Windows 任务调度器;顾名思义,Windows 也有任务调度工具。
Linux 有等效的任务调度器,但是它被称为cron(没有 GUI)。cron相当于 Windows 的任务调度程序,但是它的操作更简单、更可靠、更灵活,因为它没有 GUI 的负担。Linux 的cron工具是 Linux 默认的任务调度工具。它可以执行 Linux 的本地命令,并设置 Python 脚本等应用以设定的时间间隔运行。熟练的 Linux 系统管理员在处理系统监控和备份任务时,会使用自动化的基于 shell 的脚本或其他编程语言做大量工作。为了帮助完成这项任务,Linux 中最常用的工具是cron工具,它的作用与 Windows 系统的 Windows 任务调度器相同。当你写完 Python 应用(脚本)后,你必须学会如何安排你的脚本在特定的时间和特定的周期运行重复的任务,所以掌握如何使用cron并正确运行安排好的任务是至关重要的。当然,如果你工作的公司有一个无底洞的 IT 预算,总有一个简单的方法,那就是购买一个开箱即用的任务调度程序,但是当他们已经雇佣了像你这样的聪明人,为什么你的公司要为这些免费和开源的工具付费呢?
在本节中,您将学习如何在 Ubuntu 和 CentOS 系统中使用cron安排任务。基本语法几乎相同,但是有足够的变化来区别对待它们。
Ubuntu 20.04 LTS 任务调度:crontab
在 Ubuntu 20.04 服务器上,cron被称为crontab。您将编写一个问候 Python 应用,并使用它通过 Ubuntu 的crontab学习任务调度。你需要在你的 Ubuntu 虚拟服务器上完成这些任务。您在这里学到的安排简单脚本的方法也可以用于安排更复杂的脚本。在安排任务时,脚本的难度无关紧要;创建脚本计划相对简单。
|
工作
|
| — | — |
| 1 | 首先,创建一个新目录来放置您的脚本,编写用于crontab测试的三行 Python 代码,并将其保存为print_hello_friend.py。您可以在脚本中更改问候语。这里我们将从datetime库中导入datatime模块,并将print (datetime.now())语句放在打印hello之前。因为我来自澳大利亚,我最喜欢的问候是“G’day mate”pynetauto@ubuntu20s1:~$ mkdir my_cron``pynetauto@ubuntu20s1:~$ cd my_cron``pynetauto@ubuntu20s1:~/my_cron$ nano print_hello_friend.py``pynetauto@ubuntu20s1:~/my_cron$ cat print_hello_friend.py``from datetime import datetime``print(datetime.now())``print("G'day Mate!") |
| 2 | 接下来,运行一次代码,检查它是否打印出了时间和您的“hello”语句。pynetauto@ubuntu20s1:~/my_cron$ python3 print_hello_friend.py``2021-01-13 14:01:40.770547``G'day Mate! |
| 3 | 现在您将使用crontab来调度这个 Python 代码的运行。在创建时间表之前,您将学习如何快速使用crontab。首先,您必须选择是以标准用户还是根用户的身份运行脚本。其次,决定是以不可执行文件模式运行 Python 代码,还是将其更改为可执行文件模式,然后运行它。您已经学习了如何在 Linux 服务器上将一个不可执行的文件转换成可执行文件。如果您已经决定以非根 Linux 用户的身份运行crontab,那么您必须执行带有前导sudo命令的crontab -e命令,这样您就可以输入sudo crontab –e。另一方面,如果您已经决定作为根用户运行crontab,您不需要附加sudo命令。a.非根用户 crontab 调度程序执行pynetauto@ubuntu20s1:~/my_cron$ sudo crontab -eb.root 用户 crontab 调度程序执行pynetauto@ubuntu20s1:~/my_cron$ crontab -e |
| 4 | 为了让cron正确执行您的脚本,您需要检查 Python 可执行文件的位置,并运行which python3或which python3.8命令来确认目录。此外,运行pwd命令来检查 Python 脚本的工作目录。pynetauto@ubuntu20s1:~/my_cron$ which python3.8``/usr/bin/python3.8``pynetauto@ubuntu20s1:~/my_cron$ pwd``/home/pynetauto/my_cron |
| 5 | 接下来,使用crontab -e命令运行crontab来打开 crontab 调度程序。pynetauto用户是一个sudoer,在没有sudo命令的情况下运行命令。当您第一次使用crontab -e命令时,会弹出一条消息来选择您所选择的文本编辑器。选择最简单的或者推荐的,是 nano 文本编辑器,1。pynetauto@ubuntu20s1:~/my_cron$ crontab -e``no crontab for pynetauto - using an empty one``Select an editor. To change later, run 'select-editor'.``1\. /bin/nano <---- easiest``2\. /usr/bin/vim.basic``3\. /usr/bin/vim.tiny``4\. /bin/ed``Choose 1-4 [1]: 1 |
| 6 | 选择 nano 作为crontab调度程序的文本编辑器后,按 Enter 键打开crontab调度程序文件,如下所示。快速浏览信息,并将光标移动到文件的最后一行。准确输入此处显示的内容并保存文件。稍后你会学到更多关于cron时间格式的知识,所以你不知道每行或每个字符是什么意思。就目前而言,你必须先用自己的双手去尝试。[…omitted for brevity]``# For more information see the manual pages of crontab(5) and cron(8)``#``# m h dom mon dow command``* * * * * /usr/bin/python3.8 /home/pynetauto/my_cron/print_hello_friend.py >> /home/pynetauto/my_cron/cron.log |
| 7 | 接下来,等待一到两分钟。您应该看到在您的/home/pynetauto/my_cron目录下自动创建了一个名为cron.log的日志文件。pynetauto@ubuntu20s1:~/my_cron$ ls -lh``total 8.0K``-rw-rw-r-- 1 pynetauto pynetauto 39 Jan 13 14:09 cron.log``-rw-rw-r-- 1 pynetauto pynetauto 73 Aug 27 22:48 print_hello_friend.py用cat命令检查cron.log文件,检查crontab是否正常运行。您将看到cron作业每分钟都在运行,并且问候记录在cron.log文件中。所以,现在你知道了crontab –e中的五星(* * * * *)代表每一分钟。pynetauto@ubuntu20s1:~/my_cron$ cat cron.log``2021-01-13 14:09:01.393031``G'day Mate!``2021-01-13 14:10:01.423129``G'day Mate! |
| 8 | 要检查您的cron作业,请使用crontab –l命令。如果您已经以用户身份登录,则可以选择指定用户。根用户下没有cron作业,所以用它来检查另一个用户的cron日程。pynetauto@ubuntu20s1:~/my_cron$ crontab -l``* * * * * /usr/bin/python3.8 /home/pynetauto/my_cron/print_hello_friend.py >> /home/pynetauto/my_cron/cron.log |
| | pynetauto@ubuntu20s1:~/my_cron$ sudo crontab -u root -l``[sudo] password for pynetauto:**********``no crontab for root |
| 9 | 要管理另一个用户的cron作业,您可以运行sudo crontab –u user_name –l。要修改时间间隔或取消另一个用户的cron工作,使用sudo crontab –u user_name –e并进行所需的时间更改。如果用户没有任何cron工作,它将返回no crontab for user_name消息。pynetauto@ubuntu20s1:~$ sudo crontab -u pythonadmin -e``pynetauto@ubuntu20s1:~$ sudo crontab -u pythonadmin -l``no crontab for pythonadmin |
| 10 | 最后,要取消(停用)当前运行的crontab调度,用crontab -e打开调度文件,在特定的cron任务行前添加#,删除该行,保存文件。控制crontab的cron任务的另一种方法是使用service cron stop 、 service cron start和service cron restart命令停止/启动/重启crontab服务。# * * * * * /usr/bin/python3.8 /home/pynetauto/my_cron/print_hello_friend.py >> /home/pynetauto/my_cron/cron.log |
在步骤 6 中,每次cron作业运行并执行脚本时,活动都被记录到cron.log文件中。但是,两个右箭头或更大的符号(>>)在这里是什么意思呢?如果只用一支箭呢?让我们检查一下不同之处。
* * * * * /usr/bin/python3.8 /home/pynetauto/my_cron/print_hello_friend.py >> /home/pynetauto/my_cron/cron.log
使用两个右箭头将日志追加到下一行。因此,以前的内容不会被覆盖。
pynetauto@ubuntu20s1:~/my_cron$ 猫 cron.log
...
2021-01-13 14:39:01.318529
G'day Mate!
2021-01-13 14:40:01.348718
G'day Mate!
...
如果这里只使用了一个右箭头,那么前面的内容将被完全覆盖,只有最后执行的内容会被记录到文件中。你自己试试,亲眼看看。
* * * * * /usr/bin/python3.8 /home/pynetauto/my_cron/print_hello_friend.py > /home/pynetauto/my_cron/cron.log
pynetauto@ubuntu20s1:~/my_cron$ cat cron.log
2021-01-13 14:44:01.467980
G'day Mate!
CentOS8.1 任务调度器:crond
CentOS 和 Ubuntu cron版本之间有一些细微的差别。您必须熟悉生产中的 Red Hat 和 Debian 衍生的 Linux OS,因为它们是企业 IT 生态系统中最常见的 Linux OS。类似于 Ubuntu 的crontab,CentOS 提供了一个叫做crond的cron。按照 CentOS 8.1 服务器上的说明,学习如何使用crond。要完成以下任务,请通过 SSH 连接登录centos8s1。您需要在实验拓扑中运行部分或全部路由器和交换机。见图 15-10 。
|
工作
|
| — | — |
| 1 | 在前面的章节中,我们介绍了如何探测一个特定的端口(端口 22);在这里,您将学习如何使用 Python 脚本从您的 Linux 服务器 ping 一个 IP 地址。让我们创建一个简单的 ICMP ping 脚本,让它 ping 我们网络中的所有 IP 地址,并将其记录到cron.log文件中。这一次,我们将创建一个名为ip_addresses.txt的外部文本文件来读取每个设备的 IP 地址,然后 ping 三次,并将活动记录到cron.log文件中。首先,按照以下说明创建两个文件ip_addresses.txt和ping_sweep1.py:[pynetauto@centos8s1 ~]$ mkdir icmp_sweeper``[pynetauto@centos8s1 ~]$ cd icmp_ sweeper``[pynetauto@centos8s1 icmp_ sweeper]$ touch ip_addresses.txt``[pynetauto@centos8s1 icmp_ sweeper]$ nano ip_addresses.txt``ip_addresses.txt``192.168.183.10``192.168.183.20``192.168.183.101``192.168.183.102``192.168.183.133``192.168.183.153``192.168.183.244创建一个ping_ip_add.py文件并开始编写代码。重要的是,您要自己键入这些代码,以获得足够的练习。[pynetauto@centos8s1 icmp_ sweeper]$ nano ping_sweep1.py``ping_sweep1.py``#!/usr/bin/python3 # shebang line to tell Linux to run this file as Python3 application``import os # import os module``import datetime # import datetime module``with open("/home/pynetauto/icmp_sweeper/ip_addresses.txt", "r") as ip_addresses: #with open closes file automatically, read the file from specified directory``print("-"*80) # Divider``print(datetime.datetime.now()) # Print current time``for ip_add in ip_addresses: # Use a simple for loop through each line in a file to get ip``ip = ip_add.strip() # Remove any white spaces``rep = os.system('ping -c 3 ' + ip) # Use Linux OS to send 3 ping (ICMP) messages``if rep == 0: # response 0 means up``print(f"{ip} is reachable.") # Informational``else: # response is not 0, then there maybe network connectivity issue``print(f"{ip} is either offline or icmp is filtered.") # Informational``print("-"*80) # Divider``print("All tasks completed.")前面的脚本将打印当前时间,然后 ping 每个 IP 地址三次。然后,如果它在网络上,它将打印“IP 是可达的。”否则,它将显示“IP 离线或 icmp 被过滤” |
| 2 | 这一次,让 Python 脚本成为可执行文件,以便在crond中调度时简化代码。首先,检查用户对脚本的权限。使用chmod +x命令使文件可执行,并再次检查权限。运行chmod命令并将x添加到文件属性后,文件的颜色应该变成绿色。[pynetauto@centos8s1 icmp_sweeper]$ ls -l``total 8``-rw-rw-r--. 1 pynetauto pynetauto 110 Jan 13 15:03 ip_addresses.txt``-rw-rw-r--. 1 pynetauto pynetauto 954 Jan 13 15:02 ping_sweep1.py``[pynetauto@centos8s1 icmp_sweeper]$ chmod +x ping_sweep1.py``[pynetauto@centos8s1 icmp_sweeper]$ ls -l``total 8``-rw-rw-r--. 1 pynetauto pynetauto 110 Jan 13 15:03 ip_addresses.txt``-rwxrwxr-x. 1 pynetauto pynetauto 954 Jan 13 15:02 ping_sweep1.py |
| 3。 | CentOS 的crond与 Ubuntu 的crontab有两个明显的不同。第一个明显的区别是名字,同样,当你必须在 CentOS 上重新安装cron时,你必须使用名字sudo yum install cronie来安装crond。您可以使用rpm -q cronie命令在 CentOS 上检查crond版本。[pynetauto@centos8s1 icmp_pinger]$ rpm -q cronie``cronie-1.5.2-4.el8.x86_64第二个区别是cron任务被安排在哪里。与 Ubuntu 不同,CentOS 的crond直接在/etc/crontab文件中调度任务,如下图所示:[pynetauto@centos8s1 icmp_sweeper]$ cat /etc/crontab``HELL=/bin/bash``PATH=/sbin:/bin:/usr/sbin:/usr/bin``MAILTO=root``# For details see man 4 crontabs``# Example of job definition:``# .---------------- minute (0 - 59)``# | .------------- hour (0 - 23)``# | | .---------- day of month (1 - 31)``# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...``# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat``# | | | | |``# * * * * * user-name command to be executed |
| 4 | 接下来,使用systemctl status crond命令检查crond服务是否可操作。[pynetauto@centos8s1 icmp_sweeper]$ systemctl status crond●t0]Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled; vendor prese>``Active: active (running) since Wed 2021-01-13 14:55:14 AEDT; 19min ago``Main PID: 1405 (crond)``Tasks: 6 (limit: 11166)``Memory: 4.4M``CGroup: /system.slice/crond.service■t0][...omitted for brevity] |
| 5 | 当调度 shell 命令或 Python 脚本在 CentOS crond上运行时,您可以配置和调度不同格式的cron命令。例如,就像前面的 Ubuntu 示例一样,您可以使用完整路径方法来调度一个cron作业。在 CentOS 8.1 中,同样的方法也适用,如下所示:[pynetauto@centos8s1 icmp_sweeper]$ which python3.6``/usr/bin/python3.6``[pynetauto@centos8s1 icmp_ sweeper]$ pwd``/home/pynetauto/icmp_pinger``[pynetauto@centos8s1 icmp_ sweeper]$ sudo nano /etc/crontab``[... omitted for brevity]``*/5 * * * * root /usr/bin/python3.6 /home/pynetauto/icmp_sweeper/ping_sweep1.py >> /home/pynetauto/icmp_sweeper/cron.log 2>&1在这里,我们将 Python 脚本制作成一个可执行文件,并在不指定应用位置的情况下运行它。您的 Python 脚本必须包含第一个 shebang 行(#!/usr/bin/python3)。另外,请注意,您必须指定一个用户来运行脚本;因为/etc/crontab是根用户文件,所以作为根用户运行脚本是最容易的,所以当您调度任务时,确保为cron任务成功运行指定了root用户。现在,打开etc/crontab,安排你的 ICMP 脚本每五分钟向你实验室中的所有路由器和交换机发送 pings。*/5 * * * *表示每隔五分钟。脚本将在第 0、5、10、15、20、25、30、35、40、45、50、55 分钟运行,并在第 0 或 60 分钟运行,直到您暂停或删除cron作业。[pynetauto@centos8s1 icmp_pinger]$ sudo nano /etc/crontab``[... omitted for brevity]``*/5 * * * * root /home/pynetauto/icmp_sweeper/ping_sweep1.py >> /home/pynetauto/icmp_sweeper/cron.log 2>&1 |
| 6 | 最后,打开当前工作目录下的cron.log文件,检查通信检查任务是否每五分钟进行一次。您可能需要等待五分钟,让cron.log文件出现在您的工作目录中,当您查看该文件时,它将类似于以下内容:[pynetauto@centos8s1 icmp_sweeper]$ ls -lh``total 12K``-rw-r--r--. 1 root root 3.2K Jan 13 15:35 cron.log``-rw-rw-r--. 1 pynetauto pynetauto 110 Jan 13 15:03 ip_addresses.txt``-rwxrwxr-x. 1 pynetauto pynetauto 886 Jan 13 15:35 ping_sweep1.py``[pynetauto@centos8s1 icmp_pinger]$ cat cron.log``PING 192.168.183.10 (192.168.183.10) 56(84) bytes of data.``64 bytes from 192.168.183.10: icmp_seq=1 ttl=255 time=15.8 ms``64 bytes from 192.168.183.10: icmp_seq=2 ttl=255 time=15.8 ms``64 bytes from 192.168.183.10: icmp_seq=3 ttl=255 time=21.9 ms``--- 192.168.183.10 ping statistics ---``3 packets transmitted, 3 received, 0% packet loss, time 6ms``rtt min/avg/max/mdev = 15.768/17.798/21.850/2.865 ms``[... omitted for brevity]``PING 192.168.183.244 (192.168.183.244) 56(84) bytes of data.``64 bytes from 192.168.183.244: icmp_seq=2 ttl=255 time=8.33 ms``64 bytes from 192.168.183.244: icmp_seq=3 ttl=255 time=7.81 ms``--- 192.168.183.244 ping statistics ---``3 packets transmitted, 2 received, 33.3333% packet loss, time 7ms``rtt min/avg/max/mdev = 7.808/8.070/8.333/0.277 ms``--------------------------------------------------------------------------------``2021-01-13 15:35:01.973235``192.168.183.10 is reachable.``192.168.183.20 is reachable.``192.168.183.101 is reachable.``192.168.183.102 is reachable .``192.168.183.133 is reachable.``192.168.183.153 is reachable.``192.168.183.244 is reachable.``--------------------------------------------------------------------------------``All tasks are completed. |
| 7 | 在本实验结束时,请确保您返回到/etc/crontab文件,并将#添加到每一行的开头以禁用它。您可以选择删除该行,然后保存更改。 |

图 15-10。
centos8s1,crond 实验室所需设备
开箱即用,crontab比 crond 好用多了。此外,它还可以灵活地在不同的用户账户下安排cron任务,因此许多用户会发现crontab使用起来更加直观。如果你和我一样,可以选择使用以下 Linux 命令在 CentOS8.1 服务器上安装crontab。但是请注意,在生产环境中,一些旧的 Linux 服务器没有连接到互联网,您可能必须使用crond来调度任务,因此了解不同 Linux 操作系统上不同cron工具之间的差异总是有好处的。无论如何,要在 CentOS8 虚拟机上安装crontab,请遵循以下命令:
[pynetauto@centos8s1 ~]$ sudo dnf update
[pynetauto@centos8s1 ~]$ sudo dnf install crontabs
[pynetauto@centos8s1 ~]$ sudo systemctl enable crond.service
[pynetauto@centos8s1 ~]$ sudo systemctl start crond.service
Note
dnf(也称为基于 RPM 发行版的下一代包管理工具)将取代 Fedora、CentOS 和 Red Hat 发行版中的旧 YUM 包管理工具。
接下来,让我们回顾一下cron工作定义或五个星号。
通过示例了解 cron 作业定义
为了有效地使用 Linux cron,您必须理解每个cron作业行开头的五星(星号)的含义。如果你是一个现有的cron用户,并且了解五个星号代表什么以及它们是如何使用的,那就太好了;然后你就可以进入下一部分了。如果您是第一次接触cron,请阅读本部分的其余内容,以理解五个星号的含义。以下片段直接来自 CentOS 的/etc/crontab文件;这是您了解如何在cron调度命令、脚本或任务所需要知道的全部内容:
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
为了便于解释,所有练习将基于crontab而不是crond,所以在 LTS Ubuntu 20 中打开你的crond。
通常,默认的cron作业以五星(星号)开始。这五颗星与任务运行的频率相关,或者换句话说,与某个任务的时间和日期间隔相关。五个值之后是运行特定任务所需的命令(CMD)。这里有一个例子:
分钟
|
小时
|
一月中的某一天
|
月
|
星期几
|
煤矿管理局
|
| — | — | — | — | — | — |
| Zero | one | Thirty-one | Twelve | * | /home/pynetauto/ny_eve.py >> ~/cron_time.log |
表 15-1 是之前显示的作业定义示例的列表版本。
表 15-1。
Linux crontab:时间单位
|单位
|
可用值
|
转换
|
| — | — | — |
| 一小时的最小值 | 0–59 分钟 | 1 小时= 60 分钟 |
| 一天中的某个小时 | 0–23 小时 | 1 天= 24 小时 |
| 一月中的某一天 | 1–31 天 | 1 个月= 28–31 天 |
| 月 | 1-12 日或 1 月、2 月、3 月、4 月…12 月 | 一月、二月、三月、四月… |
| 星期几 | 0–6 | 1 周= 7 天 |
| 煤矿管理局 | 运行的命令 | 不适用的 |
根据您的 Linux 操作系统类型,每个命令也略有不同,但它们在大多数情况下几乎是相同的。如果这些值使用另一种格式,您应该参考操作系统发行商的文档,了解不同的时间单位变化。让我们研究更多的例子来理解cron计划的任务定义。以下示例涵盖了最常见的职务定义。如果您有不同的需求,您可以对以下示例进行修改,并在您的cron作业调度中使用它们。
|
例子
(括号中的备选方案)
|
说明
|
| — | — | — |
| one | * * * * * | 五个星号是默认值,它告诉 Linux 系统每分钟执行一个任务。您已经使用了五个星号来运行 Python 脚本。 |
| Two | 15 03 25 03 * | 3 月 25 日 3 点 15 分。 |
| three | 15 1,13 * * * | 在凌晨 1 点 15 分和下午 1 点 15 分 |
| four | 00 07-11 * * * | 每天上午 7 点到 11 点之间的整点 |
| five | 00 07-11 * * 1-5 | 与示例 4 相同,但仅在工作日运行(1 =周一至 5 = Fri) |
| six | 0 2 * * 6,0 | 仅在周六和周日凌晨 2 点 |
| seven | 00 23 * * 1``(00 23 * * Mon) | 周一晚上 11 点 |
| eight | 0/5 * * * * | 每隔 5 分钟,每隔 5 分钟 |
| nine | 0/15 * * * * | 每隔 15 分钟分钟,每隔一刻钟 |
| Ten | 0/30 * * * * | 每 30 分钟一次,每半小时一次 |
| Eleven | 0 0 * 1/1 * | 在每月第一天的午夜 |
| Twelve | 0/10 * * * 0 (0,10,20,30,40,50 * * * 0) | 每隔 10 分钟;在更新的cron版本上受支持每隔 10 分钟;旧版本cron支持 |
| Thirteen | @reboot | 重启后 |
| Fourteen | @hourly``(0 * * * *) | 在分钟 |
| Fifteen | @daily``(@midnight)``(0 0 * * *) | 在 00:00(午夜) |
| Sixteen | @weekly``(0 0 * * 0) | 每周日午夜 |
| Seventeen | @monthly``(0 0 1 * *) | 在每月第一天的午夜 |
| Eighteen | @yearly``(@annually)``(0 0 1 1 *) | 每年 1 月 1 日1的午夜 |
关于cron更详细的描述还可以在维基百科上找到。你可以通过访问下面的维基百科网站了解更多关于cron和如何使用cron的信息。
URL: https://en.wikipedia.org/wiki/Cron
此外,您可以通过使用下面列出的cron maker 网站,了解更多关于如何实际使用cron的信息:
URL: http://www.cronmaker.com/
URL: http://corntab.com/
在本节中,您使用 Linux cron配置并完成了简单 Python 脚本的调度,然后您使用示例了解了五星背后的含义。现在,你不必用心理解cron的定义;你可以随时回到这个页面作为参考。接下来,您将重温 SNMP 基础知识,为 Python 与网络设备上的 SNMPv3 的交互做好准备,并讨论如何通过 SNMPv3 将 Python 用于系统监控。
使用 Python 运行 SNMPv3 查询
尽管 SNMP 在企业网络监控中无处不在,但它是一项未被充分重视的技术,它一直在检查我们的网络。没有多少网络工程师关注 SNMP 是如何深入工作的,或者它是做什么的,因为它无缝地工作。我们真的不能责怪工程师对 SNMP 一无所知,因为网络监控被认为是入门级的工程师工作。当你往上爬时,你会把更多的精力放在客户服务上,把它放在第一位,然后是网络管理的设计、实施和咨询部分。但是,您应该知道 Python 是如何与 SNMP 交互的吗?答案是肯定的!
在本节中,您将快速回顾 SNMP 概念,以便快速掌握,然后借用一些 Python SNMPv2c 代码来重写代码,以便使用 SNMPv3 与网络设备进行交互。使用 SNMP 开发一个完整的 Python 工具可能需要几周或几个月的时间,所以在这里您将简单地尝试学习 Python 如何使用 SNMP 与网络设备交互。
SNMP 快速入门指南
为了帮助您更新 SNMP 知识,我们将介绍一些基本的 SNMP 事实。如果你每天都在使用 SNMP 技术,你可以跳过这一部分,直接进入 SNMP 实验室。如果您不熟悉 SNMP,这将是 SNMP 的快速入门指南,以了解 Python 如何与 SNMP 交互。
快速 SNMP 历史记录
为了更全面地了解某项技术,了解一项技术的历史总是有好处的。SNMP 最初是作为 ICMP 出现的,并不断发展,直到成为首选的监控工具。
-
1981 :互联网工程任务组(IETF)定义了互联网控制管理协议(ICMP),这是一种用于探查网络上设备的网络通信状态的协议。
-
1987 年 : ICMP 被重新定义为简单网关监控协议(SGMP),作为 RFC 1028 的一部分。
-
1989 年到 1990 年 : SGMP 被重新定义为简单网络监控协议(SNMP),它是作为 RFC 1155、1156 和 1157 的一部分引入的。
-
1990 年至今:大多数 IT 和网络供应商接受 SNMP 作为监控 IP 设备的标准协议。
资料来源:(RFC:777,1028,1155,1156,1157)
SNMP 标准操作
与许多服务器和客户机模型一样,SNMP 也使用服务器和客户机模型。SNMP 服务器被称为管理器,它与客户端(节点)一起工作,被称为代理。即使你已经完成了 CCNA 的研究,你可能也不熟悉标准 SNMP 操作的细节。如果这是您第一次使用 SNMP,请不要跳过这一部分。
-
SNMP 充当服务器(管理器)和客户端(代理)。
-
通常,SNMP 代理作为代理安装在受监控的设备上。
-
SNMP 遵循预设的规则来监控网络设备,并收集和存储监控设备的信息。
-
代理可以使用轮询或事件报告方法将其状态发送给管理器。
-
管理信息库(MIB)是代理用来收集数据的信息标准。
-
对象标识(OID)是 MIB 管理对象的主键。
SNMP 轮询与陷阱(事件报告)方法
为了收集数据,SNMP 管理器向代理发送 Get 请求消息,然后代理发回一条带有其状态的响应消息。SNMP 服务器请求特定的信息,然后 SNMP 客户端做出响应。在 SNMP 服务器可以更改 SNMP 客户端状态的环境中,服务器可以使用 SNMP Set 请求来更改 SNMP 客户端的状态。换句话说,你可以使用 SNMP 来控制客户端,但通常情况下,SNMP 的功能仅限于监控网络。轮询方法在网络上是健谈的,因此它比陷阱方法产生更多的流量。在陷阱方法中,只有在其系统中有任何被监视的信息时,SNMP 客户端才向 SNMP 服务器的陷阱接收器发送信息。这是当今业界最常见的网络监控。此外,在稳定的网络中,它产生的网络流量相对较少。
SNMP 版本
表 15-2 显示了运行中的所有 SNMP 版本。很多遗留系统并不支持更安全的 SNMPv3,所以在大多数网络中,使用最多的是俗称 SNMPv2 的 SNMPv2c,但由于安全问题,越来越多的企业正在向 SNMPv3 迁移。随着旧设备的更新,越来越多的设备可以支持 SNMPv3,并且随着旧设备的退役,SNMP v3 正在慢慢得到部署。远离 SNMPv2 的另一个重要催化剂是虚拟化和基于云的基础架构的引入,这些基础架构现在是企业网络和服务器网络的标准。在虚拟化云世界中,除了实际的服务器群和物理连接,几乎所有东西都以文件或软件的形式存在,这意味着技术不会受到硬件限制的束缚。查看表 15-2 以检查每个版本的特性。
表 15-2。
SNMP 版本
|版本
|
特征
|
| — | — |
| SNMPv1 | 使用社区字符串(密钥);没有内置的安全性;仅支持 32 位系统 |
| SNMPv2c | 最常用;支持 32/64 位系统;提高了 64 位系统的性能;与 v1 几乎相同的功能;安全性低;跨平台支持 |
| SNMPv3 | 经理和代理系统已更改为对象;增加了 64 位系统安全性;保证认证和隐私;将每个 SNMP 实体检测为唯一的引擎 ID 名称 |
了解 TCP/IP 协议栈中的 SNMP 协议
SNMP 是 TCP/IP 协议栈中的应用协议,位于 OSI 模型的传输层;它使用 UDP 在服务器和客户端之间进行通信。以下是 SNMP 通信端口信息以及管理器和代理如何通信的快速摘要:
-
SNMP Get 消息通过 UDP 端口 161 进行通信。
-
SNMP Set 消息也通过 UDP 端口 161 进行通信。
-
使用 SNMP 陷阱时,SNMP 陷阱通过 UDP 端口 162 进行通信,发送消息的代理不必检查陷阱是否发送正确。
-
当使用 SNMP Inform 时,除了它确认已经通过 UDP 端口 162 正确接收到 Inform 消息之外,它的行为是相同的。
关于 SNMP 消息类型
SNMP 管理器和 SNMP 代理之间发送和接收的消息有六种类型;表 15-3 总结了这些信息类型。
表 15-3。
SNMP 消息类型
|消息类型
|
PDU 类型
|
说明
|
| — | — | — |
| get-request | Zero | 获取代理管理 MIB 中指定的对象的值。 |
| get-next-request | one | 获取代理管理 MIB 中指定的对象的下一个对象值。当 MIB 项目有一个表时使用它。 |
| get-bulk-request | one | 它的工作方式类似于get-next-request,并按照指定的次数获取代理管理 MIB 中指定的对象值。 |
| set-request | Two | 设置(更改)代理管理 MIB 中指定的对象的值。 |
| get-response | three | 返回经理请求的结果。 |
| traps | four | 当代理管理 MIB 中发生特定事件时,此消息会发送给管理器以通知管理器。 |
*PDU 类型值的范围仅在 0 和 4 之间。
了解 SNMP 的 SMI、MIB 和 OID
接下来,在简要解释了 SNMP 的 SMI、MIB 和 OID 的定义之后,您将了解安全方法、访问策略、陷阱的同步和异步。让我们快速了解一下 SNMP 术语以及它们在 SNMP 监控网络中的用途。
-
结构化管理信息(SMI) : SMI 是一个定义和配置 MIB 以及创建和管理 MIB 标准的工具。
-
管理信息库(MIB):MIB 是从 SNMP 服务器的角度管理的一组对象。MIB 是管理员可以查看或设置的对象数据库。每个对象都有一个树状结构,您可以使用特定的 MIB 值检查代理的状态或更改其值。ASN.1 (Abstract Notation One)是一种描述对象的脚本语言,包括 SNMP 对象。
-
对象标识符(OID) :必须分配 OID 来生成 MIB。oid 被表示为一系列唯一的特定数字,使用类似于 IP 地址的符号。OID 是一个标识符(主键),它指定了 MIB 的管理对象。您可以运行 OID 查找工具来详细了解 OID。
SNMP 访问策略
为了确保 SNMP 安全性,在管理器和代理通信之间使用 SNMP 社区字符串。manager 需要适当的团体字符串来访问网络中的关键设备信息。大多数网络厂商在网络设备出厂时将默认的公共社区字符串配置为 public;许多网络管理员更改社区字符串,以防止入侵者获取有关网络设置的信息。SNMP 可以根据不同的社区字符串提供不同的信息访问级别。有只读、读写和陷阱社区字符串。
-
SNMP 只读社区字符串:代理的信息可以从其他 SNMP 管理器中读取。
InterMapper使用只读信息映射设备。 -
SNMP 读写社区字符串:使用请求进行通信,可以读取和更改其他设备的状态或设置。注意
InterMapper是只读的,所以它不使用读写字符串模式。 -
SNMP 陷阱社区字符串:这是代理用来向
InterMapper发送 SNMP 陷阱的社区字符串,无论使用哪个 SNMP 陷阱社区字符串,它都会接收并存储信息。
使用 SNMP 陷阱时的同步与异步通信
以下是 SNMP 使用的两种 SNMP 陷阱方法的简要说明。通常,如果使用 SNMP 在 SNMP 管理器(服务器)和代理(客户端)之间传递陷阱消息,SNMP 必须使用同步或异步模式。这两种模式的区别如下:
-
同步模式:使用同步模式时,发送端和接收端使用一个独立于数据的参考时钟。管理器和代理必须根据同步信号一起工作。为了执行连续数据传输,这是一种必须在发送侧和接收侧之间完成一次数据传输/接收才能传输下一次数据的模式。
-
异步模式:在使用异步模式的情况下,时差由接收信号时钟识别,并一次发送一个字符,与发送方的时钟无关。起始位和停止位被添加到要发送的数据包的前面。换句话说,在 SNMP 代理以异步模式运行的情况下,当与 Get 请求通信时,各种数据被交换,并且不规则传输可能是可能的,而不管时间和顺序。
使用 SNMP 陷阱时,生成的消息是一般陷阱或通知陷阱消息。陷阱使用异步通信方法。SNMP 代理单方面发送不需要接收方(SNMP 管理器)确认的信息。相反,SNMP 代理在同步模式下工作,因为发送的信息必须得到接收方(即 SNMP 管理器)的确认。
SNMP 相关的 Python 库
为了让 Python 使用 SNMP 与其他网络设备通信,您必须首先安装正确的 SNMP 库来帮助您实现目标。在本书中,将安装pysnmp模块,使用 Python 与路由器和交换机进行交互。但是,根据您希望使用 Python 和 SNMP 实现的目标,您可以利用几个 SNMP Python 模块。表 15-4 描述了 Python SNMP 相关模块,供您将来参考。
表 15-4。
Python SNMP 库
|SNMP 包名称
|
功能和下载链接
|
| — | — |
| pysnmp | 基于 Python 的 SNMP 模块程序执行速度慢https://pypi.org/project/pysnmp/ |
| python-netsnmp | 对net-snmp使用默认 Python 绑定支持非 Pythonic 接口http://net-snmp.sourceforge.net/wiki/index.php/Python_Bindings |
| snimpy | 为 SNMP 查询开发的 Python 工具基于 PySNMP 开发程序执行速度慢https://snimpy.readthedocs.io/en/latest/ |
| easy-snmp | 基于net-snmp开发的库 Python 风格的界面支持和操作程序执行速度很快https://easysnmp.readthedocs.io/en/latest/ |
| fastsnmpy | 使用基于net-snmp的异步包装器为 SNMPwalk 开发https://github.com/ajaysdesk/fastsnmpy |
了解 MIB 和 oid 并直接浏览 oid
SNMP 代理与 SNMP 管理器共享大量信息。让我们熟悉一下 MIB 和 oid。如前所述,OID 是在 MIB 中指定管理对象的标识符。这意味着 OID 是 MIB 的一部分,它使用唯一的 ID 来表示每个 MIB。OID 的例子是 1 . 3 . 6 . 1 . 2 . 1 . 1 . 3 . 0;这个 OID 是一个包含系统启动后正常运行时间信息的标识符。每个 SNMP 的这些唯一编号可以通过 MIB 库找到,设备制造商通过他们的官方网站共享这些信息。如果设备连接到网络,系统和网络设备都可以使用 MIB 和唯一的 oid 通过 SNMP 交流其系统状态。在这里,您将在我们的 GNS3 实验室中使用交互式 Linux 会话在 Cisco CML 路由器和交换机上执行snmpwalk。稍后,您将从互联网上找到可用的 SNMPv2c Python 代码,并将其更改为支持 SNMPv3,并在我们的实验室拓扑中探测设备信息。
学习在 Linux 服务器上使用 SNMPwalk
首先,您需要安装 SNMP 软件,使您的 Linux 服务器成为 SNMP 管理器。然后,根据哪些代理使用 SNMP 版本,设置密码以向管理器进行身份验证。通常,SNMP 管理器作为独立的服务器安装在 Linux 或 Windows 服务器上,并且安装和使用支持 SNMP API 的程序。最常用的 SNMP 服务器程序是网络安全管理软件产品网络性能监视器、佩斯勒 PRTG 网络监视器和 SysAid 监视器。该软件的大部分都提供了测试用的免费发布版本。有关这些计划的更多详细信息,请参考每个供应商的网站和资源。
这里,我们将在 Linux 服务器上安装 SNMP 服务器软件,在所有网络设备上配置 SNMPv3,然后在命令行执行虚拟网络设备的 MIB 的 OID。首先,在 Linux 服务器上安装 SNMP 管理器软件,如下节所述。如果要从另一台 SNMP 服务器进行监控,可以选择安装 SNMP 代理(客户端)软件。所用的 Linux 服务器是 CentOS 8.1 服务器,因此您必须登录到centos8s1服务器才能逐步执行任务。实验室拓扑与之前相同;您需要一台 CentOS 服务器,并且所有路由器和交换机都处于开机状态。
在 CentOS8 上安装和配置 SNMP 服务器
按照以下步骤在 CentOS 8 服务器上安装 SNMP 服务,并配置用户和用户的身份验证和授权设置。你必须在你的centos8s1服务器中键入用粗体标记的命令。
|
工作
|
| — | — |
| 1 | 首先,使用这里显示的命令在 CentOS 服务器上安装net-snmp-utils和net-snmp-devel包。net-snmp-utils是用于snmpwalk的工具。[pynetauto@centos8s1 ~]$ sudo yum install -y net-snmp net-snmp-utils net-snmp-devel |
| 2 | 配置 SNMPv3 用户和验证密码。此处配置的信息将用于配置 SNMP 代理,在本例中为网络设备。首先,在 CentOS 服务器上启用并启动snmpd;然后检查运行状态。之后,在配置新的 SNMP 用户和密码之前停止该服务。[pynetauto@centos8s1 ~]$ sudo systemctl enable snmpd``[pynetauto@centos8s1 ~]$ sudo systemctl start snmpd``[pynetauto@centos8s1 ~]$ sudo systemctl status snmpd``[pynetauto@centos8s1 ~]$ sudo systemctl stop snmpd在 Linux 系统上,有三种方法可以为 SNMPv3 用户配置密码,这里将讨论这三种方法。如果在用户配置步骤中未指定认证和加密方法,将自动使用 MD5 和 AES,并且访问级别将设置为只读(-ro)。使用其中一种方法添加 SNMP 用户和密码。A.命令行方式:如果您通过命令设置用户,您可以在一个命令行中同时设置用户名、认证密码和仅加密密钥。典型的命令选项如下所示:net-snmp-config --create-snmpv3-user [-ro] [-A authpass] [-X privpass] [-a MD5|SHA] [-x DES|AES] [username]示例 1:使用默认 MD5 和 DES 创建 SNMP 用户,但不指定私有密码:[pynetauto@centos8s1 ~]$ sudo net-snmp-config --create-snmpv3-user -A AUTHPass1 SNMPUser2``adding the following line to /var/lib/net-snmp/snmpd.conf:``createUser SNMPUser1 MD5 "AUTHPass1" DES ""``adding the following line to /etc/snmp/snmpd.conf:``rwuser SNMPUser1示例 2:指定身份验证和加密以及私有密码方法的 SNMP 用户创建。以下示例使用 DES 加密配置 MD5 身份验证:[pynetauto@centos8s1 ~]$ sudo net-snmp-config --create-snmpv3-user -ro -A AUTHPass1 -X PRIVPass1 -a MD5 -x DES SNMPUser3``adding the following line to /var/lib/net-snmp/snmpd.conf:``createUser SNMPUser2 MD5 "AUTHPass1" DES "PRIVPass1"``adding the following line to /etc/snmp/snmpd.conf:``rouser SNMPUser2示例 3:这是使用 SHA 身份验证和 AES 加密的 SNMP 用户创建:[pynetauto@centos8s1 ~]$ sudo net-snmp-config --create-snmpv3-user -ro -A AUTHPass1 -X PRIVPass1 -a SHA -x AES SNMPUser1``adding the following line to /var/lib/net-snmp/snmpd.conf:``createUser SNMPUser3 SHA "AUTHPass1" AES "PRIVPass1"``adding the following line to /etc/snmp/snmpd.conf:``rouser SNMPUser3B.手动配置:您也可以自己打开 SNMP 配置文件,在文件末尾追加新的用户信息来设置 SNMP 用户。只需打开这两个文件并添加用户信息。例 4:这是一个使用 MD5 的名为SNMPUser1的用户账号,创建了 DES。添加最后一行和用户名,如下所示:[pynetauto@centos8s1 ~]$ sudo nano /var/lib/net-snmp/snmpd.conf``[...omitted for brevity]``##############################################################``#``# snmpNotifyFilterTable persistent data``#``##############################################################``engineBoots 18``oldEngineID 0x80001f8880acfa8434b71f4a5f00000000``createUser SNMPUser4 MD5 "AUTHPass1" DES "PRIVPass1" <<< Add this to the last line``[pynetauto@centos8s1 ~]$ sudo nano /etc/snmp/snmpd.conf``[...omitted for brevity]``###############################################################################``# Further Information``#``# See the snmpd.conf manual page, and the output of "snmpd -H".``rwuser SNMPUser2``rouser SNMPUser3``rouser SNMPUser1``rwuser SNMPUser4 <<< Add this to the last line交互模式:这个交互命令在使用 MD5 和 DES 创建 SNMP 用户时很有用。[pynetauto@centos8s1 ~]$ sudo net-snmp-create-v3-user``[sudo] password for pynetauto:``Enter a SNMPv3 user name to create:``SNMPUser5``Enter authentication pass-phrase:``AUTHPass1``Enter encryption pass-phrase:``[press return to reuse the authentication pass-phrase]``PRIVPass1``adding the following line to /var/lib/net-snmp/snmpd.conf:``createUser SNMPUser MD5 "AUTHPass1" DES "PRIVPass1"``adding the following line to /etc/snmp/snmpd.conf:``rwuser SNMPUser5 |
| 3 | 输入以下命令再次启动 SNMP 服务,并检查 snmpd 服务的状态。状态必须为“活动:活动(正在运行)”[pynetauto@centos8s1 ~]$ sudo systemctl start snmpd``[pynetauto@centos8s1 ~]$ sudo systemctl status snmpd●t0]Loaded: loaded (/usr/lib/systemd/system/snmpd.service; enabled; vendor preset: disabled)``Active: active (running) since Wed 2021-01-13 16:23:03 AEDT; 5s ago``Main PID: 43244 (snmpd)``Tasks: 1 (limit: 11166)``Memory: 4.9M``CGroup: /system.slice/snmpd.serviceε |
| 4 | 使用SNMPwalk命令检查 SNMP 是否在本地服务器上正常工作。使用方法 c 中创建的SNMPUser运行 CentOS 服务器的snmpwalk。[pynetauto@centos8s1 icmp_sweeper]$ snmpwalk -v3 -u SNMPUser5 -l authPriv -a MD5 -A AUTHPass1 -X PRIVPass1 localhost``SNMPv2-MIB::sysDescr.0 = STRING: Linux centos8s1 4.18.0-193.14.2.el8_2.x86_64 #1 SMP Sun Jul 26 03:54:29 UTC 2020 x86_64``SNMPv2-MIB::sysObjectID.0 = OID: NET-SNMP-MIB::netSnmpAgentOIDs.10``DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (15885) 0:02:38.85``SNMPv2-MIB::sysContact.0 = STRING: Root <root@localhost> (configure /etc/snmp/snmp.local.conf)``SNMPv2-MIB::sysName.0 = STRING: centos8s1``SNMPv2-MIB::sysLocation.0 = STRING: Unknown (edit /etc/snmp/snmpd.conf)``SNMPv2-MIB::sysORLastChange.0 = Timeticks: (0) 0:00:00.00``SNMPv2-MIB::sysORID.1 = OID: SNMP-FRAMEWORK-MIB::snmpFrameworkMIBCompliance``SNMPv2-MIB::sysORID.2 = OID: SNMP-MPD-MIB::snmpMPDCompliance``SNMPv2-MIB::sysORID.3 = OID: SNMP-USER-BASED-SM-MIB::usmMIBCompliance``SNMPv2-MIB::sysORID.4 = OID: SNMPv2-MIB::snmpMIB``SNMPv2-MIB::sysORID.5 = OID: SNMP-VIEW-BASED-ACM-MIB::vacmBasicGroup``SNMPv2-MIB::sysORID.6 = OID: TCP-MIB::tcpMIB``SNMPv2-MIB::sysORID.7 = OID: IP-MIB::ip``SNMPv2-MIB::sysORID.8 = OID: UDP-MIB::udpMIB``[... omitted for brevity] |
以下是 Linux snmpwalk命令中使用的选项句柄的简要描述:
snmpwalk -v3 -u SNMPUser2 -l authPriv -a MD5 -A AUTHPass1 -X PRIVPass1 127.0.0.1
-v3(版本)
-u (SNMP 用户名)
-l(安全级别)
-a(身份验证方法)
-A(通行短语)
使用 Python 脚本配置 Cisco 路由器和交换机以支持 SNMPv3
以下是 Cisco 设备的 SNMP 相关设置。要实现 SNMPv3 而不是 SNMPv2c,你得比 SNMPv2c 更懂 SNMP 认证。由于 SNMPv3 遗留的兼容性问题,SNMP v2c 仍然是目前使用的最流行的 SNMP 版本。旧设备没有支持 SNMP 版本 3 的软件代码,所以除非您的网络由旧设备组成,否则 SNMPv2c 将是您的选择。未来,几乎所有设备都将支持更安全的 SNMPv3。在本书中,您将改用 SNMPv3。您将编写一个快速 Python 脚本,在所有 Cisco 设备上添加 SNMP 配置。
||
工作
|
| — | — |
| 1 | 所有 SNMP 代理分别管理自己的(系统)信息,这些信息可以在 SNMPv3 消息中使用。对于 SNMP 代理设备,如 Cisco 路由器和交换机,Cisco 建议在配置和使用 SNMPv3 之前设置唯一的 SNMP 引擎 id。如果在配置期间没有指定 SNMP 引擎 ID,那么企业号和默认 MAC 地址的组合将生成唯一的引擎 ID。要正确地向 Cisco 设备添加 SNMPv3 配置,您必须使用这里建议的三个snmp-server命令。A.引擎 ID 配置命令示例如下所示:LAB-R1(config)# snmp-server engineID local 19216818310B.SNMP 组名配置命令如下所示。在这种情况下,GROUP1是 SNMP 组名。该命令将创建一个名为GROUP1的 SNMPv3 组,并带有私有密码。LAB-R1(config)# snmp-server group GROUP1 v3 privC.使用与管理端 SNMP 用户证书相同的信息,您可以使用以下命令在 Cisco 设备上配置 SNMP 用户:LAB-R1(config)# snmp-server user SNMPUser1 GROUP1 v3 auth sha AUTHPass1 priv aes 128 PRIVPass1您可以使用show snmp user来检查 Cisco 设备上的 SNMP 用户信息。在下一步中,根据前面的命令,让我们编写一个 Python 脚本,将此配置一次添加到所有设备。LAB-R1# show snmp user``*Jan 13 05:22:55.217: %SYS-5-CONFIG_I: Configured from console by console``User name: SNMPUser1``Engine ID: 192168183100``storage-type: nonvolatile active``Authentication Protocol: SHA``Privacy Protocol: AES128``Group-name: GROUP1 |
| 2 | 为了让本实验更有趣,我们将复制我们之前创建的 ICMP sweeper 工具,并在登录设备和配置 SNMP 之前,在我们的脚本中将它用作预通信检查工具。因此,使用这些命令复制文件,然后将ping_sweep1.py脚本重命名为precheck_tool.py:[pynetauto@centos8s1 ~]$ pwd``/home/pynetauto``[pynetauto@centos8s1 ~]$ mkdir snmp_test``[pynetauto@centos8s1 ~]$ ls icmp_sweeper``cron.log ip_addresses.txt ping_sweep1.py``[pynetauto@centos8s1 ~]$ mkdir snmp_test``[pynetauto@centos8s1 ~]$ cp ./icmp_sweeper/ping_sweep1.py ./snmp_test/precheck_tool.py``[pynetauto@centos8s1 ~]$ cp ./icmp_sweeper/ip_addresses.txt ./snmp_test/ip_addresses.txt``[pynetauto@centos8s1 ~]$ cd snmp_test``[pynetauto@centos8s1 snmp_test]$ ls``ip_addresses.txt precheck_tool.py在这个任务结束时,您应该在/home/pynetauto/snmp_test目录下有两个文件。ip_addresses.txt文件应该已经包含了我们拓扑中所有路由器和交换机的 IP 地址。 |
| 3 | 您将在 nano 文本编辑器中打开并修改precheck_tool.py,以便脚本的主要部分成为一个函数。这样,我们可以从另一个 Python 脚本中调用这个函数作为一个模块。按如下方式修改文件:[pynetauto@centos8s1 snmp_test]$ nano precheck_tool.py``GNU nano 4.8 /home/pynetauto/snmp_test/precheck_tool.py``#!/usr/bin/python3``import os``def icmp_pinger(ip):``rep = os.system('ping -c 3 ' + ip)``if rep == 0:``print(f"{ip} is reachable.") # Print ip is reachable if the device is on the network``else:``print(f"{ip} is either offline or icmp is filtered. Exiting.")``exit() # Exit application if any ip address is unreachable.``print("-"*80)``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line当满足else条件时,例如 192.168.183.10 不可达,将退出程序,如下图所示:[pynetauto@centos8s1 snmp_test]$ python3 snmp_config.py``PING 192.168.183.10 (192.168.183.10) 56(84) bytes of data.``--- 192.168.183.10 ping statistics ---``3 packets transmitted, 0 received, 100% packet loss, time 62ms``192.168.183.10 is either offline or icmp is filtered. Exiting . |
| 4 | 现在,是时候创建配置脚本snmp_config.py了。作为最佳实践,我们将使用每个设备的 IP 地址来指定 SNMP 引擎 ID。IP 地址中的点将被删除,整个数字将被重新用作设备的 SNMP 引擎 ID。[pynetauto@centos8s1 snmp_test]$ nano snmp_config.py``GNU nano 4.8 /home/pynetauto/snmp_test/snmp_config.py``#!/usr/bin/python3``import time``import paramiko``from getpass import getpass``# Custom tools``from precheck_tool import icmp_pinger``with open("/home/pynetauto/snmp_test/ip_addresses.txt", "r") as ip_addresses:``for ip in ip_addresses:``ip = ip.strip()``icmp_pinger(ip)``username = input("Enter username : ") # Ask for username``password = getpass("Enter password : " ) # Ask for password``with open("/home/pynetauto/snmp_test/ip_addresses.txt", "r") as ip_addresses:``for ip in ip_addresses:``ip = ip.strip()``eng_id = ip.replace(".", "") # Remove dot in ip address and use it as SNMP Engine ID``print ("Now logging into " + (ip))``ssh_client = paramiko.SSHClient()``ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())``ssh_client.connect(hostname=ip,username=username,password=password)``print (f"Successful connection to {ip}\n")``print ("Now completing following tasks : " + "\n")``remote_connection = ssh_client.invoke_shell()``print (f"Adding SNMP configuration to {ip}")``remote_connection.send("show clock\n")``remote_connection.send("configure terminal\n") # Add SNMP configurations``remote_connection.send(f"snmp-server engineID local {eng_id}\n")``remote_connection.send("snmp-server group GROUP1 v3 priv\n")``remote_connection.send("snmp-server user SNMPUser1 GROUP1 v3 auth sha AUTHPass1 priv aes 128 PRIVPass1\n")``remote_connection.send("do wri\n")``remote_connection.send("exit\n")``time.sleep(2)``print ()``time.sleep(2)``output = remote_connection.recv(65535)``print((output).decode('ascii'))``print(f"Successfully configured {ip} & Disconnecting.")``print("-"*80)``ssh_client.close``time.sleep(2)``print("All tasks were completed successfully. Bye!")``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line |
| 5 | 在运行脚本之前,您需要通过运行下面的pip3命令来安装paramiko库。在centos8s1服务器上安装paramiko。[pynetauto@centos8s1 snmp_test]$ sudo pip3 install paramiko |
| 6 | 您应该看到最后一行代码是一个成功的配置脚本,它在主脚本的for循环之外。运行snmp_config.py并在所有设备上配置 SNMP 组设置。该脚本最初将使用 ICMP 检查网络连接。如果任何设备不可达,应用将退出。如果所有设备都在网络上,脚本将运行并完成所有设备上的 SNMP 配置。[pynetauto@centos8s1 snmp_test]$ python3 snmp_config.py``PING 192.168.183.10 (192.168.183.10) 56(84) bytes of data.``64 bytes from 192.168.183.10: icmp_seq=1 ttl=255 time=14.7 ms``64 bytes from 192.168.183.10: icmp_seq=2 ttl=255 time=11.5 ms``64 bytes from 192.168.183.10: icmp_seq=3 ttl=255 time=6.22 ms |
| | --- 192.168.183.10 ping statistics ---``3 packets transmitted, 3 received, 0% packet loss, time 6ms``rtt min/avg/max/mdev = 6.215/10.819/14.749/3.516 ms``192.168.183.10 is reachable .``--------------------------------------------------------------------------------``PING 192.168.183.20 (192.168.183.20) 56(84) bytes of data.``64 bytes from 192.168.183.20: icmp_seq=1 ttl=255 time=22.4 ms``64 bytes from 192.168.183.20: icmp_seq=2 ttl=255 time=25.0 ms``64 bytes from 192.168.183.20: icmp_seq=3 ttl=255 time=22.5 ms``[...omitted for brevity]``Successfully configured 192.168.183.153 & Disconnecting.``--------------------------------------------------------------------------------``Now logging into 192.168.183.244``Successful connection to 192.168.183.244``Now completing following tasks :``Adding SNMP configuration to 192.168.183.244``**************************************************************************``* IOSv is strictly limited to use for evaluation, demonstration and IOS *``* education. IOSv is provided as-is and is not supported by Cisco's *``* Technical Advisory Center. Any use or disclosure, in whole or in part, *``* of the IOSv Software or Documentation to any third party for any *``* purposes is expressly prohibited except as otherwise authorized by *``* Cisco in writing. *``**************************************************************************``Lab-SW4#show clock``*12:33:19.576 UTC Wed Jan 13 2021``Lab-SW4#configure terminal``Enter configuration commands, one per line. End with CNTL/Z.``Lab-SW4(config)#snmp-server engineID local 192168183244``Lab-SW4(config)#snmp-server group GROUP1 v3 priv``Lab-SW4(config)#$User1 GROUP1 v3 auth sha AUTHPass1 priv aes 128 PRIVPass1``Lab-SW4(config)#do wri``Building configuration...``Successfully configured 192.168.183.244 & Disconnecting.``--------------------------------------------------------------------------------``All tasks were completed successfully. Bye! |
| 7 | 成功运行 SNMP 配置后,登录每台设备,确认每台设备上与 SNMP 相关的配置。图中显示了一个Lab-SW4配置的示例,但是所有其他设备都应该具有 SNMP 用户和组配置。 |
| | Lab-SW4#show snmp user``User name: SNMPUser1``Engine ID: 192168183244``storage-type: nonvolatile active``Authentication Protocol: SHA``Privacy Protocol: AES128``Group-name: GROUP1``Lab-SW4#show run | in snmp``snmp-server engineID local 192168183244``snmp-server group GROUP1 v3 priv |
您的 Cisco 设备现在已准备好与您的 SNMP 服务器进行 SNMP 通信。
从 Linux 服务器进行 SNMPwalk
从您的centos8s1服务器,让我们继续使用 SNMPv3 命令执行snmpwalk;这里有很多东西要学。
|
工作
|
| — | — |
| 1 | CentOS 8.1 服务器使用以下snmpwalk命令通过 SNMP 检索LAB-R1的 vIOS MIB 使用的 OID 信息。如果使用此命令,将加载所有 OID 信息,如下所示:[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10``SNMPv2-MIB::sysDescr.0 = STRING: Cisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)``Technical Support: http://www.cisco.com/techsupport``Copyright (c) 1986-2016 by Cisco Systems, Inc.``Compiled Tue 22-Mar-16 16:19 by prod_rel_team``SNMPv2-MIB::sysObjectID.0 = OID: SNMPv2-SMI::enterprises.9.1.1041``DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (157667) 0:26:16.67``SNMPv2-MIB::sysContact.0 = STRING:``SNMPv2-MIB::sysName.0 = STRING: LAB-R1.pynetauto.local``SNMPv2-MIB::sysLocation.0 = STRING:``SNMPv2-MIB::sysServices.0 = INTEGER: 78``SNMPv2-MIB::sysORLastChange.0 = Timeticks: (0) 0:00:00.00``SNMPv2-MIB::sysORID.1 = OID: SNMPv2-SMI::enterprises.9.7.129``SNMPv2-MIB::sysORID.2 = OID: SNMPv2-SMI::enterprises.9.7.115``SNMPv2-MIB::sysORID.3 = OID: SNMPv2-SMI::enterprises.9.7.265``SNMPv2-MIB::sysORID.4 = OID: SNMPv2-SMI::enterprises.9.7.112``SNMPv2-MIB::sysORID.5 = OID: SNMPv2-SMI::enterprises.9.7.106``SNMPv2-MIB::sysORID.6 = OID: SNMPv2-SMI::enterprises.9.7.47``SNMPv2-MIB::sysORID.7 = OID: SNMPv2-SMI::enterprises.9.7.122``[... omitted for brevity] |
| 2 | 如果您在其他设备上运行相同的命令,您还应该看到每个设备的所有 MIB 信息。[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.20``[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.101``[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.102``[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.133``[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.153``[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.244 |
| 3 | 使用snmpwalk或snmpget命令练习检索LAB-SW1的系统信息。1.3.6.1.2.1.1.3.0 ( sysUpTime.0)是表示系统启动时间的 OID。您可以使用snmpget命令来检索和检查从 SNMP 管理器到 SNMP 代理的信息。[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 sysUpTime.0``DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (1606783) 4:27:47.83SNMP 代理LAB-SW1告诉我们系统重启后的正常运行时间是 4 小时 27 分 47 秒。 |
| 4 | 使用 1.3.6.1.2.1.1.5.0 ( sysName.0或sysName)检查路由器或交换机的名称。[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 sysName.0``SNMPv2-MIB::sysName.0 = STRING: LAB-R1.pynetauto.local``[pynetauto@centos8s1 snmp_test ]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 sysName``SNMPv2-MIB::sysName.0 = STRING: LAB-R1.pynetauto.local``[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 1.3.6.1.2.1.1.5.0``SNMPv2-MIB::sysName.0 = STRING: LAB-R1.pynetauto.local |
| 5 | 如果使用 1.3.6.1.2.1.2.2.1.7( ifAdminStatus.1),可以查看界面的设置状态。该信息确认管理员已经激活了该界面。[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 ifAdminStatus``IF-MIB::ifAdminStatus.1 = INTEGER: up(1)``IF-MIB::ifAdminStatus.2 = INTEGER: up(1)``IF-MIB::ifAdminStatus.3 = INTEGER: down(2)``IF-MIB::ifAdminStatus.4 = INTEGER: down(2)``IF-MIB::ifAdminStatus.5 = INTEGER: up(1)``IF-MIB::ifAdminStatus.6 = INTEGER: up(1)``IF-MIB::ifAdminStatus.7 = INTEGER: up(1)``[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 1.3.6.1.2.1.2.2.1.7``IF-MIB::ifAdminStatus.1 = INTEGER: up(1)``IF-MIB::ifAdminStatus.2 = INTEGER: up(1)``IF-MIB::ifAdminStatus.3 = INTEGER: down(2)``IF-MIB::ifAdminStatus.4 = INTEGER: down(2)``IF-MIB::ifAdminStatus.5 = INTEGER: up(1)``IF-MIB::ifAdminStatus.6 = INTEGER: up(1)``IF-MIB::ifAdminStatus.7 = INTEGER: up(1) |
| 6 | 您可能已经注意到,第一个接口的 OID 是在 1.3.6.1.2.1.2.2.1.7 后面附加了. 1 的 ID。如果添加并执行. 1,如下所示,GigabitEthernet0/0 端口的状态显示为 up(1)。[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 1.3.6.1.2.1.2.2.1.7.1``IF-MIB::ifAdminStatus.1 = INTEGER: up(1) |
| 7 | 同样,在末尾添加. 3 意味着千兆以太网 0/1。此端口当前被禁用,因此显示为 down(2)。[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 1.3.6.1.2.1.2.2.1.7.3``IF-MIB::ifAdminStatus.3 = INTEGER: down(2) |
| 8 | 如果使用 OID 1.3.6.1.2.1.2.2.1.8 ( ifOperStatus),可以查询所有接口的运行状态。从您的服务器快速运行一个snmpwalk命令进行确认。[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 ifOperStatus``IF-MIB::ifOperStatus.1 = INTEGER: up(1)``IF-MIB::ifOperStatus.2 = INTEGER: up(1)``IF-MIB::ifOperStatus.3 = INTEGER: down(2)``IF-MIB::ifOperStatus.4 = INTEGER: down(2)``IF-MIB::ifOperStatus.5 = INTEGER: up(1)``IF-MIB::ifOperStatus.6 = INTEGER: up(1)``IF-MIB::ifOperStatus.7 = INTEGER: up(1) |
| 9 | 使用snmpget命令查询有关接口的信息,在本例中是 GigabitEthernet0/0。IfDescr.1查询接口描述,ifOperStatus.1查询接口运行状态。可以用 2 代替数字 1 来查询下一个千兆以太网 2 接口。[pynetauto@centos8s1 snmp_test]$ snmpget -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 ifDescr.1 ifOperStatus.1``IF-MIB::ifDescr.1 = STRING: GigabitEthernet0/0``IF-MIB::ifOperStatus.1 = INTEGER: up(1) |
| 10 | 下面的snmpget命令查询 GigabitEthernet0/3 的接口状态;由于没有使用,所以标记为down(2)。[pynetauto@centos8s1 snmp_test]$ snmpget -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 ifDescr.4 ifOperStatus.4``IF-MIB::ifDescr.4 = STRING: GigabitEthernet0/3``IF-MIB::ifOperStatus.4 = INTEGER: down(2) |
| 11 | 许多 oid 可以揭示每个组件和配置的当前状态。另一个值得注意的事实是 RMON OID;它可以泄露软件版本号和引导信息。该示例显示了路由器 RMON OID。[pynetauto@centos8s1 snmp_test]$ snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 rmon``RMON-MIB::rmon.19.1.0 = Hex-STRING: FF C0 00 40``RMON-MIB::rmon.19.2.0 = STRING: "15.6(2)T"``RMON-MIB::rmon.19.3.0 = ""``RMON-MIB::rmon.19.4.0 = Hex-STRING: 07 E4 08 1E 02 15 16 07``RMON-MIB::rmon.19.5.0 = INTEGER: 1``RMON-MIB::rmon.19.6.0 = STRING: "flash0:/vios-adventerprisek9-m"``RMON-MIB::rmon.19.7.0 = IpAddress: 0.0.0.0``RMON-MIB::rmon.19.8.0 = INTEGER: 1``RMON-MIB::rmon.19.9.0 = INTEGER: 1``RMON-MIB::rmon.19.12.0 = IpAddress: 0.0.0.0``RMON-MIB::rmon.19.15.0 = Hex-STRING: 00``RMON-MIB::rmon.19.16.0 = Hex-STRING: 7E 00 |
vIOS 中使用的 OID 信息几乎与真实的 IOS 设备相同。您可以从本实验中学到很多东西,并从前面的示例中获得有用的信息。同样,您必须自己在键盘上键入命令,并学习前面的概念。参见图 15-11 。
对于思科网络设备,您可以使用思科功能导航器找到所需的 OID 信息。
URL: https://cfnng.cisco.com/mibs
有关思科 MIB 的一些问答,请参考以下内容:
以下是对思科设备最有用的 MIB,供您参考:
IF-MIB:接口计数器
IP-MIB:包含 IP 地址
IP-FORWARD-MIB:包含一个路由表
实体-MIB:包含库存信息
LLDP-MIB:包含邻居信息

图 15-11。
思科功能导航器
借用一些 Python SNMP 代码的例子
正如本书所讨论的那样,学习一门编程语言,比如 Python、JavaScript 或 Perl,并不能让你成为一名可以立即开发应用的网络工程师。基于您强大的基础网络知识,您必须尽力拓宽您支持的技术。你的旅程可能会变得漫长,也许对某些人来说太慢了。幸运的是,你经常可以在网上找到合适的代码供你使用并借用。何必多此一举呢?如果你不是第一个遇到特定问题的人,那么别人已经在你之前解决了这个问题。你只需要知道如何以及在哪里寻找你要找的特定信息。(当然,我相信有些知识更靠谱,以其他形式传递更好。)
在网上,我正在寻找一些使用 SNMP 的 Python 用例,看到了一篇很好的文章,就把这个站点加入了书签,以备将来参考。当写这本书的时候,我被要求将 SNMP 和 Python 作为这本书的一部分。但是我不想花几个星期的时间去写新的代码;我想找到一个现有的 SNMP Python 脚本来展示 Python 如何在 SNMP 中使用的例子。本节将介绍的 SNMP Python 代码来自 Alessandro Maggio 的主页。感谢亚历山德罗,在他的同意下,我已经把这个剧本作为这本书的一部分。要获得代码的完整解释,请访问 Alex 的博客。
在这里找到 Alessandro Maggio 的博客和代码教程:
URL: https://www.ictshore.com/
URL: https://www.ictshore.com/sdn/python-snmp-tutorial/
SNMP 实验室源代码
以下代码是 SNMPv2c 的 Python 代码。SNMPv2c 使用只读社区字符串,简化了服务器端和代理端的 SNMP 配置。尽管如此,我们都知道,SNMPv2c 不如 SNMPv3 安全,所以您将快速检查代码,并进行微小的修改,以将其转换为 SNMPv3 代码。稍后,基于一个场景,我们将编写一个简单的 Python 集成脚本来与这个脚本协调工作。首先,让我们仔细看看原始的 SNMP Python 代码。解释总是以#或""" """开头。
[pynetauto@centos8s1 snmp_test]$ nano quicksnmp.py
GNU nano 4.8 /home/pynetauto/snmp_test/quicksnmp.py
# This code uses the High-Level API module included in the pysnmp library.
from pysnmp import hlapi
"""
The first function or get () function defines pre-requisite information to initiate and make device information requests. Generally, for such a function to work, you specifically have to provide the target IP (or DNS name), Object ID(OID), Credentials, SNMP port number (161), EngineID, or SNMP context. There is no strict requirement to parse the EngineID and SNMP context in simpler Python code, so in this example, the EngineID or SNMP context is not parsed to communicate with the Agent. All this information forms a handler, which this script enables the Server to communicate with the Agent so that the Agent can send the requested information about itself.
"""
def get(target, oids, credentials, port=161, engine=hlapi.SnmpEngine(), context=hlapi.ContextData()):
handler = hlapi.getCmd(
engine,
credentials,
hlapi.UdpTransportTarget((target, port)),
context,
*construct_object_types(oids)
)
return fetch(handler, 1)[0]
# Second function needs a single argument list_of_oids, a blank list is used to collect and return object types
# information.
def construct_object_types(list_of_oids):
object_types = []
for oid in list_of_oids:
object_types.append(hlapi.ObjectType(hlapi.ObjectIdentity(oid)))
return object_types
# Third function requires two arguments and uses the result list to handle any errors in the script
.
def fetch(handler, count):
result = []
for i in range(count):
try:
error_indication, error_status, error_index, var_binds = next(handler)
if not error_indication and not error_status:
items = {}
for var_bind in var_binds:
items[str(var_bind[0])] = cast(var_bind[1])
result.append(items)
else:
raise RuntimeError('Got SNMP error: {0}'.format(error_indication))
except StopIteration:
break
return result
# The fourth function, cast() uses the information received from PySNMP to pass the values, this function
# plays the role of changing the value into int or float or string type.
def cast(value):
try:
return int(value)
except (ValueError, TypeError):
try:
return float(value)
except (ValueError, TypeError):
try:
return str(value)
except (ValueError, TypeError):
pass
return value
# This code is written for SNMPv2c and uses a community string named 'ICTSHORE'.
hlapi.CommunityData('ICTSHORE')
# If SNMPv3 is used, a user ID and two authentication keys should be used. The script (or the Server) uses
# this information to communicate with the desired SNMP Agent to get device information.
hlapi.UsmUserData('testuser', authKey='authenticationkey', privKey='encryptionkey',
authProtocol=hlapi.usmHMACSHAAuthProtocol, privProtocol=hlapi.usmAesCfb128Protocol)
# Specifies the specific OID to obtain specified information from the SNMP Agent. '1.3.6.1.2.1.1.5.0' denotes the dot IOD for a hostname
.
print(get('192.168.47.10', ['1.3.6.1.2.1.1.5.0'], hlapi.CommunityData('ICTSHORE')))
^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos
^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
源代码:www . ictshore . com/sdn/python-SNMP-tutorial/
SNMPv3 Python 代码,用于查询思科设备信息
现在你要重申之前的 SNMPv2c Python 代码来支持 SNMPv3。通过研究其他人的代码,并在实验室环境中测试脚本,您可以更深入地了解 Python 如何与不同的应用交互和行为。这也是观察和学习 SNMPv2c 和 SNMPv3 实际区别的绝佳机会。
||
工作
|
| — | — |
| 1 | 为了让服务器(centos8s1)通过 SNMP 与网络设备通信,您将首先安装服务器的pysnmp库。使用sudo pip3 install pysnmp命令完成安装。[pynetauto@centos8s1 ~]$ sudo pip3 install pysnmp |
| 2 | 创建一个名为quicksnmp_v3.py的 SNMP Python 代码后,用 nano 编辑器打开文件,将quicksnmp.py中的所有代码行复制粘贴到quicksnmp_v3.py中。[pynetauto@centos8s1 ~]$cd snmp_test``[pynetauto@centos8s1 snmp_test]$ nano quicksnmp_v3.py |
| 3 | 要将quicksnmp.py转换成 SNMP 版本 3 脚本,需要进行以下修改。首先,当你阅读关于 pysnmplabs 的文档时。com ,你会看到需要导入并使用from pysnmp.hlapi import UsmUserData。http://snmplabs.com/pysnmp/docs/api-reference.html#pysnmp.hlapi.UsmUserDataA.在代码的顶部添加from pysnmp.hlapi import UsmUserData,如下所示:GNU nano 4.8 /home/pynetauto/snmp_test/quicksnmp_v3.py``from pysnmp import hlapi``from pysnmp.hlapi import UsmUserData``def get(target, oids, credentials, port=161, engine=hlapi.SnmpEngine(), context=hlapi.ContextData()):``handler = hlapi.getCmd(``engine,``credentials,``hlapi.UdpTransportTarget((target, port)),``context,``*construct_object_types(oids)``)``return fetch(handler, 1)[0]``def construct_object_types(list_of_oids):``object_types = []``for oid in list_of_oids:``object_types.append(hlapi.ObjectType(hlapi.ObjectIdentity(oid)))``return object_types``def fetch(handler, count):``result = []``for i in range(count):``try:``error_indication, error_status, error_index, var_binds = next(handler)``if not error_indication and not error_status:``items = {}``for var_bind in var_binds:``items[str(var_bind[0])] = cast(var_bind[1])``result.append(items)``else:``raise RuntimeError('Got SNMP error: {0}'.format(error_indication))``except StopIteration:``break``return result``def cast(value):``try:``return int(value)``except (ValueError, TypeError):``try:``return float(value)``except (ValueError, TypeError):``try:``return str(value)``except (ValueError, TypeError):``pass``return value``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To LineB.接下来,您需要输入您的用户信息、身份验证密钥和私钥。如前面的 SNMP 用户配置所示,SNMPv3 用户SNMPUser1配置了 SHA 身份验证密钥和 AES128 私钥。因此,添加如下所示的代码行:hlapi.UsmUserData('SNMPUser1', authKey="AUTHPass1", privKey="PRIVPass1", authProtocol=hlapi.usmHMACSHAAuthProtocol, privProtocol=hlapi.usmAesCfb128Protocol)C.输入print语句,以便get()函数可以传递正确的代理 IP 信息和其他凭证变量,从而在屏幕上打印信息。print(get('192.168.183.10', ['1.3.6.1.2.1.1.5.0'], hlapi.UsmUserData('SNMPUser1', authKey="AUTHPass1", privKey="PRIVPass1", authProtocol=hlapi.usmHMACSHAAuthProtocol, privProtocol=hlapi.usmAesCfb128Protocol))) |
| 4 | 在最后一行代码中使用dot OID '1.3.6.1.2.1.1.5.0'并执行前面的 Python 代码来检索和打印设备名 192.168.183.10。[pynetauto@centos8s1 snmp_test]$ python3 quicksnmp_v3.py``{'1.3.6.1.2.1.1.5.0': 'LAB-R1.pynetauto.local'} |
| 5 | 如果您想检索 GigabitEthernet0/0 的运行状态,您可以在最后一行添加另一条 OID 值为'1.3.6.1.2.1.2.2.1.7.1'的点 OID 线。当您运行更改后的脚本时,接口状态将以数字形式返回:1 表示打开,2 表示关闭。D.在quicksnmp_v3.py的末尾添加以下一行:print(get('192.168.183.10', ['1.3.6.1.2.1.2.2.1.7.1'], hlapi.UsmUserData('SNMPUser1', authKey="AUTHPass1", privKey='PRIVP$``52``[pynetauto@centos8s1 snmp_test]$ python3 quicksnmp_v3.py``{'1.3.6.1.2.1.1.5.0': 'LAB-R1.pynetauto.local'}``{'1.3.6.1.2.1.2.2.1.7.1': 1} |
| 6 | 当您增加最后一位数字并运行 GigabitEthernet 0/3 接口状态脚本时,它会返回 2。它和任何东西都没有联系。4 是接口参考号,2 表示关闭状态。[pynetauto@centos8s1 snmp_test]$ python3 quicksnmp_v3.py``{'1.3.6.1.2.1.1.5.0': 'LAB-R1.pynetauto.local'}``{'1.3.6.1.2.1.2.2.1.7.4': 2} |
使用 Python SNMP 代码运行接口描述查询
这个实验是上一个实验的延续,您首先必须复制一个quicksnmp_v3.py脚本,并创建另一个脚本来完成它。以下脚本将允许您使用 SNMPv3 Python 代码对接口及其描述运行 SNMP 查询。由于这本书是一本介绍性的书,试图介绍尽可能多的 Python 和网络自动化,所以我们不会对 SNMP 深入了解,把这个任务留给 Cisco 文档。这里重要的是学习 Python 如何使用基本的 SNMP MIB 和 OID 信息与 Cisco 设备交互。
|
工作
|
| — | — |
| 1 | 现在,使用 Linux 的cp命令,复制quicksnmp_v3.py并创建另一个名为quicksnmp_v3_get_bulk_auto.py的文件。[pynetauto@centos8s1 snmp_test]$ cp quicksnmp_v3.py quicksnmp_v3_get_bulk_auto.py``[pynetauto@centos8s1 snmp_test]$ nano quicksnmp_v3_get_bulk_auto.py |
| 2 | 在行尾,添加下面的代码,并运行它。您将在底部追加下一行代码。另外,请确保您使用的是 SW3 IP 地址 192.168.183.153。这段代码将对接口的名称和描述运行 SNMP 查询。GNU nano 4.8 /home/pynetauto/snmp_test/quicksnmp_v3_get_bulk_auto.py``[... omitted for brevity]``def cast(value):``try:``return int(value)``except (ValueError, TypeError):``try :``return float(value)``except (ValueError, TypeError):``try:``return str(value)``except (ValueError, TypeError):``pass``return value # existing quicksnmp_v3.py``#Append the following to the end of the code``def get_bulk(target, oids, credentials, count, start_from=0, port=161, engine=hlapi.SnmpEngine(), context=hlapi.ContextData()):``handler = hlapi.bulkCmd(``engine,``credentials,``hlapi.UdpTransportTarget((target, port)),``context,``start_from, count,``*construct_object_types(oids)``)``return fetch(handler, count)``def get_bulk_auto(target, oids, credentials, count_oid, start_from=0, port=161, engine=hlapi.SnmpEngine(), context=hlapi.ContextData()):``count = get(target, [count_oid], credentials, port, engine, context)[count_oid]``return get_bulk(target, oids, credentials, count, start_from, port, engine, context)``its = get_bulk_auto('192.168.183.153', ['1.3.6.1.2.1.2.2.1.2 ', '1.3.6.1.2.1.31.1.1.1.18'], hlapi.UsmUserData('SNMPUser1', authKey="AUTHPass1", privKey="PRIVPass1", authProtocol=hlapi.usmHMACSHAAuthProtocol, privProtocol=hlapi.usmAesCfb128Protocol), '1.3.6.1.2.1.2.1.0')``for it in its:``for k, v in it.items():``print("{0}={1}".format(k, v))``print('')``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line |
| 3 | 最后,运行quicksnmp_v3_get_bulk_auto.py代码,应该会得到接口名称及其描述;由于我们只为Gi0/0和vlan1配置了描述,您将会看到这些接口的名称。[pynetauto@centos8s1 snmp_test]$ python3 quicksnmp_v3_get_bulk_auto.py``{'1.3.6.1.2.1.1.5.0': 'Lab-sw3.pynetauto.com'}``1.3.6.1.2.1.2.2.1.2.1=GigabitEthernet0/0``1.3.6.1.2.1.31.1.1.1.18.1="1GB Main Connection"``1.3.6.1.2.1.2.2.1.2.2=GigabitEthernet0/1``1.3.6.1.2.1.31.1.1.1.18.2=``[... omitted for brevity]``1.3.6.1.2.1.2.2.1.2.16=GigabitEthernet3/3``1.3.6.1.2.1.31.1.1.1.18.16=``1.3.6.1.2.1.2.2.1.2.17=Null0``1.3.6.1.2.1.31.1.1.1.18.17=``1.3.6.1.2.1.2.2.1.2.18=Vlan1``1.3.6.1.2.1.31.1.1.1.18.18="Native interface" |
您可以查询和了解数百个 SNMP OID 对象。如果你有一点点的空闲时间,你可以探索更多关于思科设备 MIB 的信息,尤其是与 MIB 信息相关的 CPU 和进程。这里有一个 Cisco 链接,向您展示如何使用 SNMP 查询 CPU:
摘要
在本章中,您学习了如何在 Ubuntu 和 CentOS 上使用 Linux 调度程序cron。现在,您可以安排 Python 应用在指定时间或指定间隔运行。一旦你完成了一个应用的开发,你就可以使用cron来运行任务,即使是在一天中的奇数时间,你也不会失去任何一个美好的睡眠。工具cron是一个强大的、免费的、开源的任务调度器,只要我们可以,我们就应该依赖开源工具。您还学习了 SNMP 基础,并使用pysnmp模块查看了SNMPwalk示例;在这个过程中,我们借用了另一个开发人员的工作,这样我们就不必重新发明轮子。这可以节省你很多时间。
有了 SNMP 和 Python,我们可以做很多事情,但那将是另外一整本书。在第十六章中,你将探索更多的网络自动化场景。此外,您还将学习使用用于 Python 测试的虚拟环境virtualenv,以及 Ansible、pyATS 和 Docker、sendmail 和 Twilio SMS Python 网络自动化应用开发。
Python SSH 自动化指南
1063

被折叠的 条评论
为什么被折叠?



