├── .gitignore ├── Chapter01 ├── helloworld.py └── math_stuff │ ├── __init__.py │ └── subtract.py ├── Chapter02 ├── 2_DC_Topology.yaml ├── 2_DC_Topology_CML_2.6_Fix.yaml ├── chapter2_1.py ├── chapter2_2.py ├── chapter2_3.py ├── chapter2_4.py ├── chapter2_5.py ├── commands.txt ├── devices.json ├── hosts.yaml ├── lax-edg-r1_output.txt └── lax-edg-r2_output.txt ├── Chapter03 ├── Arista │ ├── eapi_1.py │ ├── eapi_2.py │ ├── eapi_2_acl.py │ └── pyeapi_1.py ├── Cisco │ ├── cisco_meraki_1.py │ ├── cisco_meraki_2.py │ ├── cisco_nxapi_1.py │ ├── cisco_nxapi_2.py │ ├── cisco_nxapi_3.py │ ├── cisco_nxapi_4.py │ ├── cisco_yang_1.py │ ├── cisco_yang_1_interfaces.xml │ └── nxapi_show_interface_output.txt ├── Juniper │ ├── junos_netconf_1.py │ ├── junos_netconf_2.py │ ├── junos_netconf_3.py │ ├── junos_pyez_1.py │ └── junos_pyez_2.py └── VyOS │ └── vyos_1.py ├── Chapter04 ├── Templates │ ├── file1 │ ├── file2 │ ├── hosts │ ├── nx-osv-1.conf │ ├── nx-osv-2.conf │ ├── nxos.j2 │ ├── template_1.yml │ └── template_2.yml ├── ansible.cfg ├── host_vars │ ├── iosv-1 │ ├── iosv-2 │ ├── lax-cor-r1 │ ├── lax-edg-r1 │ ├── lax-edg-r2 │ ├── nyc-cor-r1 │ ├── nyc-edg-r1 │ └── nyc-edg-r2 ├── hosts ├── hosts_full ├── ios_conditional.yml ├── ios_conditional_config.yml ├── ios_config_backup.yml ├── ios_facts_output.txt ├── ios_facts_playbook.yml ├── nxos_config_backup.yml ├── standard_loop.yml ├── standard_loop_vlan_example.yml └── standard_loop_vlan_example_2.yml ├── Chapter05 ├── ansible_container │ ├── ansible.cfg │ ├── host_vars │ │ ├── iosv-1 │ │ └── iosv-2 │ ├── hosts │ └── ios_config_backup.yml └── container_lab │ ├── .srl02.clab.yml.bak │ ├── clab-srl02 │ ├── ansible-inventory.yml │ ├── ca │ │ ├── root │ │ │ ├── root-ca.csr │ │ │ └── root-ca.pem │ │ ├── srl1 │ │ │ ├── srl1.csr │ │ │ └── srl1.pem │ │ └── srl2 │ │ │ ├── srl2.csr │ │ │ └── srl2.pem │ ├── srl1 │ │ └── topology.yml │ ├── srl2 │ │ └── topology.yml │ └── topology-data.json │ ├── srl02.clab.yml │ ├── srl1.cfg │ └── srl2.cfg ├── Chapter06 ├── 2_DC_Topology_with_Hosts.yaml ├── access_list_mac_iosv.yml ├── access_list_nxosv.yml ├── chapter6_topology.virl.backup ├── host_vars │ ├── nyc-cor-r1 │ └── nyc-edg-r1 ├── hosts ├── python_re_search_1.py ├── python_re_search_2.py ├── scapy_ping_collection.py └── scapy_tcp_scan_1.py ├── Chapter07 ├── cacti_1.py ├── cacti_2.py ├── chapter7_topology.virl.bak ├── figuer1.png ├── figure1.pdf ├── matplotlib_1.py ├── matplotlib_1_result.png ├── matplotlib_2.py ├── matplotlib_2_result.png ├── matplotlib_3.py ├── matplotlib_3_result.png ├── pygal_1.py ├── pygal_2.py ├── pygal_example_1.svg ├── pygal_example_2.svg ├── pygal_example_3.svg ├── pysnmp_1.py ├── pysnmp_2.py ├── pysnmp_3.py └── results.txt ├── Chapter08 ├── chapter8_graphviz_1.gv ├── chapter8_gv_1.gv ├── chapter8_gv_2.gv ├── chapter8_ntop_1.py ├── chapter8_ntop_2.py ├── chapter8_sflowtool_1.py ├── chapter8_topology.virl ├── chapter8_topology.yaml ├── cisco_config_lldp.yml ├── cisco_config_netflow.yml ├── cisco_discover_lldp.yml ├── cisco_graph_lldp.py ├── host_vars │ ├── r1 │ ├── r2 │ ├── r3 │ ├── r4 │ ├── r5-tor │ └── r6-edge ├── hosts ├── netFlow_v5_parser.py ├── output │ ├── chapter8_gv_1.png │ ├── chapter8_gv_2.pdf │ ├── chapter8_gv_3.gv │ ├── chapter8_gv_3.gv.pdf │ ├── chapter8_lldp_graph.gv │ ├── chapter8_lldp_graph.gv.pdf │ ├── chapter8_lldp_graph.gv_neato.pdf │ └── chapter8_lldp_graph.gv_v1.pdf └── tmp │ ├── r1_lldp_output.txt │ ├── r2_lldp_output.txt │ ├── r3_lldp_output.txt │ ├── r5-tor_lldp_output.txt │ └── r6-edge_lldp_output.txt ├── Chapter09 ├── TestApp │ ├── app │ │ ├── __init__.py │ │ └── network.db │ ├── main.py │ ├── requirements.txt │ ├── start.sh │ └── uwsgi.ini ├── chapter9_1.py ├── chapter9_2.py ├── chapter9_3.py ├── chapter9_4.py ├── chapter9_5.py ├── chapter9_6.py ├── chapter9_7.py ├── chapter9_8.py ├── chapter9_9.py ├── chapter9_db_1.py ├── chapter9_pexpect_1.py ├── chapter9_request_1.py ├── chapter9_topology.virl └── requirements.txt ├── Chapter10 ├── async_count.py ├── asyncio_1.py ├── multiprocess_1.py ├── scrapli │ ├── scrapli_example_1.py │ ├── scrapli_example_2.py │ ├── scrapli_example_3_async.py │ └── scrapli_example_3_sync.py ├── sync_count.py └── threading_1.py ├── Chapter11 ├── Chapter11_1_query_vpc.py ├── Chapter11_2_query_route_tables.py ├── Chapter11_3_cloud_formation.yml ├── Chapter11_4_cloud_formation_full.yml └── Chapter11_5_security_group.py ├── Chapter12 ├── Chapter12_1_auth.py ├── Chapter12_2_subnet.py └── Chapter12_3_vnet.py ├── Chapter13 ├── Chapter13_1.py ├── Chapter13_2.py ├── Chapter13_3.py ├── Chapter13_4.py ├── Chapter13_5.py ├── Chapter13_6.py ├── chapter13_topology.virl.backup ├── network_configs │ └── simple_config.conf ├── query_body_1.json ├── query_body_2.json ├── query_body_3.json ├── query_body_4.json └── query_body_5.json ├── Chapter14 └── Chapter14_1.py ├── Chapter16 ├── chapter15_topology.virl ├── chapter16_10_ping.py ├── chapter16_1_xml.py ├── chapter16_2_validation.py ├── chapter16_3_test_fail.py ├── chapter16_4_unittest.py ├── chapter16_5_more_unittest.py ├── chapter16_5_more_unittest_mocks.py ├── chapter16_6_pytest_1.py ├── chapter16_7_pytest_2.py ├── chapter16_8_pytest_3.py ├── chapter16_9_pytest_4.py ├── chapter16_topology.yaml ├── pyATS │ ├── chapter16_11_pyats_1.py │ ├── chapter16_12_pyats_2.py │ ├── chapter16_pyats_testbed_1.yml │ └── chapter16_pyats_testbed_2.yml └── result.xml ├── Errata.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # echou common 35 | credentials* 36 | config* 37 | *insecure* 38 | src_env 39 | secrets* 40 | id_rsa 41 | *key* 42 | 43 | # Docker 44 | Dockerfile 45 | docker-compose.yml 46 | 47 | # Files that might appear in the root of a volume 48 | .DocumentRevisions-V100 49 | .fseventsd 50 | .Spotlight-V100 51 | .TemporaryItems 52 | .Trashes 53 | .VolumeIcon.icns 54 | 55 | # Directories potentially created on remote AFP share 56 | .AppleDB 57 | .AppleDesktop 58 | Network Trash Folder 59 | Temporary Items 60 | .apdisk 61 | 62 | # Ansible gitignore 63 | *.retry 64 | 65 | # Python gitignore 66 | 67 | # Byte-compiled / optimized / DLL files 68 | __pycache__/ 69 | *.py[cod] 70 | *$py.class 71 | 72 | # C extensions 73 | *.so 74 | 75 | # Distribution / packaging 76 | .Python 77 | env/ 78 | build/ 79 | develop-eggs/ 80 | dist/ 81 | downloads/ 82 | eggs/ 83 | .eggs/ 84 | lib/ 85 | lib64/ 86 | parts/ 87 | sdist/ 88 | var/ 89 | wheels/ 90 | *.egg-info/ 91 | .installed.cfg 92 | *.egg 93 | 94 | # PyInstaller 95 | # Usually these files are written by a python script from a template 96 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 97 | *.manifest 98 | *.spec 99 | 100 | # Installer logs 101 | pip-log.txt 102 | pip-delete-this-directory.txt 103 | 104 | # Unit test / coverage reports 105 | htmlcov/ 106 | .tox/ 107 | .coverage 108 | .coverage.* 109 | .cache 110 | nosetests.xml 111 | coverage.xml 112 | *,cover 113 | .hypothesis/ 114 | 115 | # Translations 116 | *.mo 117 | *.pot 118 | 119 | # Django stuff: 120 | *.log 121 | local_settings.py 122 | 123 | # Flask stuff: 124 | instance/ 125 | .webassets-cache 126 | 127 | # Scrapy stuff: 128 | .scrapy 129 | 130 | # Sphinx documentation 131 | docs/_build/ 132 | 133 | # PyBuilder 134 | target/ 135 | 136 | # Jupyter Notebook 137 | .ipynb_checkpoints 138 | 139 | # pyenv 140 | .python-version 141 | 142 | # celery beat schedule file 143 | celerybeat-schedule 144 | 145 | # SageMath parsed files 146 | *.sage.py 147 | 148 | # dotenv 149 | .env 150 | 151 | # virtualenv 152 | .venv 153 | venv/ 154 | ENV/ 155 | 156 | # Spyder project settings 157 | .spyderproject 158 | 159 | # Rope project settings 160 | .ropeproject 161 | 162 | # IDE 163 | .vscode 164 | 165 | # Windows image file caches 166 | Thumbs.db 167 | ehthumbs.db 168 | 169 | # Folder config file 170 | Desktop.ini 171 | 172 | # Recycle Bin used on file shares 173 | $RECYCLE.BIN/ 174 | 175 | # Windows Installer files 176 | *.cab 177 | *.msi 178 | *.msm 179 | *.msp 180 | 181 | # Windows shortcuts 182 | *.lnk 183 | 184 | # ========================= 185 | # Operating System Files 186 | # ========================= 187 | 188 | # OSX 189 | # ========================= 190 | 191 | .DS_Store 192 | .AppleDouble 193 | .LSOverride 194 | 195 | # Thumbnails 196 | ._* 197 | 198 | # echou common 199 | credentials* 200 | config* 201 | *insecure* 202 | src_env 203 | secrets* 204 | id_rsa 205 | *key* 206 | 207 | # Docker 208 | Dockerfile 209 | docker-compose.yml 210 | 211 | # Files that might appear in the root of a volume 212 | .DocumentRevisions-V100 213 | .fseventsd 214 | .Spotlight-V100 215 | .TemporaryItems 216 | .Trashes 217 | .VolumeIcon.icns 218 | 219 | # Directories potentially created on remote AFP share 220 | .AppleDB 221 | .AppleDesktop 222 | Network Trash Folder 223 | Temporary Items 224 | .apdisk 225 | 226 | # Ansible gitignore 227 | *.retry 228 | 229 | # Python gitignore 230 | 231 | # Byte-compiled / optimized / DLL files 232 | __pycache__/ 233 | *.py[cod] 234 | *$py.class 235 | 236 | # C extensions 237 | *.so 238 | 239 | # Distribution / packaging 240 | .Python 241 | env/ 242 | build/ 243 | develop-eggs/ 244 | dist/ 245 | downloads/ 246 | eggs/ 247 | .eggs/ 248 | lib/ 249 | lib64/ 250 | parts/ 251 | sdist/ 252 | var/ 253 | wheels/ 254 | *.egg-info/ 255 | .installed.cfg 256 | *.egg 257 | 258 | # PyInstaller 259 | # Usually these files are written by a python script from a template 260 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 261 | *.manifest 262 | *.spec 263 | 264 | # Installer logs 265 | pip-log.txt 266 | pip-delete-this-directory.txt 267 | 268 | # Unit test / coverage reports 269 | htmlcov/ 270 | .tox/ 271 | .coverage 272 | .coverage.* 273 | .cache 274 | nosetests.xml 275 | coverage.xml 276 | *,cover 277 | .hypothesis/ 278 | 279 | # Translations 280 | *.mo 281 | *.pot 282 | 283 | # Django stuff: 284 | *.log 285 | local_settings.py 286 | 287 | # Flask stuff: 288 | instance/ 289 | .webassets-cache 290 | 291 | # Scrapy stuff: 292 | .scrapy 293 | 294 | # Sphinx documentation 295 | docs/_build/ 296 | 297 | # PyBuilder 298 | target/ 299 | 300 | # Jupyter Notebook 301 | .ipynb_checkpoints 302 | 303 | # pyenv 304 | .python-version 305 | 306 | # celery beat schedule file 307 | celerybeat-schedule 308 | 309 | # SageMath parsed files 310 | *.sage.py 311 | 312 | # dotenv 313 | .env 314 | 315 | # virtualenv 316 | .venv 317 | venv/ 318 | ENV/ 319 | 320 | # Spyder project settings 321 | .spyderproject 322 | 323 | # Rope project settings 324 | .ropeproject 325 | 326 | # IDE 327 | .vscode 328 | 329 | -------------------------------------------------------------------------------- /Chapter01/helloworld.py: -------------------------------------------------------------------------------- 1 | # This is a comment 2 | print("hello world") 3 | 4 | -------------------------------------------------------------------------------- /Chapter01/math_stuff/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter01/math_stuff/__init__.py -------------------------------------------------------------------------------- /Chapter01/math_stuff/subtract.py: -------------------------------------------------------------------------------- 1 | def subtract(a, b): 2 | c = a - b 3 | return c 4 | 5 | -------------------------------------------------------------------------------- /Chapter02/chapter2_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pexpect 4 | 5 | devices = {'iosv-1': {'prompt': 'lax-edg-r1#', 'ip': '192.168.2.51'}, 6 | 'iosv-2': {'prompt': 'lax-edg-r2#', 'ip': '192.168.2.52'}} 7 | username = 'cisco' 8 | password = 'cisco' 9 | 10 | for device in devices.keys(): 11 | device_prompt = devices[device]['prompt'] 12 | child = pexpect.spawn('telnet ' + devices[device]['ip']) 13 | child.expect('Username:') 14 | child.sendline(username) 15 | child.expect('Password:') 16 | child.sendline(password) 17 | child.expect(device_prompt) 18 | child.sendline('show version | i V') 19 | child.expect(device_prompt) 20 | print(child.before) 21 | child.sendline('exit') 22 | 23 | -------------------------------------------------------------------------------- /Chapter02/chapter2_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import getpass 4 | from pexpect import pxssh 5 | 6 | devices = {'lax-edg-r1': {'prompt': 'lax-edg-r1#', 'ip': '192.168.2.51'}, 7 | 'lax-edg-r2': {'prompt': 'lax-edg-r2#', 'ip': '192.168.2.52'}} 8 | commands = ['term length 0', 'show version', 'show run'] 9 | 10 | username = input('Username: ') 11 | password = getpass.getpass('Password: ') 12 | 13 | # Starts the loop for devices 14 | for device in devices.keys(): 15 | outputFileName = device + '_output.txt' 16 | device_prompt = devices[device]['prompt'] 17 | child = pxssh.pxssh() 18 | child.login(devices[device]['ip'], username.strip(), password.strip(), auto_prompt_reset=False) 19 | # Starts the loop for commands and write to output 20 | with open(outputFileName, 'wb') as f: 21 | for command in commands: 22 | child.sendline(command) 23 | child.expect(device_prompt) 24 | f.write(child.before) 25 | 26 | child.logout() 27 | 28 | -------------------------------------------------------------------------------- /Chapter02/chapter2_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import paramiko, getpass, time 4 | 5 | devices = {'lax-edg-r1': {'ip': '192.168.2.51'}, 6 | 'lax-edg-r2': {'ip': '192.168.2.52'}} 7 | commands = ['show version\n', 'show run\n'] 8 | 9 | username = input('Username: ') 10 | password = getpass.getpass('Password: ') 11 | 12 | max_buffer = 65535 13 | 14 | def clear_buffer(connection): 15 | if connection.recv_ready(): 16 | return connection.recv(max_buffer) 17 | 18 | # Starts the loop for devices 19 | for device in devices.keys(): 20 | outputFileName = device + '_output.txt' 21 | connection = paramiko.SSHClient() 22 | connection.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 23 | connection.connect(devices[device]['ip'], username=username, password=password, look_for_keys=False, allow_agent=False) 24 | new_connection = connection.invoke_shell() 25 | output = clear_buffer(new_connection) 26 | time.sleep(5) 27 | new_connection.send("terminal length 0\n") 28 | output = clear_buffer(new_connection) 29 | with open(outputFileName, 'wb') as f: 30 | for command in commands: 31 | new_connection.send(command) 32 | time.sleep(5) 33 | output = new_connection.recv(max_buffer) 34 | print(output) 35 | f.write(output) 36 | 37 | new_connection.close() 38 | -------------------------------------------------------------------------------- /Chapter02/chapter2_4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import paramiko, getpass, time, json 4 | 5 | with open('devices.json', 'r') as f: 6 | devices = json.load(f) 7 | 8 | with open('commands.txt', 'r') as f: 9 | commands = f.readlines() 10 | 11 | username = input('Username: ') 12 | password = getpass.getpass('Password: ') 13 | 14 | max_buffer = 65535 15 | 16 | def clear_buffer(connection): 17 | if connection.recv_ready(): 18 | return connection.recv(max_buffer) 19 | 20 | # Starts the loop for devices 21 | for device in devices.keys(): 22 | outputFileName = device + '_output.txt' 23 | connection = paramiko.SSHClient() 24 | connection.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 25 | connection.connect(devices[device]['ip'], username=username, password=password, look_for_keys=False, allow_agent=False) 26 | new_connection = connection.invoke_shell() 27 | output = clear_buffer(new_connection) 28 | time.sleep(2) 29 | new_connection.send("terminal length 0\n") 30 | output = clear_buffer(new_connection) 31 | with open(outputFileName, 'wb') as f: 32 | for command in commands: 33 | new_connection.send(command) 34 | time.sleep(2) 35 | output = new_connection.recv(max_buffer) 36 | print(output) 37 | f.write(output) 38 | 39 | new_connection.close() 40 | -------------------------------------------------------------------------------- /Chapter02/chapter2_5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from nornir import InitNornir 4 | from nornir_utils.plugins.functions import print_result 5 | from nornir_netmiko import netmiko_send_command 6 | 7 | 8 | nr = InitNornir() 9 | 10 | result = nr.run( 11 | task=netmiko_send_command, 12 | command_string="show arp" 13 | ) 14 | 15 | print_result(result) 16 | -------------------------------------------------------------------------------- /Chapter02/commands.txt: -------------------------------------------------------------------------------- 1 | config t 2 | logging buffered 30000 3 | end 4 | copy run start -------------------------------------------------------------------------------- /Chapter02/devices.json: -------------------------------------------------------------------------------- 1 | { 2 | "lax-edg-r1": { 3 | "ip": "192.168.2.51" 4 | }, 5 | "lax-edg-r2": { 6 | "ip": "192.168.2.52" 7 | } 8 | } -------------------------------------------------------------------------------- /Chapter02/hosts.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | lax-edg-r1: 3 | hostname: '192.168.2.51' 4 | port: 22 5 | username: 'cisco' 6 | password: 'cisco' 7 | platform: 'cisco_ios' 8 | 9 | lax-edg-r2: 10 | hostname: '192.168.2.52' 11 | port: 22 12 | username: 'cisco' 13 | password: 'cisco' 14 | platform: 'cisco_ios' -------------------------------------------------------------------------------- /Chapter02/lax-edg-r1_output.txt: -------------------------------------------------------------------------------- 1 | terminal length 0 2 | lax-edg-r1#config t 3 | Enter configuration commands, one per line. End with CNTL/Z. 4 | lax-edg-r1(config)#logging buffered 30000 5 | lax-edg-r1(config)#end 6 | lax-edg-r1#copy run start -------------------------------------------------------------------------------- /Chapter02/lax-edg-r2_output.txt: -------------------------------------------------------------------------------- 1 | terminal length 0 2 | lax-edg-r2#config t 3 | Enter configuration commands, one per line. End with CNTL/Z. 4 | lax-edg-r2(config)#logging buffered 30000 5 | lax-edg-r2(config)#end 6 | lax-edg-r2#copy run start -------------------------------------------------------------------------------- /Chapter03/Arista/eapi_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | 3 | from __future__ import print_function 4 | from jsonrpclib import Server 5 | import ssl 6 | 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | 9 | switch = Server("https://admin:arista@192.168.199.158/command-api") 10 | 11 | response = switch.runCmds( 1, [ "show version" ] ) 12 | print('Serial Number: ' + response[0]['serialNumber']) 13 | -------------------------------------------------------------------------------- /Chapter03/Arista/eapi_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | 3 | from __future__ import print_function 4 | from jsonrpclib import Server 5 | import ssl, pprint 6 | 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | 9 | # Run Arista commands thru eAPI 10 | def runAristaCommands(switch_object, list_of_commands): 11 | response = switch_object.runCmds(1, list_of_commands) 12 | return response 13 | 14 | 15 | switch = Server("https://admin:arista@192.168.199.158/command-api") 16 | 17 | commands = ["enable", "configure", "interface ethernet 1/3", "switchport access vlan 100", "end", "write memory"] 18 | 19 | response = runAristaCommands(switch, commands) 20 | pprint.pprint(response) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter03/Arista/eapi_2_acl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2014 Arista Networks 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | 22 | 23 | import argparse 24 | import jsonrpclib 25 | import os 26 | import sys 27 | import subprocess 28 | 29 | import ssl 30 | ssl._create_default_https_context = ssl._create_unverified_context 31 | 32 | # EAPI script to remotely edit an access list across multiple 33 | # Arista switches using your editor of choice. 34 | 35 | # From a central server with IP connectivity to your switch, run this 36 | # script and specify an ACL name and a series of switches you are 37 | # interested in editing. This then opens your $EDITOR (e.g. vi or emacs) 38 | # with the contents of the named ACL. When you're finished, close the file 39 | # and this script will update that ACL across all of the switches you specified. 40 | # No more dealing with annoying line numbers in the CLI! 41 | 42 | def main(): 43 | parser = argparse.ArgumentParser(description="Edit Arista ACLs using your local editor") 44 | parser.add_argument("acl", metavar="ACL", 45 | help="Name of the access list to modify") 46 | parser.add_argument("switches", metavar="SWITCH", nargs="+", 47 | help="Hostname or IP of the switch to query") 48 | parser.add_argument("--username", help="Name of the user to connect as", 49 | default="admin") 50 | parser.add_argument("--password", help="The user's password") 51 | parser.add_argument("--https", help="Use HTTPS instead of HTTP", 52 | action="store_const", const="https", default="http") 53 | args = parser.parse_args() 54 | 55 | aclName = args.acl 56 | tmpfile = "/tmp/AclEditor-%s" % aclName 57 | apiEndpoints = getEndpoints(args.switches, args.https, 58 | args.username, args.password) 59 | prepopulateAclFile(tmpfile, aclName, apiEndpoints) 60 | edits = getEdits(tmpfile) 61 | applyChanges(aclName, apiEndpoints, edits) 62 | print 63 | print "Done!" 64 | 65 | def getEndpoints(switchHostnames, protocol, username, password): 66 | """ Check that each server is up, and return a mapping from 67 | hostname to jsonrpclib.Server """ 68 | apiEndpoints = {} # mapping from hostname to the API endpoint 69 | for switch in switchHostnames: 70 | url = "{protocol}://{user}:{pw}@{hostname}/command-api".format( 71 | protocol=protocol, user=username, pw=password, hostname=switch) 72 | server = jsonrpclib.Server(url) 73 | try: 74 | # We should at least be able to 'enable' 75 | server.runCmds(1, ["enable"]) 76 | except Exception as e: 77 | print "Unable to run 'enable' on switch", e 78 | sys.exit(1) 79 | apiEndpoints[switch] = server 80 | return apiEndpoints 81 | 82 | def prepopulateAclFile(filename, aclName, apiEndpoints): 83 | """ Given a jsonrpclib.Server called 'switch', prepopulate 84 | 'filename' with the ACL contents. If the ACL does not yet exist, 85 | just print a message """ 86 | 87 | # Currently assume all switches have the same config, so just use a 88 | # random one as the sample. 89 | apiEndpoint = apiEndpoints.itervalues().next() 90 | responseList = apiEndpoint.runCmds(1, ["enable", 91 | "show ip access-lists %s" % aclName]) 92 | response = responseList[1] # Only care about the ACL output. 93 | if not response["aclList"]: 94 | print "No existing access list named", aclName, "- creating new ACL" 95 | else: 96 | # Prepopulate the file with the existing config 97 | print "Editing existing access list:" 98 | with open(filename, "w") as f: 99 | for rule in response["aclList"][0]["sequence"]: 100 | line = str(rule["sequenceNumber"]) + " " + rule["text"] + "\n" 101 | print " ", line, 102 | f.write(line) 103 | print 104 | 105 | def getEdits(filename): 106 | """ Opens an editor for the user to edit the ACL, and returns a 107 | list of the new ACL contents """ 108 | editor = os.environ.get("EDITOR", "vi") # default editor is "vi" 109 | ret = subprocess.Popen([editor, filename]).wait() 110 | if ret != 0: 111 | print "Bad editor exit. Aborting." 112 | sys.exit(1) 113 | # Read in the file as a list of lines 114 | aclContents = open(filename, "r").readlines() 115 | print "New access list:" 116 | print " ", " ".join(aclContents) 117 | print 118 | return aclContents 119 | 120 | def applyChanges(aclName, apiEndpoints, aclRules): 121 | """ Given the switch mapping and a list of the new ACL rules, apply 122 | the ACL to each switch """ 123 | cmdList = ["enable", 124 | "configure", 125 | # Not the most efficient way to clear an ACL: 126 | "no ip access-list %s" % aclName, 127 | # Now enter configuration mode for the ACL: 128 | "ip access-list %s" % aclName] 129 | cmdList = cmdList + aclRules + ["exit"] 130 | 131 | for hostname, apiEndpoint in apiEndpoints.iteritems(): 132 | print "Updating access list on switch", hostname, "....", 133 | try: 134 | apiEndpoint.runCmds(1, cmdList) 135 | except jsonrpclib.ProtocolError as e: 136 | print "[ERROR]" 137 | print " ", e 138 | # jsonrpclib isn't very friendly at getting the error data as 139 | # specified by the spec. This is a shortcut for getting the 140 | # last error: 141 | errorResponse = jsonrpclib.loads(jsonrpclib.history.response) 142 | print " Details:", errorResponse["error"]["data"][-1]["errors"] 143 | else: 144 | print "[SUCCESS]" 145 | 146 | if __name__ == "__main__": 147 | main() 148 | 149 | -------------------------------------------------------------------------------- /Chapter03/Arista/pyeapi_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pyeapi 4 | 5 | class my_switch(): 6 | 7 | def __init__(self, config_file_location, device): 8 | # loads the config file 9 | pyeapi.client.load_config(config_file_location) 10 | self.node = pyeapi.connect_to(device) 11 | self.hostname = self.node.enable('show hostname')[0]['result']['hostname'] 12 | self.running_config = self.node.enable('show running-config') 13 | 14 | def create_vlan(self, vlan_number, vlan_name): 15 | vlans = self.node.api('vlans') 16 | vlans.create(vlan_number) 17 | vlans.set_name(vlan_number, vlan_name) 18 | 19 | -------------------------------------------------------------------------------- /Chapter03/Cisco/cisco_meraki_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import requests 3 | import pprint 4 | 5 | myheaders={'X-Cisco-Meraki-API-Key': '6bec40cf957de430a6f1f2baa056b99a4fac9ea0'} 6 | url = 'https://dashboard.meraki.com/api/v0/organizations' 7 | response = requests.get(url, headers=myheaders, verify=False) 8 | pprint.pprint(response.json()) 9 | 10 | 11 | -------------------------------------------------------------------------------- /Chapter03/Cisco/cisco_meraki_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import requests 3 | import pprint 4 | 5 | myheaders={'X-Cisco-Meraki-API-Key': '6bec40cf957de430a6f1f2baa056b99a4fac9ea0'} 6 | orgId = '549236' 7 | url = 'https://dashboard.meraki.com/api/v0/organizations/' + orgId + '/networks' 8 | response = requests.get(url, headers=myheaders, verify=False) 9 | pprint.pprint(response.json()) 10 | -------------------------------------------------------------------------------- /Chapter03/Cisco/cisco_nxapi_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from ncclient import manager 4 | 5 | conn = manager.connect( 6 | host='192.168.2.50', 7 | port=22, 8 | username='cisco', 9 | password='cisco', 10 | hostkey_verify=False, 11 | device_params={'name': 'nexus'}, 12 | look_for_keys=False 13 | ) 14 | 15 | for value in conn.server_capabilities: 16 | print(value) 17 | 18 | conn.close_session() 19 | -------------------------------------------------------------------------------- /Chapter03/Cisco/cisco_nxapi_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import requests 4 | import json 5 | import pprint 6 | 7 | url='http://192.168.2.50/ins' 8 | switchuser='cisco' 9 | switchpassword='cisco' 10 | 11 | myheaders={'content-type':'application/json-rpc'} 12 | payload=[ 13 | { 14 | "jsonrpc": "2.0", 15 | "method": "cli", 16 | "params": { 17 | "cmd": "show version", 18 | "version": 1.2 19 | }, 20 | "id": 1 21 | } 22 | ] 23 | response = requests.post(url,data=json.dumps(payload), headers=myheaders,auth=(switchuser,switchpassword)).json() 24 | 25 | print(response['result']['body']['sys_ver_str']) 26 | 27 | -------------------------------------------------------------------------------- /Chapter03/Cisco/cisco_nxapi_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import requests 4 | import json 5 | 6 | url='http://192.168.2.50/ins' 7 | switchuser='cisco' 8 | switchpassword='cisco' 9 | 10 | myheaders={'content-type':'application/json-rpc'} 11 | payload=[ 12 | { 13 | "jsonrpc": "2.0", 14 | "method": "cli", 15 | "params": { 16 | "cmd": "hostname lax-cor-r1-new", 17 | "version": 1.2 18 | }, 19 | "id": 1 20 | } 21 | ] 22 | response = requests.post(url,data=json.dumps(payload), headers=myheaders,auth=(switchuser,switchpassword)).json() 23 | -------------------------------------------------------------------------------- /Chapter03/Cisco/cisco_nxapi_4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import requests 4 | import json 5 | 6 | url='http://192.168.2.50/ins' 7 | switchuser='cisco' 8 | switchpassword='cisco' 9 | 10 | myheaders={'content-type':'application/json-rpc'} 11 | payload=[ 12 | { 13 | "jsonrpc": "2.0", 14 | "method": "cli", 15 | "params": { 16 | "cmd": "interface ethernet 2/12", 17 | "version": 1.2 18 | }, 19 | "id": 1 20 | }, 21 | { 22 | "jsonrpc": "2.0", 23 | "method": "cli", 24 | "params": { 25 | "cmd": "description foo-bar", 26 | "version": 1.2 27 | }, 28 | "id": 2 29 | }, 30 | { 31 | "jsonrpc": "2.0", 32 | "method": "cli", 33 | "params": { 34 | "cmd": "end", 35 | "version": 1.2 36 | }, 37 | "id": 3 38 | }, 39 | { 40 | "jsonrpc": "2.0", 41 | "method": "cli", 42 | "params": { 43 | "cmd": "copy run start", 44 | "version": 1.2 45 | }, 46 | "id": 4 47 | } 48 | ] 49 | 50 | response = requests.post(url,data=json.dumps(payload), headers=myheaders,auth=(switchuser,switchpassword)).json() 51 | 52 | -------------------------------------------------------------------------------- /Chapter03/Cisco/cisco_yang_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from ncclient import manager 4 | import xml.dom.minidom 5 | 6 | host = "sandbox-iosxe-recomm-1.cisco.com" 7 | username = "developer" 8 | password = "C1sco12345" 9 | port = 830 10 | 11 | yang_file = "cisco_yang_1_interfaces.xml" 12 | 13 | conn = manager.connect(host=host, port=port, username=username, password=password, hostkey_verify=False, device_params={'name': 'default'}, allow_agent=False, look_for_keys=False) 14 | 15 | with open(yang_file) as f: 16 | output = (conn.get_config('running', f.read())) 17 | 18 | print(xml.dom.minidom.parseString(output.xml).toprettyxml()) 19 | -------------------------------------------------------------------------------- /Chapter03/Cisco/cisco_yang_1_interfaces.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Chapter03/Juniper/junos_netconf_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from ncclient import manager 4 | 5 | conn = manager.connect( 6 | host='192.168.2.70', 7 | port='830', 8 | username='juniper', 9 | password='juniper!', 10 | timeout=10, 11 | device_params={'name':'junos'}, 12 | hostkey_verify=False) 13 | 14 | result = conn.command('show version', format='text') 15 | print(result.xpath('output')[0].text) 16 | conn.close_session() 17 | -------------------------------------------------------------------------------- /Chapter03/Juniper/junos_netconf_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from ncclient import manager 4 | from ncclient.xml_ import new_ele, sub_ele 5 | 6 | conn = manager.connect(host='192.168.2.70', port='830', username='juniper', password='juniper!', timeout=10, device_params={'name':'junos'}, hostkey_verify=False) 7 | 8 | # lock configuration and make configuration changes 9 | conn.lock() 10 | 11 | # build configuration 12 | config = new_ele('system') 13 | sub_ele(config, 'host-name').text = 'master' 14 | sub_ele(config, 'domain-name').text = 'python' 15 | 16 | # send, validate, and commit config 17 | conn.load_configuration(config=config) 18 | conn.validate() 19 | commit_config = conn.commit() 20 | print(commit_config.tostring) 21 | 22 | # unlock config 23 | conn.unlock() 24 | 25 | # close session 26 | conn.close_session() 27 | 28 | 29 | -------------------------------------------------------------------------------- /Chapter03/Juniper/junos_netconf_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from ncclient import manager 4 | from ncclient.xml_ import new_ele, sub_ele 5 | 6 | # make a connection object 7 | def connect(host, port, user, password): 8 | connection = manager.connect(host=host, port=port, username=user, 9 | password=password, timeout=10, device_params={'name':'junos'}, 10 | hostkey_verify=False) 11 | return connection 12 | 13 | # execute show commands 14 | def show_cmds(conn, cmd): 15 | result = conn.command(cmd, format='text') 16 | return result 17 | 18 | # push out configuration 19 | def config_cmds(conn, config): 20 | conn.lock() 21 | conn.load_configuration(config=config) 22 | commit_config = conn.commit() 23 | return commit_config.tostring 24 | 25 | 26 | if __name__ == '__main__': 27 | conn = connect('192.168.2.70', '830', 'juniper', 'juniper!') 28 | result = show_cmds(conn, 'show version') 29 | print('show version: ' + str(result)) 30 | new_config = new_ele('system') 31 | sub_ele(new_config, 'host-name').text = 'foo' 32 | sub_ele(new_config, 'domain-name').text = 'bar' 33 | result = config_cmds(conn, new_config) 34 | print('change id: ' + str(result)) 35 | conn.close_session() 36 | 37 | 38 | -------------------------------------------------------------------------------- /Chapter03/Juniper/junos_pyez_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from jnpr.junos import Device 3 | import xml.etree.ElementTree as ET 4 | import pprint 5 | 6 | dev = Device(host='192.168.2.70', user='juniper', passwd='juniper!') 7 | 8 | try: 9 | dev.open() 10 | except Exception as err: 11 | print(err) 12 | sys.exit(1) 13 | 14 | result = dev.rpc.get_interface_information(interface_name='em1', terse=True) 15 | pprint.pprint(ET.tostring(result)) 16 | 17 | dev.close() 18 | 19 | -------------------------------------------------------------------------------- /Chapter03/Juniper/junos_pyez_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from jnpr.junos import Device 3 | from jnpr.junos.utils.config import Config 4 | 5 | dev = Device(host='192.168.2.70', user='juniper', passwd='juniper!') 6 | 7 | try: 8 | dev.open() 9 | except Exception as err: 10 | print(err) 11 | sys.exit(1) 12 | 13 | config_change = """ 14 | 15 | master 16 | python 17 | 18 | """ 19 | 20 | cu = Config(dev) 21 | cu.lock() 22 | cu.load(config_change) 23 | cu.commit() 24 | cu.unlock() 25 | 26 | dev.close() 27 | 28 | -------------------------------------------------------------------------------- /Chapter03/VyOS/vyos_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import vymgmt 4 | 5 | vyos = vymgmt.Router('192.168.2.116', 'vyos', password='vyos') 6 | vyos.login() 7 | vyos.configure() 8 | vyos.set("system domain-name networkautomationnerds.net") 9 | vyos.commit() 10 | vyos.save() 11 | vyos.exit() 12 | vyos.logout() 13 | exit() 14 | 15 | -------------------------------------------------------------------------------- /Chapter04/Templates/file1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter04/Templates/file1 -------------------------------------------------------------------------------- /Chapter04/Templates/file2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter04/Templates/file2 -------------------------------------------------------------------------------- /Chapter04/Templates/hosts: -------------------------------------------------------------------------------- 1 | [lax_cor_devices] 2 | lax-cor-r1 3 | 4 | [lax_edg_devices] 5 | lax-edg-r1 6 | lax-edg-r2 7 | 8 | [nyc_cor_devices] 9 | nyc-cor-r1 10 | 11 | [nyc_edg_devices] 12 | nyc-edg-r1 13 | nyc-edg-r2 14 | 15 | [lax_dc:children] 16 | lax_cor_devices 17 | lax_edg_devices 18 | 19 | [nyc_dc:children] 20 | nyc_cor_devices 21 | nyc_edg_devices 22 | 23 | [ios_devices:children] 24 | lax_edg_devices 25 | nyc_edg_devices 26 | 27 | [nxos_devices:children] 28 | nyc_cor_devices 29 | lax_cor_devices 30 | -------------------------------------------------------------------------------- /Chapter04/Templates/nx-osv-1.conf: -------------------------------------------------------------------------------- 1 | hostname nx-osv-1 2 | 3 | feature telnet 4 | feature ospf 5 | feature bgp 6 | feature interface-vlan 7 | 8 | feature netflow 9 | 10 | username cisco password cisco role network-operator 11 | 12 | vlan 100 13 | vlan 200 14 | vlan 300 15 | 16 | interface 100 17 | ip address 192.168.10.1/24 18 | interface 200 19 | ip address 192.168.20.1/24 20 | interface 300 21 | ip address 192.168.30.1/24 22 | -------------------------------------------------------------------------------- /Chapter04/Templates/nx-osv-2.conf: -------------------------------------------------------------------------------- 1 | hostname nx-osv-2 2 | 3 | feature telnet 4 | feature ospf 5 | feature bgp 6 | feature interface-vlan 7 | 8 | 9 | username cisco password cisco role network-operator 10 | 11 | vlan 100 12 | vlan 200 13 | vlan 300 14 | 15 | -------------------------------------------------------------------------------- /Chapter04/Templates/nxos.j2: -------------------------------------------------------------------------------- 1 | hostname {{ item.value.hostname }} 2 | 3 | feature telnet 4 | feature ospf 5 | feature bgp 6 | feature interface-vlan 7 | 8 | {% if item.value.netflow_enable %} 9 | feature netflow 10 | {% endif %} 11 | 12 | username {{ item.value.username }} password {{ item.value.password }} role network-operator 13 | 14 | {% for vlan_num in item.value.vlans %} 15 | vlan {{ vlan_num }} 16 | {% endfor %} 17 | 18 | {% if item.value.l3_vlan_interfaces %} 19 | {% for vlan_interface in item.value.vlan_interfaces %} 20 | interface {{ vlan_interface.int_num }} 21 | ip address {{ vlan_interface.ip }}/24 22 | {% endfor %} 23 | {% endif %} -------------------------------------------------------------------------------- /Chapter04/Templates/template_1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Template Basic 3 | hosts: localhost 4 | 5 | tasks: 6 | - name: copy one file to another 7 | template: 8 | src=/home/echou/Mastering_Python_Networking_Fourth_Edition/Chapter04/Templates/file1 9 | dest=/home/echou/Mastering_Python_Networking_Fourth_Edition/Chapter04/Templates/file2 -------------------------------------------------------------------------------- /Chapter04/Templates/template_2.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Template Looping 3 | hosts: localhost 4 | 5 | vars: 6 | nexus_devices: { 7 | "nx-osv-1": { 8 | "hostname": "nx-osv-1", 9 | "username": "cisco", 10 | "password": "cisco", 11 | "vlans": [100, 200, 300], 12 | "l3_vlan_interfaces": True, 13 | "vlan_interfaces": [ 14 | {"int_num": "100", "ip": "192.168.10.1"}, 15 | {"int_num": "200", "ip": "192.168.20.1"}, 16 | {"int_num": "300", "ip": "192.168.30.1"} 17 | ], 18 | "netflow_enable": True 19 | }, 20 | "nx-osv-2": { 21 | "hostname": "nx-osv-2", 22 | "username": "cisco", 23 | "password": "cisco", 24 | "vlans": [100, 200, 300], 25 | "l3_vlan_interfaces": False, 26 | "netflow_enable": False 27 | } 28 | } 29 | tasks: 30 | - name: create router configuration files 31 | template: 32 | src=/home/echou/Mastering_Python_Networking_Fourth_Edition/Chapter04/Templates/nxos.j2 33 | dest=/home/echou/Mastering_Python_Networking_Fourth_Edition/Chapter04/Templates/{{ item.key }}.conf 34 | with_dict: "{{ nexus_devices }}" 35 | -------------------------------------------------------------------------------- /Chapter04/ansible.cfg: -------------------------------------------------------------------------------- 1 | [persistent_connection] 2 | ssh_type = libssh 3 | 4 | -------------------------------------------------------------------------------- /Chapter04/host_vars/iosv-1: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.51 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco 10 | -------------------------------------------------------------------------------- /Chapter04/host_vars/iosv-2: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.52 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco -------------------------------------------------------------------------------- /Chapter04/host_vars/lax-cor-r1: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.50 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: nxos 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco 10 | -------------------------------------------------------------------------------- /Chapter04/host_vars/lax-edg-r1: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.51 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco 10 | -------------------------------------------------------------------------------- /Chapter04/host_vars/lax-edg-r2: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.52 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco -------------------------------------------------------------------------------- /Chapter04/host_vars/nyc-cor-r1: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.60 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: nxos 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco 10 | -------------------------------------------------------------------------------- /Chapter04/host_vars/nyc-edg-r1: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.61 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco 10 | -------------------------------------------------------------------------------- /Chapter04/host_vars/nyc-edg-r2: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.62 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco -------------------------------------------------------------------------------- /Chapter04/hosts: -------------------------------------------------------------------------------- 1 | [ios_devices] 2 | iosv-1 3 | iosv-2 4 | -------------------------------------------------------------------------------- /Chapter04/hosts_full: -------------------------------------------------------------------------------- 1 | [lax_cor_devices] 2 | lax-cor-r1 3 | 4 | [lax_edg_devices] 5 | lax-edg-r1 6 | lax-edg-r2 7 | 8 | [nyc_cor_devices] 9 | nyc-cor-r1 10 | 11 | [nyc_edg_devices] 12 | nyc-edg-r1 13 | nyc-edg-r2 14 | 15 | [lax_dc:children] 16 | lax_cor_devices 17 | lax_edg_devices 18 | 19 | [nyc_dc:children] 20 | nyc_cor_devices 21 | nyc_edg_devices 22 | 23 | [ios_devices:children] 24 | lax_edg_devices 25 | nyc_edg_devices 26 | 27 | [nxos_devices:children] 28 | nyc_cor_devices 29 | lax_cor_devices 30 | -------------------------------------------------------------------------------- /Chapter04/ios_conditional.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: IOS command output for when clause 3 | hosts: ios_devices 4 | gather_facts: false 5 | tasks: 6 | - name: show hostname 7 | ios_command: 8 | commands: 9 | - show run | i hostname 10 | register: output 11 | 12 | - name: show output with when conditions 13 | when: output.stdout == ["hostname nyc-edg-r2"] 14 | debug: 15 | msg: '{{ output }}' 16 | 17 | -------------------------------------------------------------------------------- /Chapter04/ios_conditional_config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: IOS command output for when clause 3 | hosts: ios_devices 4 | gather_facts: false 5 | tasks: 6 | - name: show hostname 7 | ios_command: 8 | commands: 9 | - show run | i hostname 10 | register: output 11 | 12 | - name: show output with when conditions 13 | when: output.stdout == ["hostname nyc-edg-r2"] 14 | ios_config: 15 | lines: 16 | - logging buffered 30000 17 | 18 | -------------------------------------------------------------------------------- /Chapter04/ios_config_backup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Back Up IOS Device Configurations 3 | hosts: all 4 | gather_facts: false 5 | tasks: 6 | - name: backup 7 | ios_config: 8 | backup: yes 9 | 10 | -------------------------------------------------------------------------------- /Chapter04/ios_facts_playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: IOS network facts 3 | connection: network_cli 4 | gather_facts: false 5 | hosts: ios_devices 6 | tasks: 7 | - name: Gathering facts via ios_facts module 8 | ios_facts: 9 | when: ansible_network_os == 'ios' 10 | 11 | - name: Display certain facts 12 | debug: 13 | msg: "The hostname is {{ ansible_net_hostname }} running {{ ansible_net_version }}" 14 | 15 | - name: Display all facts for hosts 16 | debug: 17 | var: hostvars 18 | -------------------------------------------------------------------------------- /Chapter04/nxos_config_backup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Back Up NX-OS Device Configurations 3 | hosts: nxos_devices 4 | gather_facts: false 5 | tasks: 6 | - name: backup 7 | nxos_config: 8 | backup: yes 9 | 10 | -------------------------------------------------------------------------------- /Chapter04/standard_loop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Echo Loop Items 3 | hosts: "localhost" 4 | gather_facts: false 5 | tasks: 6 | - name: echo loop items 7 | command: echo "{{ item }}" 8 | loop: 9 | - 'r1' 10 | - 'r2' 11 | - 'r3' 12 | - 'r4' 13 | - 'r5' 14 | -------------------------------------------------------------------------------- /Chapter04/standard_loop_vlan_example.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add Multiple Vlans 3 | hosts: "nyc-cor-r1" 4 | gather_facts: false 5 | connection: network_cli 6 | vars: 7 | vlan_numbers: [100, 200, 300] 8 | tasks: 9 | - name: add vlans 10 | nxos_config: 11 | lines: 12 | - vlan {{ item }} 13 | loop: "{{ vlan_numbers }}" 14 | register: output 15 | -------------------------------------------------------------------------------- /Chapter04/standard_loop_vlan_example_2.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add Multiple Vlans 3 | hosts: "nyc-cor-r1" 4 | gather_facts: false 5 | connection: network_cli 6 | vars: 7 | vlans: { 8 | "100": {"description": "floor_1", "ip": "192.168.10.1"}, 9 | "200": {"description": "floor_2", "ip": "192.168.20.1"}, 10 | "300": {"description": "floor_3", "ip": "192.168.30.1"} 11 | } 12 | 13 | tasks: 14 | - name: add vlans 15 | nxos_config: 16 | lines: 17 | - vlan {{ item.key }} 18 | with_dict: "{{ vlans }}" 19 | 20 | - name: configure vlans 21 | nxos_config: 22 | lines: 23 | - description {{ item.value.description }} 24 | - ip address {{ item.value.ip }}/24 25 | parents: interface vlan {{ item.key }} 26 | with_dict: "{{ vlans }}" 27 | 28 | -------------------------------------------------------------------------------- /Chapter05/ansible_container/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | host_key_checking = False 3 | 4 | [persistent_connection] 5 | ssh_type = libssh 6 | 7 | -------------------------------------------------------------------------------- /Chapter05/ansible_container/host_vars/iosv-1: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.51 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco 10 | -------------------------------------------------------------------------------- /Chapter05/ansible_container/host_vars/iosv-2: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.52 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco -------------------------------------------------------------------------------- /Chapter05/ansible_container/hosts: -------------------------------------------------------------------------------- 1 | [ios_devices] 2 | iosv-1 3 | iosv-2 4 | -------------------------------------------------------------------------------- /Chapter05/ansible_container/ios_config_backup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Back Up IOS Device Configurations 3 | hosts: all 4 | gather_facts: false 5 | tasks: 6 | - name: backup 7 | ios_config: 8 | backup: yes 9 | 10 | -------------------------------------------------------------------------------- /Chapter05/container_lab/.srl02.clab.yml.bak: -------------------------------------------------------------------------------- 1 | # topology documentation: http://containerlab.dev/lab-examples/two-srls/ 2 | # https://github.com/srl-labs/containerlab/blob/main/lab-examples/srl02/srl02.clab.yml 3 | name: srl02 4 | 5 | topology: 6 | nodes: 7 | srl1: 8 | kind: srl 9 | image: ghcr.io/nokia/srlinux 10 | startup-config: srl1.cfg 11 | srl2: 12 | kind: srl 13 | image: ghcr.io/nokia/srlinux 14 | startup-config: srl2.cfg 15 | 16 | links: 17 | - endpoints: ["srl1:e1-1", "srl2:e1-1"] 18 | 19 | 20 | -------------------------------------------------------------------------------- /Chapter05/container_lab/clab-srl02/ansible-inventory.yml: -------------------------------------------------------------------------------- 1 | all: 2 | children: 3 | srl: 4 | hosts: 5 | clab-srl02-srl1: 6 | ansible_host: 172.20.20.3 7 | clab-srl02-srl2: 8 | ansible_host: 172.20.20.2 9 | -------------------------------------------------------------------------------- /Chapter05/container_lab/clab-srl02/ca/root/root-ca.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICxjCCAa4CAQAwXzELMAkGA1UEBhMCQkUxEDAOBgNVBAcTB0FudHdlcnAxDjAM 3 | BgNVBAoTBU5va2lhMRYwFAYDVQQLEw1Db250YWluZXIgbGFiMRYwFAYDVQQDEw1z 4 | cmwwMiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw23n 5 | yPJ4XUramJJfMLPbVHWb/C0pE6xYMPJ6AP6+LIosjf5ofbTd7HhPVz3koTlF2iCH 6 | sT6kFoz51sLbsiLV5SrTr0kZYRhd3MA1OUCiCERb3YMzFWFXThTiwkaCY+xlIKjS 7 | Nkg/HgwuzJ2xmzpnKP+S1zU88goMPhOyGvlQdkFouttqegrGEdeInq5vt6al36xF 8 | nWRIeT1X6vPMtt3Hp1WGXld5NbxiSqIdw3FiGzaUET4SKoLZFcxILPrCK1E6D6vh 9 | 5ntUp20GmjhdBbD/gvVCna78/KDDydueJiXPSFmE1kZhM09cHIfttatCY/NptT0h 10 | NH7CdWUgX9LpxsxniwIDAQABoCIwIAYJKoZIhvcNAQkOMRMwETAPBgNVHRMBAf8E 11 | BTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCHw8xWGEjfAHSIvEmeIP8/T/R+oj5/ 12 | T1fLUAKT4L1JndkRQT1nHUQ+S5BmJUxIBXmfhNzQwWUPHXOmcD/7TO7w61R0inBw 13 | dMmW0cGw9p5UEw7taFQ43SKz+LhEs975Xm5JS66gJR+v9aXykSujOSJVW/QWOCza 14 | 2h7LNvIEP3ABu5XmQFMsdtAR5ZrEcB3yok9IS514got8oh80n69FgiGEtLxWrRcj 15 | 4bcYvK3XBudAJol3AShf0sXGIQU5kguQ54OD/XqQVCa8eKAyHd4e4aigmpT3BMcT 16 | Qo/5P1gWIuIklC/oIPv8o3e2JwRasLDiUQjfNuM5hlw3EUzNJZhGUtDL 17 | -----END CERTIFICATE REQUEST----- 18 | 19 | -------------------------------------------------------------------------------- /Chapter05/container_lab/clab-srl02/ca/root/root-ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDkDCCAnigAwIBAgIUc/q/ZoRKB0HXufJeLztqGSc+vWUwDQYJKoZIhvcNAQEL 3 | BQAwXzELMAkGA1UEBhMCQkUxEDAOBgNVBAcTB0FudHdlcnAxDjAMBgNVBAoTBU5v 4 | a2lhMRYwFAYDVQQLEw1Db250YWluZXIgbGFiMRYwFAYDVQQDEw1zcmwwMiBSb290 5 | IENBMCAXDTIyMDkxMjAwMzgwMFoYDzIwNTIwOTA0MDAzODAwWjBfMQswCQYDVQQG 6 | EwJCRTEQMA4GA1UEBxMHQW50d2VycDEOMAwGA1UEChMFTm9raWExFjAUBgNVBAsT 7 | DUNvbnRhaW5lciBsYWIxFjAUBgNVBAMTDXNybDAyIFJvb3QgQ0EwggEiMA0GCSqG 8 | SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDbefI8nhdStqYkl8ws9tUdZv8LSkTrFgw 9 | 8noA/r4siiyN/mh9tN3seE9XPeShOUXaIIexPqQWjPnWwtuyItXlKtOvSRlhGF3c 10 | wDU5QKIIRFvdgzMVYVdOFOLCRoJj7GUgqNI2SD8eDC7MnbGbOmco/5LXNTzyCgw+ 11 | E7Ia+VB2QWi622p6CsYR14ierm+3pqXfrEWdZEh5PVfq88y23cenVYZeV3k1vGJK 12 | oh3DcWIbNpQRPhIqgtkVzEgs+sIrUToPq+Hme1SnbQaaOF0FsP+C9UKdrvz8oMPJ 13 | 254mJc9IWYTWRmEzT1wch+21q0Jj82m1PSE0fsJ1ZSBf0unGzGeLAgMBAAGjQjBA 14 | MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRHY9x0 15 | CHqeZmKNC83sihFAf1PI3TANBgkqhkiG9w0BAQsFAAOCAQEAFdHC0fEnv0ELZgQD 16 | FQfZDSsRzP5WSw4jwHQBnOJAIsGNw+a5av5m2bocbS5YEIlVvP8O2/EkD9DfhAUe 17 | tsjhTcnQnIVMaX3JewS7cDSzdDbR347YYtryyr0Plqq9riSIDTRbnj0sIExqM0J+ 18 | ERKRngeparfozVcFTC9Nhfu84p5twM/gbnqvE8CPDkHC1Nrv0DmVyMO8GqZx8keD 19 | sahNOZliFW+yIBWZ4+5bA3CGdPnu1HeKIUz6ZsOx3adsKDVcYXydFf7LB4KC7OoI 20 | xbPeOzcblR+jE1UVCkGTor0zu9SpEOxUe9NHpTsZc+VjLT5a3OCekbEleagYIE4b 21 | K7klxA== 22 | -----END CERTIFICATE----- 23 | 24 | -------------------------------------------------------------------------------- /Chapter05/container_lab/clab-srl02/ca/srl1/srl1.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC5jCCAc4CAQAwXzELMAkGA1UEBhMCQkUxEDAOBgNVBAcTB0FudHdlcnAxDjAM 3 | BgNVBAoTBU5va2lhMRYwFAYDVQQLEw1Db250YWluZXIgbGFiMRYwFAYDVQQDEw1z 4 | cmwxLnNybDAyLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7VQ7 5 | 3kZ5P0zEc00nRCOh2Vt0e4aPUiPtrcvMkvjc67lFAaNbNN6Js7A9WWc8FHRZG4Tm 6 | Rx/vdm2pQQhuTmmZR37NkT/Rl/FHCPguAZ8gFqx2i7LtFYN826h8Vn40rpUUZPv3 7 | umGKzcvb2B0VvLDvLJvVqmY5jdDUmd0tpN54anRFtD82zH7gxUz/K/TpdCQhDnUO 8 | +dC9ZM2kjft9DADit4EW/3pTNfnRHBary2en9Km5wmSq8c2rk0wOpB5fiXh7I3uU 9 | kEWCjI6MuJeYF996NZWBG3NtPKwyNWFQNNZTkHYY9FFYBFnm5KY8L8ObAJuTcdbV 10 | KHKgbVdCFhJ095LPWwIDAQABoEIwQAYJKoZIhvcNAQkOMTMwMTAvBgNVHREEKDAm 11 | ggRzcmwxgg9jbGFiLXNybDAyLXNybDGCDXNybDEuc3JsMDIuaW8wDQYJKoZIhvcN 12 | AQELBQADggEBAJbB0pj6qJlQ4XxhwLRUxp8bXwMOKuHz6PcKE793fhL+YYx3uYNK 13 | GvDhOXkI4dJvNLMrqtIytvAnWVdz9DqUU2AX2dSfvI0Qym+PXC3k7z3vqHlfz5kV 14 | wbfaOKwa3mFXR9/Zo+0VCT8VIaGSUdtAKCIqk7kfjEmQw/eD7Tul1dXqoXQlSwfk 15 | tmgvkGvIctbjIt4FrzMl3aVTiZ7AbG1bMgYn/vSbSYk43K4VxYV1uUidjGWeKEqM 16 | Tl7EBze8kV78YFu0wZ5enQzuLVZ9Tu1WWZZTjNgeMEbnIlfm5kF5alZLBHXlEZ7e 17 | Z87H5mSuoAthkmS9NHR6/IlD7VGD4DLgKbA= 18 | -----END CERTIFICATE REQUEST----- 19 | 20 | -------------------------------------------------------------------------------- /Chapter05/container_lab/clab-srl02/ca/srl1/srl1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID/jCCAuagAwIBAgIUAeKdnoRvcX06NRqAOCMFxIP8lNMwDQYJKoZIhvcNAQEL 3 | BQAwXzELMAkGA1UEBhMCQkUxEDAOBgNVBAcTB0FudHdlcnAxDjAMBgNVBAoTBU5v 4 | a2lhMRYwFAYDVQQLEw1Db250YWluZXIgbGFiMRYwFAYDVQQDEw1zcmwwMiBSb290 5 | IENBMB4XDTIyMDkxMjAwMzgwMFoXDTIzMDkxMjAwMzgwMFowXzELMAkGA1UEBhMC 6 | QkUxEDAOBgNVBAcTB0FudHdlcnAxDjAMBgNVBAoTBU5va2lhMRYwFAYDVQQLEw1D 7 | b250YWluZXIgbGFiMRYwFAYDVQQDEw1zcmwxLnNybDAyLmlvMIIBIjANBgkqhkiG 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7VQ73kZ5P0zEc00nRCOh2Vt0e4aPUiPtrcvM 9 | kvjc67lFAaNbNN6Js7A9WWc8FHRZG4TmRx/vdm2pQQhuTmmZR37NkT/Rl/FHCPgu 10 | AZ8gFqx2i7LtFYN826h8Vn40rpUUZPv3umGKzcvb2B0VvLDvLJvVqmY5jdDUmd0t 11 | pN54anRFtD82zH7gxUz/K/TpdCQhDnUO+dC9ZM2kjft9DADit4EW/3pTNfnRHBar 12 | y2en9Km5wmSq8c2rk0wOpB5fiXh7I3uUkEWCjI6MuJeYF996NZWBG3NtPKwyNWFQ 13 | NNZTkHYY9FFYBFnm5KY8L8ObAJuTcdbVKHKgbVdCFhJ095LPWwIDAQABo4GxMIGu 14 | MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw 15 | DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUUdfgVSZnOg/bgDKa/qznpqxL9OswHwYD 16 | VR0jBBgwFoAUR2PcdAh6nmZijQvN7IoRQH9TyN0wLwYDVR0RBCgwJoIEc3JsMYIP 17 | Y2xhYi1zcmwwMi1zcmwxgg1zcmwxLnNybDAyLmlvMA0GCSqGSIb3DQEBCwUAA4IB 18 | AQC923UlzccV+nSOyDtmoU0DYliZ9UKY4CYsl2IwAIEjYyVGEGS5iCX2wVTZgkt5 19 | i8/axFvfTpR7oUooxLQvGAVEOQLv2R6Fq0nqvKAxwbCHJQjloxqfRfBUGyTkRy8u 20 | quJbBcxAd70BNHjcxcFA7cfGLCEo7TRuiHyi76HiQLtttYvCXbVBOLMadS7VRC+w 21 | gPd8l7G7A/zXdKwNM2XR0x6ZQNBdVvJ/mHdYtwVbnQnzg2HDhzR3b6IFQSBWQDi6 22 | /dOrRxLpI2mO9GFiejN8KsqdxZVA5kzPIuTTRnQSezatusiJHPfyiGFbcK/uMbRp 23 | +eSKjuAjtiI3krlMX+RTw0dD 24 | -----END CERTIFICATE----- 25 | 26 | -------------------------------------------------------------------------------- /Chapter05/container_lab/clab-srl02/ca/srl2/srl2.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC5jCCAc4CAQAwXzELMAkGA1UEBhMCQkUxEDAOBgNVBAcTB0FudHdlcnAxDjAM 3 | BgNVBAoTBU5va2lhMRYwFAYDVQQLEw1Db250YWluZXIgbGFiMRYwFAYDVQQDEw1z 4 | cmwyLnNybDAyLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5btb 5 | 5NR3OcyiACTHsTQALfKtxtKZowQ4OAobTJFKgra+FE8M1oRSoxU8//Xj9KRbF4l5 6 | tvhvvjD2W24Z1at8qXgv6V26rUGHw8b28SK9grumE4YWSSy58syVPg4CPk4qJnwa 7 | 14Pl6WEI2YkSN8qIUKZc8YNP7/fFM6HEuPbtUgSp6xxcf1fhX1eZQvPB8FXTSSRS 8 | fx/zXmPeZfshlwYDoBWAfjKX/11PtnrnpB95x/Z8hzA8XOgknMijzthWNB/01qC9 9 | 4V8dcEVEv2qWCMUV66O544i1hFdH8djDFNTBMQt4zApmYANp3PCE6YucuFivz9gf 10 | cUa0s0HYguUb+uHDWwIDAQABoEIwQAYJKoZIhvcNAQkOMTMwMTAvBgNVHREEKDAm 11 | ggRzcmwygg9jbGFiLXNybDAyLXNybDKCDXNybDIuc3JsMDIuaW8wDQYJKoZIhvcN 12 | AQELBQADggEBAGXmaagAZKlFjH0rxIvi/uvgEuXAPK3GYZUbaXYbG/qcVEJByrz9 13 | VgSZ6g77ZlHjSt2pQ3wsXY+i08pPvmHwbnhTp0q+gZE/02WbcKwRIfyycoEqmKiU 14 | 5AnW54hPyL24Z8OUK0/wER/3yAY3SMhXfrGxhRQ9/oWA+0GQnrKEeoR/Iq0QCW88 15 | dRSWqYMIKEi/VY+MzG9XLtJinDmeWLgVc1fDovSYdjEEZ7Q1iawi90LuBKlXKdcX 16 | GebGLZTZsa7Zw3tNRSl5kUfhoN119k2F7bcVRmpBpVjEZaI3uBTvStGCBLExdw30 17 | c+lUirvsQRN1xw4lIRVU0i0hQtONkN/IRxs= 18 | -----END CERTIFICATE REQUEST----- 19 | 20 | -------------------------------------------------------------------------------- /Chapter05/container_lab/clab-srl02/ca/srl2/srl2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID/jCCAuagAwIBAgIUcv7tSlIu1ri7yRersljdrCZoK8owDQYJKoZIhvcNAQEL 3 | BQAwXzELMAkGA1UEBhMCQkUxEDAOBgNVBAcTB0FudHdlcnAxDjAMBgNVBAoTBU5v 4 | a2lhMRYwFAYDVQQLEw1Db250YWluZXIgbGFiMRYwFAYDVQQDEw1zcmwwMiBSb290 5 | IENBMB4XDTIyMDkxMjAwMzgwMFoXDTIzMDkxMjAwMzgwMFowXzELMAkGA1UEBhMC 6 | QkUxEDAOBgNVBAcTB0FudHdlcnAxDjAMBgNVBAoTBU5va2lhMRYwFAYDVQQLEw1D 7 | b250YWluZXIgbGFiMRYwFAYDVQQDEw1zcmwyLnNybDAyLmlvMIIBIjANBgkqhkiG 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5btb5NR3OcyiACTHsTQALfKtxtKZowQ4OAob 9 | TJFKgra+FE8M1oRSoxU8//Xj9KRbF4l5tvhvvjD2W24Z1at8qXgv6V26rUGHw8b2 10 | 8SK9grumE4YWSSy58syVPg4CPk4qJnwa14Pl6WEI2YkSN8qIUKZc8YNP7/fFM6HE 11 | uPbtUgSp6xxcf1fhX1eZQvPB8FXTSSRSfx/zXmPeZfshlwYDoBWAfjKX/11Ptnrn 12 | pB95x/Z8hzA8XOgknMijzthWNB/01qC94V8dcEVEv2qWCMUV66O544i1hFdH8djD 13 | FNTBMQt4zApmYANp3PCE6YucuFivz9gfcUa0s0HYguUb+uHDWwIDAQABo4GxMIGu 14 | MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw 15 | DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUFC2KkXjLwj7m++9DJ/DF+KgHFeUwHwYD 16 | VR0jBBgwFoAUR2PcdAh6nmZijQvN7IoRQH9TyN0wLwYDVR0RBCgwJoIEc3JsMoIP 17 | Y2xhYi1zcmwwMi1zcmwygg1zcmwyLnNybDAyLmlvMA0GCSqGSIb3DQEBCwUAA4IB 18 | AQAJppwYcxh4GvS1cp/T36Ln3xm/qE41l7Ox5aZjca91u7tsY7y6U5BjTlABgeiM 19 | RqOdj2YxrfOyu5nirvD/EIWy1wUioscL+m25frh95Qc9DLc9N3aV6B/jowEdGV1y 20 | lWAfds48VopCDSQ2i3Vd9s2wJOvtH+R9+3AuzNt4FwhmDcY6HjTyMDqlSWkaP+9N 21 | dO2HkQHeSfJjQAlPvouDH1lObSetiCGZPv7ly8ABf7VEc61RhKV/PXMZ1EXvv6Y2 22 | DvKLNXmWG/fmv09SixHZbNQZhMIE8Nu3aSrHKIWVd/xcNg4nsweaIFaQwrK053PX 23 | MZZPxGIM8qWExk2Lv/7VL4Xz 24 | -----END CERTIFICATE----- 25 | 26 | -------------------------------------------------------------------------------- /Chapter05/container_lab/clab-srl02/srl1/topology.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Nokia 2 | # Licensed under the BSD 3-Clause License. 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | chassis_configuration: 6 | "chassis_type": 65 7 | "base_mac": "1a:85:00:00:00:00" 8 | "cpm_card_type": 176 9 | 10 | slot_configuration: 11 | 1: 12 | "card_type": 176 13 | "mda_type": 195 # imm48-25g-sfp28+8-100g-qsfp28 14 | -------------------------------------------------------------------------------- /Chapter05/container_lab/clab-srl02/srl2/topology.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Nokia 2 | # Licensed under the BSD 3-Clause License. 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | chassis_configuration: 6 | "chassis_type": 65 7 | "base_mac": "1a:21:01:00:00:00" 8 | "cpm_card_type": 176 9 | 10 | slot_configuration: 11 | 1: 12 | "card_type": 176 13 | "mda_type": 195 # imm48-25g-sfp28+8-100g-qsfp28 14 | -------------------------------------------------------------------------------- /Chapter05/container_lab/clab-srl02/topology-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "srl02", 3 | "type": "clab", 4 | "clab": { 5 | "config": { 6 | "prefix": "clab", 7 | "mgmt": { 8 | "network": "clab", 9 | "bridge": "br-4807fa9091c5", 10 | "ipv4-subnet": "172.20.20.0/24", 11 | "ipv4-gw": "172.20.20.1", 12 | "ipv6-subnet": "2001:172:20:20::/64", 13 | "ipv6-gw": "2001:172:20:20::1", 14 | "mtu": "1500", 15 | "external-access": true 16 | } 17 | } 18 | }, 19 | "nodes": { 20 | "srl1": { 21 | "index": "0", 22 | "shortname": "srl1", 23 | "longname": "clab-srl02-srl1", 24 | "fqdn": "srl1.srl02.io", 25 | "group": "", 26 | "labdir": "/home/echou/Mastering_Python_Networking_Fourth_Edition/Chapter05/container_lab/clab-srl02/srl1", 27 | "kind": "srl", 28 | "image": "ghcr.io/nokia/srlinux", 29 | "mgmt-net": "", 30 | "mgmt-intf": "", 31 | "mgmt-ipv4-address": "172.20.20.3", 32 | "mgmt-ipv4-prefix-length": 24, 33 | "mgmt-ipv6-address": "2001:172:20:20::3", 34 | "mgmt-ipv6-prefix-length": 64, 35 | "mac-address": "", 36 | "labels": { 37 | "clab-mgmt-net-bridge": "br-4807fa9091c5", 38 | "clab-node-group": "", 39 | "clab-node-kind": "srl", 40 | "clab-node-lab-dir": "/home/echou/Mastering_Python_Networking_Fourth_Edition/Chapter05/container_lab/clab-srl02/srl1", 41 | "clab-node-name": "srl1", 42 | "clab-node-type": "ixrd2", 43 | "clab-topo-file": "/home/echou/Mastering_Python_Networking_Fourth_Edition/Chapter05/container_lab/srl02.clab.yml", 44 | "containerlab": "srl02" 45 | } 46 | }, 47 | "srl2": { 48 | "index": "1", 49 | "shortname": "srl2", 50 | "longname": "clab-srl02-srl2", 51 | "fqdn": "srl2.srl02.io", 52 | "group": "", 53 | "labdir": "/home/echou/Mastering_Python_Networking_Fourth_Edition/Chapter05/container_lab/clab-srl02/srl2", 54 | "kind": "srl", 55 | "image": "ghcr.io/nokia/srlinux", 56 | "mgmt-net": "", 57 | "mgmt-intf": "", 58 | "mgmt-ipv4-address": "172.20.20.2", 59 | "mgmt-ipv4-prefix-length": 24, 60 | "mgmt-ipv6-address": "2001:172:20:20::2", 61 | "mgmt-ipv6-prefix-length": 64, 62 | "mac-address": "", 63 | "labels": { 64 | "clab-mgmt-net-bridge": "br-4807fa9091c5", 65 | "clab-node-group": "", 66 | "clab-node-kind": "srl", 67 | "clab-node-lab-dir": "/home/echou/Mastering_Python_Networking_Fourth_Edition/Chapter05/container_lab/clab-srl02/srl2", 68 | "clab-node-name": "srl2", 69 | "clab-node-type": "ixrd2", 70 | "clab-topo-file": "/home/echou/Mastering_Python_Networking_Fourth_Edition/Chapter05/container_lab/srl02.clab.yml", 71 | "containerlab": "srl02" 72 | } 73 | } 74 | }, 75 | "links": [ 76 | { 77 | "a": { 78 | "node": "srl1", 79 | "interface": "e1-1", 80 | "mac": "aa:c1:ab:04:fc:b3", 81 | "peer": "z" 82 | }, 83 | "z": { 84 | "node": "srl2", 85 | "interface": "e1-1", 86 | "mac": "aa:c1:ab:08:0b:b7", 87 | "peer": "a" 88 | } 89 | } 90 | ] 91 | } 92 | -------------------------------------------------------------------------------- /Chapter05/container_lab/srl02.clab.yml: -------------------------------------------------------------------------------- 1 | # topology documentation: http://containerlab.dev/lab-examples/two-srls/ 2 | # https://github.com/srl-labs/containerlab/blob/main/lab-examples/srl02/srl02.clab.yml 3 | name: srl02 4 | 5 | topology: 6 | nodes: 7 | srl1: 8 | kind: srl 9 | image: ghcr.io/nokia/srlinux 10 | startup-config: srl1.cfg 11 | srl2: 12 | kind: srl 13 | image: ghcr.io/nokia/srlinux 14 | startup-config: srl2.cfg 15 | 16 | links: 17 | - endpoints: ["srl1:e1-1", "srl2:e1-1"] 18 | 19 | -------------------------------------------------------------------------------- /Chapter05/container_lab/srl1.cfg: -------------------------------------------------------------------------------- 1 | set / interface ethernet-1/1 2 | set / interface ethernet-1/1 subinterface 0 3 | set / interface ethernet-1/1 subinterface 0 ipv4 4 | set / interface ethernet-1/1 subinterface 0 ipv4 address 192.168.0.0/31 5 | set / interface ethernet-1/1 subinterface 0 ipv6 6 | set / interface ethernet-1/1 subinterface 0 ipv6 address 2002::192.168.0.0/127 7 | 8 | set / network-instance default 9 | set / network-instance default interface ethernet-1/1.0 10 | 11 | -------------------------------------------------------------------------------- /Chapter05/container_lab/srl2.cfg: -------------------------------------------------------------------------------- 1 | set / interface ethernet-1/1 2 | set / interface ethernet-1/1 subinterface 0 3 | set / interface ethernet-1/1 subinterface 0 ipv4 4 | set / interface ethernet-1/1 subinterface 0 ipv4 address 192.168.0.1/31 5 | set / interface ethernet-1/1 subinterface 0 ipv6 6 | set / interface ethernet-1/1 subinterface 0 ipv6 address 2002::192.168.0.1/127 7 | 8 | set / network-instance default 9 | set / network-instance default interface ethernet-1/1.0 10 | 11 | -------------------------------------------------------------------------------- /Chapter06/access_list_mac_iosv.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Configure MAC Access List 3 | hosts: "nyc-edg-r1" 4 | gather_facts: false 5 | connection: local 6 | 7 | vars: 8 | cli: 9 | host: "{{ ansible_host }}" 10 | username: "{{ ansible_user }}" 11 | password: "{{ ansible_ssh_pass }}" 12 | 13 | tasks: 14 | - name: Deny Hosts with vendor id fa16.3e00.0000 15 | ios_config: 16 | lines: 17 | - access-list 700 deny fa16.3e00.0000 0000.00FF.FFFF 18 | - access-list 700 permit 0000.0000.0000 FFFF.FFFF.FFFF 19 | - name: Apply filter on bridge group 1 20 | ios_config: 21 | lines: 22 | - bridge-group 1 23 | - bridge-group 1 input-address-list 700 24 | parents: 25 | - interface GigabitEthernet0/1 26 | 27 | -------------------------------------------------------------------------------- /Chapter06/access_list_nxosv.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Configure Access List 3 | hosts: "nxosv-devices" 4 | gather_facts: false 5 | connection: local 6 | 7 | vars: 8 | cli: 9 | host: "{{ ansible_host }}" 10 | username: "{{ ansible_user }}" 11 | password: "{{ ansible_ssh_pass }}" 12 | transport: cli 13 | 14 | tasks: 15 | - nxos_acl: 16 | name: border_inbound 17 | seq: 20 18 | action: deny 19 | proto: tcp 20 | src: 172.16.0.0/12 21 | dest: any 22 | log: enable 23 | state: present 24 | - nxos_acl: 25 | name: border_inbound 26 | seq: 30 27 | action: deny 28 | proto: tcp 29 | src: 192.168.0.0/16 30 | dest: any 31 | state: present 32 | log: enable 33 | - nxos_acl: 34 | name: border_inbound 35 | seq: 40 36 | action: permit 37 | proto: tcp 38 | src: any 39 | dest: 10.0.0.9/32 40 | dest_port_op: eq 41 | dest_port1: 22 42 | state: present 43 | log: enable 44 | - nxos_acl: 45 | name: border_inbound 46 | seq: 50 47 | action: permit 48 | proto: tcp 49 | src: any 50 | dest: 10.0.0.9/32 51 | dest_port_op: eq 52 | dest_port1: 80 53 | state: present 54 | log: enable 55 | - nxos_acl: 56 | name: border_inbound 57 | seq: 60 58 | action: permit 59 | proto: tcp 60 | src: any 61 | dest: any 62 | state: present 63 | log: enable 64 | established: enable 65 | - nxos_acl: 66 | name: border_inbound 67 | seq: 1000 68 | action: deny 69 | proto: ip 70 | src: any 71 | dest: any 72 | state: present 73 | log: enable 74 | - name: apply ingress acl to Ethernet 2/4 75 | nxos_acl_interface: 76 | name: border_inbound 77 | interface: Ethernet2/4 78 | direction: ingress 79 | state: present 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Chapter06/chapter6_topology.virl.backup: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | flat 5 | 6 | 7 | 8 | ! IOS Config generated on 2017-09-08 18:49 9 | ! by autonetkit_0.23.5 10 | ! 11 | hostname iosv-1 12 | boot-start-marker 13 | boot-end-marker 14 | ! 15 | vrf definition Mgmt-intf 16 | ! 17 | address-family ipv4 18 | exit-address-family 19 | ! 20 | address-family ipv6 21 | exit-address-family 22 | ! 23 | ! 24 | ! 25 | no aaa new-model 26 | ! 27 | ! 28 | ip cef 29 | ipv6 unicast-routing 30 | ipv6 cef 31 | ! 32 | ! 33 | service timestamps debug datetime msec 34 | service timestamps log datetime msec 35 | no service password-encryption 36 | no service config 37 | enable password cisco 38 | ip classless 39 | ip subnet-zero 40 | no ip domain lookup 41 | ip domain name virl.info 42 | crypto key generate rsa modulus 768 43 | ip ssh server algorithm authentication password 44 | username cisco privilege 15 secret cisco 45 | line vty 0 4 46 | transport input ssh telnet 47 | exec-timeout 720 0 48 | password cisco 49 | login local 50 | line con 0 51 | password cisco 52 | ! 53 | no cdp run 54 | ! 55 | ! 56 | interface Loopback0 57 | description Loopback 58 | ip address 192.168.0.2 255.255.255.255 59 | ! 60 | interface GigabitEthernet0/0 61 | description OOB Management 62 | vrf forwarding Mgmt-intf 63 | ! Configured on launch 64 | no ip address 65 | duplex full 66 | speed auto 67 | no shutdown 68 | ! 69 | interface GigabitEthernet0/1 70 | description to nx-osv-1 71 | ip address 10.0.0.13 255.255.255.252 72 | ip ospf cost 1 73 | duplex full 74 | speed auto 75 | no shutdown 76 | ! 77 | interface GigabitEthernet0/2 78 | description to Server 79 | ip address 10.0.0.10 255.255.255.252 80 | ip ospf cost 1 81 | duplex full 82 | speed auto 83 | no shutdown 84 | ! 85 | ! 86 | ! 87 | router ospf 1 88 | network 192.168.0.2 0.0.0.0 area 0 89 | log-adjacency-changes 90 | passive-interface Loopback0 91 | network 10.0.0.12 0.0.0.3 area 0 92 | network 10.0.0.8 0.0.0.3 area 0 93 | ! 94 | ! 95 | router bgp 1 96 | bgp router-id 192.168.0.2 97 | no synchronization 98 | ! ibgp 99 | ! ibgp peers 100 | ! 101 | neighbor 192.168.0.1 remote-as 1 102 | neighbor 192.168.0.1 description iBGP peer nx-osv-1 103 | neighbor 192.168.0.1 update-source Loopback0 104 | ! 105 | ! 106 | ! 107 | address-family ipv4 108 | network 192.168.0.2 mask 255.255.255.255 109 | neighbor 192.168.0.1 activate 110 | exit-address-family 111 | ! 112 | ! 113 | ! 114 | end 115 | 116 | 172.16.1.154 117 | 118 | 119 | 120 | 121 | 122 | 123 | ! NX-OSv Config generated on 2017-09-08 18:49 124 | ! by autonetkit_0.23.5 125 | ! 126 | version 6.2(1) 127 | license grace-period 128 | ! 129 | hostname nx-osv-1 130 | vdc nx-osv-1 id 1 131 | allocate interface Ethernet2/1-48 132 | allocate interface Ethernet3/1-48 133 | limit-resource vlan minimum 16 maximum 4094 134 | limit-resource vrf minimum 2 maximum 4096 135 | limit-resource port-channel minimum 0 maximum 768 136 | limit-resource u4route-mem minimum 96 maximum 96 137 | limit-resource u6route-mem minimum 24 maximum 24 138 | limit-resource m4route-mem minimum 58 maximum 58 139 | limit-resource m6route-mem minimum 8 maximum 8 140 | 141 | feature telnet 142 | 143 | feature ospf 144 | feature bgp 145 | 146 | username adminbackup password 5 ! role network-operator 147 | username admin password 5 $1$KuOSBsvW$Cy0TSD..gEBGBPjzpDgf51 role network-admin 148 | username cisco password 5 $1$Nk7ZkwH0$fyiRmMMfIheqE3BqvcL0C1 role network-operator 149 | username cisco role network-admin 150 | username lab password 5 $1$buoy/oqy$.EXQz8rCn72ii8qtdldj00 role network-admin 151 | no password strength-check 152 | ip domain-lookup 153 | copp profile strict 154 | snmp-server user lab network-admin auth md5 0x5ceb414591539ee35159fca86fdfa101 priv 0x5ceb414591539ee35159fca86fdfa101 localizedkey 155 | snmp-server user admin network-admin auth md5 0x328945d53e05e8e7207f8c20b142f0b7 priv 0x328945d53e05e8e7207f8c20b142f0b7 localizedkey 156 | snmp-server user cisco network-operator auth md5 0x55b3c64a53fb95518e75358ee75e82e9 priv 0x55b3c64a53fb95518e75358ee75e82e9 localizedkey 157 | snmp-server user cisco network-admin 158 | rmon event 1 log trap public description FATAL(1) owner PMON@FATAL 159 | rmon event 2 log trap public description CRITICAL(2) owner PMON@CRITICAL 160 | rmon event 3 log trap public description ERROR(3) owner PMON@ERROR 161 | rmon event 4 log trap public description WARNING(4) owner PMON@WARNING 162 | rmon event 5 log trap public description INFORMATION(5) owner PMON@INFO 163 | 164 | 165 | vlan 1 166 | 167 | vrf context management 168 | hardware forwarding unicast trace 169 | 170 | interface Loopback0 171 | description Loopback 172 | ip address 192.168.0.1/32 173 | ip router ospf 1 area 0 174 | 175 | interface Ethernet2/1 176 | description to Client 177 | ip address 10.0.0.6/30 178 | ip router ospf 1 area 0 179 | duplex full 180 | mac-address fa16.3e00.0001 181 | no shutdown 182 | 183 | interface Ethernet2/2 184 | description to iosv-1 185 | ip address 10.0.0.14/30 186 | ip router ospf 1 area 0 187 | duplex full 188 | mac-address fa16.3e00.0002 189 | no shutdown 190 | 191 | interface mgmt0 192 | description OOB Management 193 | ! Configured on launch 194 | no ip address 195 | duplex full 196 | mac-address fa16.3e00.0003 197 | no shutdown 198 | vrf member management 199 | 200 | 201 | line console 202 | line vty 203 | router ospf 1 204 | router-id 192.168.0.1 205 | router bgp 1 206 | router-id 192.168.0.1 207 | address-family ipv4 unicast 208 | network 192.168.0.1/32 209 | ! 210 | ! iBGP 211 | ! 212 | ! iBGP peers 213 | ! 214 | neighbor 192.168.0.2 remote-as 1 215 | description iBGP peer iosv-1 216 | update-source Loopback0 217 | address-family ipv4 unicast 218 | ! 219 | ! 220 | 221 | 222 | 172.16.1.155 223 | 224 | 225 | 226 | 227 | 228 | 229 | #cloud-config 230 | bootcmd: 231 | - ln -s -t /etc/rc.d /etc/rc.local 232 | hostname: Client 233 | manage_etc_hosts: true 234 | runcmd: 235 | - start ttyS0 236 | - systemctl start getty@ttyS0.service 237 | - systemctl start rc-local 238 | - sed -i '/^\s*PasswordAuthentication\s\+no/d' /etc/ssh/sshd_config 239 | - echo "UseDNS no" >> /etc/ssh/sshd_config 240 | - service ssh restart 241 | - service sshd restart 242 | users: 243 | - default 244 | - gecos: User configured by VIRL Configuration Engine 0.23.10 245 | lock-passwd: false 246 | name: cisco 247 | plain-text-passwd: cisco 248 | shell: /bin/bash 249 | ssh-authorized-keys: 250 | - VIRL-USER-SSH-PUBLIC-KEY 251 | sudo: ALL=(ALL) ALL 252 | write_files: 253 | - path: /etc/init/ttyS0.conf 254 | owner: root:root 255 | content: | 256 | # ttyS0 - getty 257 | # This service maintains a getty on ttyS0 from the point the system is 258 | # started until it is shut down again. 259 | start on stopped rc or RUNLEVEL=[12345] 260 | stop on runlevel [!12345] 261 | respawn 262 | exec /sbin/getty -L 115200 ttyS0 vt102 263 | permissions: '0644' 264 | - path: /etc/systemd/system/dhclient@.service 265 | content: | 266 | [Unit] 267 | Description=Run dhclient on %i interface 268 | After=network.target 269 | [Service] 270 | Type=oneshot 271 | ExecStart=/sbin/dhclient %i -pf /var/run/dhclient.%i.pid -lf /var/lib/dhclient/dhclient.%i.lease 272 | RemainAfterExit=yes 273 | owner: root:root 274 | permissions: '0644' 275 | - path: /etc/rc.local 276 | owner: root:root 277 | permissions: '0755' 278 | content: |- 279 | #!/bin/sh 280 | ifconfig eth1 up 10.0.0.5 netmask 255.255.255.252 281 | route add -net 10.0.0.0/8 gw 10.0.0.6 dev eth1 282 | route add -net 192.168.0.0/29 gw 10.0.0.6 dev eth1 283 | exit 0 284 | 285 | 172.16.1.152 286 | 287 | 288 | 289 | 290 | 291 | #cloud-config 292 | bootcmd: 293 | - ln -s -t /etc/rc.d /etc/rc.local 294 | hostname: Server 295 | manage_etc_hosts: true 296 | runcmd: 297 | - start ttyS0 298 | - systemctl start getty@ttyS0.service 299 | - systemctl start rc-local 300 | - sed -i '/^\s*PasswordAuthentication\s\+no/d' /etc/ssh/sshd_config 301 | - echo "UseDNS no" >> /etc/ssh/sshd_config 302 | - service ssh restart 303 | - service sshd restart 304 | users: 305 | - default 306 | - gecos: User configured by VIRL Configuration Engine 0.23.10 307 | lock-passwd: false 308 | name: cisco 309 | plain-text-passwd: cisco 310 | shell: /bin/bash 311 | ssh-authorized-keys: 312 | - VIRL-USER-SSH-PUBLIC-KEY 313 | sudo: ALL=(ALL) ALL 314 | write_files: 315 | - path: /etc/init/ttyS0.conf 316 | owner: root:root 317 | content: | 318 | # ttyS0 - getty 319 | # This service maintains a getty on ttyS0 from the point the system is 320 | # started until it is shut down again. 321 | start on stopped rc or RUNLEVEL=[12345] 322 | stop on runlevel [!12345] 323 | respawn 324 | exec /sbin/getty -L 115200 ttyS0 vt102 325 | permissions: '0644' 326 | - path: /etc/systemd/system/dhclient@.service 327 | content: | 328 | [Unit] 329 | Description=Run dhclient on %i interface 330 | After=network.target 331 | [Service] 332 | Type=oneshot 333 | ExecStart=/sbin/dhclient %i -pf /var/run/dhclient.%i.pid -lf /var/lib/dhclient/dhclient.%i.lease 334 | RemainAfterExit=yes 335 | owner: root:root 336 | permissions: '0644' 337 | - path: /etc/rc.local 338 | owner: root:root 339 | permissions: '0755' 340 | content: |- 341 | #!/bin/sh 342 | ifconfig eth1 up 10.0.0.9 netmask 255.255.255.252 343 | route add -net 10.0.0.0/8 gw 10.0.0.10 dev eth1 344 | route add -net 192.168.0.0/29 gw 10.0.0.10 dev eth1 345 | exit 0 346 | 347 | 172.16.1.153 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | -------------------------------------------------------------------------------- /Chapter06/host_vars/nyc-cor-r1: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.60 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: nxos 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco 10 | -------------------------------------------------------------------------------- /Chapter06/host_vars/nyc-edg-r1: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.61 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco 10 | -------------------------------------------------------------------------------- /Chapter06/hosts: -------------------------------------------------------------------------------- 1 | [nxosv-devices] 2 | nyc-cor-r1 3 | 4 | [iosv-devices] 5 | nyc-edg-r1 6 | 7 | -------------------------------------------------------------------------------- /Chapter06/python_re_search_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re, datetime 4 | 5 | startTime = datetime.datetime.now() 6 | 7 | with open('sample_log_anonymized.log', 'r') as f: 8 | for line in f.readlines(): 9 | if re.search('ACLLOG-5-ACLLOG_FLOW_INTERVAL', line): 10 | print(line) 11 | 12 | endTime = datetime.datetime.now() 13 | elapsedTime = endTime - startTime 14 | print("Time Elapsed: " + str(elapsedTime)) 15 | 16 | -------------------------------------------------------------------------------- /Chapter06/python_re_search_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re, datetime 4 | 5 | startTime = datetime.datetime.now() 6 | 7 | term1 = re.compile('ACLLOG-5-ACLLOG_FLOW_INTERVAL') 8 | term2 = re.compile('PAM: Authentication failure') 9 | 10 | fileList = ['sample_log_anonymized.log', 'sample_log_anonymized_1.log'] 11 | 12 | for log in fileList: 13 | with open(log, 'r') as f: 14 | for line in f.readlines(): 15 | if re.search(term1, line) or re.search(term2, line): 16 | print(line) 17 | 18 | endTime = datetime.datetime.now() 19 | elapsedTime = endTime - startTime 20 | print("Time Elapsed: " + str(elapsedTime)) 21 | 22 | -------------------------------------------------------------------------------- /Chapter06/scapy_ping_collection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from scapy.all import * 4 | 5 | def icmp_ping(destination): 6 | # regular ICMP ping 7 | ans, unans = sr(IP(dst=destination)/ICMP()) 8 | return ans 9 | 10 | def tcp_ping(destination, dport): 11 | ans, unans = sr(IP(dst=destination)/TCP(dport=dport,flags="S")) 12 | return ans 13 | 14 | def udp_ping(destination): 15 | ans, unans = sr(IP(dst=destination)/UDP(dport=0)) 16 | return ans 17 | 18 | def answer_summary(ans): 19 | for send, recv in ans: 20 | print(recv.sprintf("%IP.src% is alive")) 21 | 22 | 23 | def main(): 24 | print("** ICMP Ping **") 25 | ans = icmp_ping("10.0.0.13-14") 26 | answer_summary(ans) 27 | print("** TCP Ping ***") 28 | ans = tcp_ping("10.0.0.13", 22) 29 | answer_summary(ans) 30 | print("** UDP Ping ***") 31 | ans = udp_ping("10.0.0.13-14") 32 | answer_summary(ans) 33 | 34 | -------------------------------------------------------------------------------- /Chapter06/scapy_tcp_scan_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from scapy.all import * 4 | import sys 5 | 6 | def tcp_scan(destination, dport): 7 | ans, unans = sr(IP(dst=destination)/TCP(sport=666,dport=dport,flags="S")) 8 | for sending, returned in ans: 9 | if 'SA' in str(returned[TCP].flags): 10 | return destination + " port " + str(sending[TCP].dport) + " is open." 11 | else: 12 | return destination + " port " + str(sending[TCP].dport) + " is not open." 13 | 14 | def main(): 15 | destination = sys.argv[1] 16 | port = int(sys.argv[2]) 17 | scan_result = tcp_scan(destination, port) 18 | print(scan_result) 19 | 20 | if __name__ == "__main__": 21 | main() 22 | 23 | -------------------------------------------------------------------------------- /Chapter07/cacti_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pexpect, sys 4 | 5 | devices = {'iosv-1': {'prompt': 'iosv-1#', 'ip': '172.16.1.189'}} 6 | username = 'cisco' 7 | password = 'cisco' 8 | 9 | for device in devices.keys(): 10 | device_prompt = devices[device]['prompt'] 11 | child = pexpect.spawn('telnet ' + devices[device]['ip']) 12 | child.expect('Username:') 13 | child.sendline(username) 14 | child.expect('Password:') 15 | child.sendline(password) 16 | child.expect(device_prompt) 17 | child.sendline('sh ip access-lists permit_snmp | i 172.16.1.173') 18 | child.expect(device_prompt) 19 | output = child.before 20 | child.sendline('exit') 21 | 22 | sys.stdout.write(str(output).split('(')[1].split()[0].strip()) 23 | 24 | -------------------------------------------------------------------------------- /Chapter07/cacti_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pexpect 4 | 5 | devices = {'iosv-1': {'prompt': 'iosv-1#', 'ip': '172.16.1.189'}} 6 | username = 'cisco' 7 | password = 'cisco' 8 | 9 | for device in devices.keys(): 10 | device_prompt = devices[device]['prompt'] 11 | child = pexpect.spawn('telnet ' + devices[device]['ip']) 12 | child.expect('Username:') 13 | child.sendline(username) 14 | child.expect('Password:') 15 | child.sendline(password) 16 | child.expect(device_prompt) 17 | child.sendline('sh ip access-lists permit_snmp | i 172.16.1.173') 18 | child.expect(device_prompt) 19 | output = child.before 20 | child.sendline('exit') 21 | 22 | print(str(output).split('(')[1].split()[0].strip()) 23 | 24 | -------------------------------------------------------------------------------- /Chapter07/figuer1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter07/figuer1.png -------------------------------------------------------------------------------- /Chapter07/figure1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter07/figure1.pdf -------------------------------------------------------------------------------- /Chapter07/matplotlib_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import matplotlib.pyplot as plt 4 | import matplotlib.dates as dates 5 | 6 | x_time = [] 7 | y_value = [] 8 | 9 | with open('results.txt', 'r') as f: 10 | for line in f.readlines(): 11 | # eval(line) reads in each line as dictionary instead of string 12 | line = eval(line) 13 | # convert to internal float 14 | x_time.append(dates.datestr2num(line['Time'])) 15 | y_value.append(line['Gig0-0_Out_uPackets']) 16 | 17 | plt.subplots_adjust(bottom=0.3) 18 | plt.xticks(rotation=80) 19 | 20 | # Use plot_date to display x-axis back in date format 21 | plt.plot_date(x_time, y_value) 22 | plt.title('Router1 G0/0') 23 | plt.xlabel('Time in UTC') 24 | plt.ylabel('Output Unicast Packets') 25 | plt.savefig('matplotlib_1_result.png') 26 | plt.show() 27 | 28 | 29 | -------------------------------------------------------------------------------- /Chapter07/matplotlib_1_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter07/matplotlib_1_result.png -------------------------------------------------------------------------------- /Chapter07/matplotlib_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import matplotlib.pyplot as plt 4 | import matplotlib.dates as dates 5 | 6 | x_time = [] 7 | out_octets = [] 8 | out_packets = [] 9 | in_octets = [] 10 | in_packets = [] 11 | 12 | with open('results.txt', 'r') as f: 13 | for line in f.readlines(): 14 | # eval(line) reads in each line as dictionary instead of string 15 | line = eval(line) 16 | # convert to internal float 17 | x_time.append(dates.datestr2num(line['Time'])) 18 | out_packets.append(line['Gig0-0_Out_uPackets']) 19 | out_octets.append(line['Gig0-0_Out_Octet']) 20 | in_packets.append(line['Gig0-0_In_uPackets']) 21 | in_octets.append(line['Gig0-0_In_Octet']) 22 | 23 | plt.subplots_adjust(bottom=0.3) 24 | plt.xticks(rotation=80) 25 | 26 | # Use plot_date to display x-axis back in date format 27 | plt.plot_date(x_time, out_packets, '-', label='Out Packets') 28 | plt.plot_date(x_time, out_octets, '-', label='Out Octets') 29 | plt.plot_date(x_time, in_packets, '-', label='In Packets') 30 | plt.plot_date(x_time, in_octets, '-', label='In Octets') 31 | 32 | plt.title('Router1 G0/0') 33 | plt.legend(loc='upper left') 34 | plt.grid(True) 35 | plt.xlabel('Time in UTC') 36 | plt.ylabel('Values') 37 | plt.savefig('matplotlib_2_result.png') 38 | plt.show() 39 | 40 | 41 | -------------------------------------------------------------------------------- /Chapter07/matplotlib_2_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter07/matplotlib_2_result.png -------------------------------------------------------------------------------- /Chapter07/matplotlib_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Example from http://matplotlib.org/2.0.0/examples/pie_and_polar_charts/pie_demo_features.html 4 | 5 | import matplotlib.pyplot as plt 6 | 7 | # Pie chart, where the slices will be ordered and plotted counter-clockwise: 8 | labels = 'TCP', 'UDP', 'ICMP', 'Others' 9 | sizes = [15, 30, 45, 10] 10 | explode = (0, 0.1, 0, 0) # Make UDP stand out 11 | 12 | fig1, ax1 = plt.subplots() 13 | ax1.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', 14 | shadow=True, startangle=90) 15 | ax1.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle. 16 | 17 | plt.savefig('matplotlib_3_result.png') 18 | plt.show() 19 | 20 | -------------------------------------------------------------------------------- /Chapter07/matplotlib_3_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter07/matplotlib_3_result.png -------------------------------------------------------------------------------- /Chapter07/pygal_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pygal 4 | 5 | x_time = [] 6 | out_octets = [] 7 | out_packets = [] 8 | in_octets = [] 9 | in_packets = [] 10 | 11 | with open('results.txt', 'r') as f: 12 | for line in f.readlines(): 13 | # eval(line) reads in each line as dictionary instead of string 14 | line = eval(line) 15 | x_time.append(line['Time']) 16 | out_packets.append(float(line['Gig0-0_Out_uPackets'])) 17 | out_octets.append(float(line['Gig0-0_Out_Octet'])) 18 | in_packets.append(float(line['Gig0-0_In_uPackets'])) 19 | in_octets.append(float(line['Gig0-0_In_Octet'])) 20 | 21 | line_chart = pygal.Line() 22 | line_chart.title = "Router 1 Gig0/0" 23 | line_chart.x_labels = x_time 24 | line_chart.add('out_octets', out_octets) 25 | line_chart.add('out_packets', out_packets) 26 | line_chart.add('in_octets', in_octets) 27 | line_chart.add('in_packets', in_packets) 28 | line_chart.render_to_file('pygal_example_2.svg') 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Chapter07/pygal_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pygal 4 | 5 | line_chart = pygal.Pie() 6 | line_chart.title = "Protocol Breakdown" 7 | line_chart.add('TCP', 15) 8 | line_chart.add('UDP', 30) 9 | line_chart.add('ICMP', 45) 10 | line_chart.add('Others', 10) 11 | line_chart.render_to_file('pygal_example_3.svg') 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Chapter07/pysnmp_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env/python3 2 | 3 | from pysnmp.entity.rfc3413.oneliner import cmdgen 4 | 5 | cmdGen = cmdgen.CommandGenerator() 6 | 7 | system_up_time_oid = "1.3.6.1.2.1.1.3.0" 8 | cisco_contact_info_oid = "1.3.6.1.4.1.9.2.1.61.0" 9 | 10 | errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd( 11 | cmdgen.CommunityData('secret'), 12 | cmdgen.UdpTransportTarget(('192.168.2.218', 161)), 13 | system_up_time_oid, 14 | cisco_contact_info_oid 15 | ) 16 | 17 | # Check for errors and print out results 18 | if errorIndication: 19 | print(errorIndication) 20 | else: 21 | if errorStatus: 22 | print('%s at %s' % ( 23 | errorStatus.prettyPrint(), 24 | errorIndex and varBinds[int(errorIndex)-1] or '?' 25 | ) 26 | ) 27 | else: 28 | for name, val in varBinds: 29 | print('%s = %s' % (name.prettyPrint(), str(val))) 30 | 31 | -------------------------------------------------------------------------------- /Chapter07/pysnmp_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env/python3 2 | 3 | from pysnmp.entity.rfc3413.oneliner import cmdgen 4 | 5 | cmdGen = cmdgen.CommandGenerator() 6 | 7 | system_name = '1.3.6.1.2.1.1.5.0' 8 | system_uptime = '1.3.6.1.2.1.1.3.0' 9 | 10 | errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd( 11 | cmdgen.CommunityData('secret'), 12 | cmdgen.UdpTransportTarget(('192.168.2.218', 161)), 13 | system_name, 14 | system_uptime 15 | ) 16 | 17 | # Check for errors and print out results 18 | if errorIndication: 19 | print(errorIndication) 20 | else: 21 | if errorStatus: 22 | print('%s at %s' % ( 23 | errorStatus.prettyPrint(), 24 | errorIndex and varBinds[int(errorIndex)-1] or '?' 25 | ) 26 | ) 27 | else: 28 | for name, val in varBinds: 29 | print('%s = %s' % (name.prettyPrint(), str(val))) 30 | 31 | -------------------------------------------------------------------------------- /Chapter07/pysnmp_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | from pysnmp.entity.rfc3413.oneliner import cmdgen 3 | import datetime 4 | 5 | cmdGen = cmdgen.CommandGenerator() 6 | 7 | host = '192.168.2.218' 8 | community = 'secret' 9 | 10 | # Hostname OID 11 | system_name = '1.3.6.1.2.1.1.5.0' 12 | 13 | # Interface OID 14 | gig0_0_in_oct = '1.3.6.1.2.1.2.2.1.10.1' 15 | gig0_0_in_uPackets = '1.3.6.1.2.1.2.2.1.11.1' 16 | gig0_0_out_oct = '1.3.6.1.2.1.2.2.1.16.1' 17 | gig0_0_out_uPackets = '1.3.6.1.2.1.2.2.1.17.1' 18 | 19 | 20 | def snmp_query(host, community, oid): 21 | errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd( 22 | cmdgen.CommunityData(community), 23 | cmdgen.UdpTransportTarget((host, 161)), 24 | oid 25 | ) 26 | 27 | # Check for errors and print out results 28 | if errorIndication: 29 | print(errorIndication) 30 | else: 31 | if errorStatus: 32 | print('%s at %s' % ( 33 | errorStatus.prettyPrint(), 34 | errorIndex and varBinds[int(errorIndex)-1] or '?' 35 | ) 36 | ) 37 | else: 38 | for name, val in varBinds: 39 | return(str(val)) 40 | 41 | result = {} 42 | result['Time'] = datetime.datetime.utcnow().isoformat() 43 | result['hostname'] = snmp_query(host, community, system_name) 44 | result['Gig0-0_In_Octet'] = snmp_query(host, community, gig0_0_in_oct) 45 | result['Gig0-0_In_uPackets'] = snmp_query(host, community, gig0_0_in_uPackets) 46 | result['Gig0-0_Out_Octet'] = snmp_query(host, community, gig0_0_out_oct) 47 | result['Gig0-0_Out_uPackets'] = snmp_query(host, community, gig0_0_out_uPackets) 48 | 49 | with open('/home/echou/Master_Python_Networking/Chapter7/results.txt', 'a') as f: 50 | f.write(str(result)) 51 | f.write('\n') 52 | 53 | -------------------------------------------------------------------------------- /Chapter07/results.txt: -------------------------------------------------------------------------------- 1 | {'Gig0-0_In_Octet': '3990616', 'Gig0-0_Out_uPackets': '60077', 'Gig0-0_In_uPackets': '42229', 'Gig0-0_Out_Octet': '5228254', 'Time': '2017-03-06T02:34:02.146245', 'hostname': 'iosv-1.virl.info'} 2 | {'Gig0-0_Out_uPackets': '60095', 'hostname': 'iosv-1.virl.info', 'Gig0-0_Out_Octet': '5229721', 'Time': '2017-03-06T02:35:02.072340', 'Gig0-0_In_Octet': '3991754', 'Gig0-0_In_uPackets': '42242'} 3 | {'hostname': 'iosv-1.virl.info', 'Gig0-0_Out_Octet': '5231484', 'Gig0-0_In_Octet': '3993129', 'Time': '2017-03-06T02:36:02.753134', 'Gig0-0_In_uPackets': '42257', 'Gig0-0_Out_uPackets': '60116'} 4 | {'Gig0-0_In_Octet': '3994504', 'Time': '2017-03-06T02:37:02.146894', 'Gig0-0_In_uPackets': '42272', 'Gig0-0_Out_uPackets': '60136', 'Gig0-0_Out_Octet': '5233187', 'hostname': 'iosv-1.virl.info'} 5 | {'Gig0-0_In_uPackets': '42284', 'Time': '2017-03-06T02:38:01.915432', 'Gig0-0_In_Octet': '3995585', 'Gig0-0_Out_Octet': '5234656', 'Gig0-0_Out_uPackets': '60154', 'hostname': 'iosv-1.virl.info'} 6 | {'Gig0-0_Out_Octet': '5236419', 'Time': '2017-03-06T02:39:01.646927', 'hostname': 'iosv-1.virl.info', 'Gig0-0_Out_uPackets': '60175', 'Gig0-0_In_Octet': '3996960', 'Gig0-0_In_uPackets': '42299'} 7 | {'Gig0-0_In_uPackets': '42311', 'hostname': 'iosv-1.virl.info', 'Time': '2017-03-06T02:40:02.456579', 'Gig0-0_Out_uPackets': '60193', 'Gig0-0_In_Octet': '3998041', 'Gig0-0_Out_Octet': '5237888'} 8 | {'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '3999414', 'Gig0-0_In_uPackets': '42326', 'Gig0-0_Out_uPackets': '60215', 'Time': '2017-03-06T02:41:02.294267', 'Gig0-0_Out_Octet': '5239725'} 9 | {'Gig0-0_Out_Octet': '5241486', 'Gig0-0_Out_uPackets': '60236', 'Gig0-0_In_uPackets': '42341', 'Time': '2017-03-06T02:42:01.966146', 'Gig0-0_In_Octet': '4000786', 'hostname': 'iosv-1.virl.info'} 10 | {'Time': '2017-03-06T02:43:01.731416', 'Gig0-0_Out_uPackets': '60254', 'Gig0-0_Out_Octet': '5242952', 'Gig0-0_In_Octet': '4001865', 'Gig0-0_In_uPackets': '42353', 'hostname': 'iosv-1.virl.info'} 11 | {'Gig0-0_Out_Octet': '5244653', 'Gig0-0_Out_uPackets': '60274', 'Gig0-0_In_uPackets': '42368', 'hostname': 'iosv-1.virl.info', 'Time': '2017-03-06T02:44:02.521641', 'Gig0-0_In_Octet': '4003237'} 12 | {'Gig0-0_Out_Octet': '5246413', 'Gig0-0_Out_uPackets': '60295', 'Time': '2017-03-06T02:45:02.228500', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '4004610', 'Gig0-0_In_uPackets': '42383'} 13 | {'Gig0-0_Out_uPackets': '60313', 'Gig0-0_Out_Octet': '5247880', 'Gig0-0_In_uPackets': '42395', 'hostname': 'iosv-1.virl.info', 'Time': '2017-03-06T02:46:01.960311', 'Gig0-0_In_Octet': '4005688'} 14 | {'Gig0-0_Out_uPackets': '60334', 'Gig0-0_Out_Octet': '5249643', 'Gig0-0_In_uPackets': '42410', 'Gig0-0_In_Octet': '4007063', 'hostname': 'iosv-1.virl.info', 'Time': '2017-03-06T02:47:01.861698'} 15 | {'hostname': 'iosv-1.virl.info', 'Gig0-0_In_uPackets': '42422', 'Gig0-0_Out_Octet': '5251109', 'Gig0-0_In_Octet': '4008142', 'Time': '2017-03-06T02:48:01.756321', 'Gig0-0_Out_uPackets': '60352'} 16 | {'Gig0-0_Out_Octet': '5252807', 'Time': '2017-03-06T02:49:01.902937', 'Gig0-0_In_Octet': '4009512', 'Gig0-0_Out_uPackets': '60372', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_uPackets': '42437'} 17 | {'Gig0-0_In_Octet': '4010588', 'Time': '2017-03-06T02:50:01.770566', 'Gig0-0_In_uPackets': '42449', 'Gig0-0_Out_uPackets': '60390', 'hostname': 'iosv-1.virl.info', 'Gig0-0_Out_Octet': '5254271'} 18 | {'Gig0-0_In_uPackets': '42464', 'Gig0-0_Out_Octet': '5256106', 'Time': '2017-03-06T02:51:02.005671', 'Gig0-0_In_Octet': '4011958', 'hostname': 'iosv-1.virl.info', 'Gig0-0_Out_uPackets': '60412'} 19 | {'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '4013328', 'Time': '2017-03-06T02:52:01.809311', 'Gig0-0_Out_uPackets': '60432', 'Gig0-0_In_uPackets': '42479', 'Gig0-0_Out_Octet': '5257804'} 20 | {'Gig0-0_Out_Octet': '5259271', 'Gig0-0_In_uPackets': '42491', 'Time': '2017-03-06T02:53:01.530597', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '4014406', 'Gig0-0_Out_uPackets': '60450'} 21 | {'Gig0-0_Out_uPackets': '60471', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_uPackets': '42506', 'Time': '2017-03-06T02:54:02.209868', 'Gig0-0_In_Octet': '4015781', 'Gig0-0_Out_Octet': '5261034'} 22 | {'Gig0-0_Out_uPackets': '60489', 'Gig0-0_In_uPackets': '42518', 'hostname': 'iosv-1.virl.info', 'Gig0-0_Out_Octet': '5262503', 'Time': '2017-03-06T02:55:02.205098', 'Gig0-0_In_Octet': '4016862'} 23 | {'hostname': 'iosv-1.virl.info', 'Gig0-0_In_uPackets': '42533', 'Gig0-0_Out_Octet': '5264263', 'Gig0-0_Out_uPackets': '60510', 'Time': '2017-03-06T02:56:02.246366', 'Gig0-0_In_Octet': '4018235'} 24 | {'Gig0-0_Out_Octet': '5266024', 'Time': '2017-03-06T02:57:02.057769', 'Gig0-0_Out_uPackets': '60531', 'Gig0-0_In_Octet': '4019607', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_uPackets': '42548'} 25 | {'Gig0-0_In_uPackets': '42560', 'Gig0-0_Out_uPackets': '60548', 'Time': '2017-03-06T02:58:01.759841', 'Gig0-0_Out_Octet': '5267430', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '4020686'} 26 | {'Gig0-0_Out_uPackets': '60569', 'Time': '2017-03-06T02:59:01.500577', 'Gig0-0_Out_Octet': '5269191', 'Gig0-0_In_uPackets': '42575', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '4022058'} 27 | {'Gig0-0_Out_uPackets': '60588', 'Gig0-0_In_uPackets': '42587', 'Gig0-0_Out_Octet': '5270737', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '4023139', 'Time': '2017-03-06T03:00:02.368872'} 28 | {'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '4024512', 'Gig0-0_In_uPackets': '42602', 'Time': '2017-03-06T03:01:02.150037', 'Gig0-0_Out_Octet': '5272497', 'Gig0-0_Out_uPackets': '60609'} 29 | {'Gig0-0_Out_Octet': '5274258', 'Gig0-0_Out_uPackets': '60630', 'Time': '2017-03-06T03:02:01.869521', 'Gig0-0_In_uPackets': '42617', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '4025884'} 30 | {'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '4026965', 'Gig0-0_Out_Octet': '5275727', 'Gig0-0_In_uPackets': '42629', 'Time': '2017-03-06T03:03:01.570950', 'Gig0-0_Out_uPackets': '60648'} 31 | {'hostname': 'iosv-1.virl.info', 'Gig0-0_In_uPackets': '42644', 'Time': '2017-03-06T03:04:02.255872', 'Gig0-0_Out_uPackets': '60669', 'Gig0-0_Out_Octet': '5277487', 'Gig0-0_In_Octet': '4028338'} 32 | {'hostname': 'iosv-1.virl.info', 'Gig0-0_Out_uPackets': '60690', 'Gig0-0_Out_Octet': '5279248', 'Time': '2017-03-06T03:05:01.962176', 'Gig0-0_In_uPackets': '42659', 'Gig0-0_In_Octet': '4029710'} 33 | {'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '4030791', 'Gig0-0_Out_Octet': '5280717', 'Time': '2017-03-06T03:06:01.661128', 'Gig0-0_In_uPackets': '42671', 'Gig0-0_Out_uPackets': '60708'} 34 | {'Gig0-0_In_Octet': '4032164', 'Time': '2017-03-06T03:07:02.336401', 'hostname': 'iosv-1.virl.info', 'Gig0-0_Out_uPackets': '60728', 'Gig0-0_Out_Octet': '5282417', 'Gig0-0_In_uPackets': '42686'} 35 | {'Gig0-0_Out_Octet': '5283884', 'Gig0-0_In_Octet': '4033242', 'Gig0-0_In_uPackets': '42698', 'Time': '2017-03-06T03:08:02.144784', 'hostname': 'iosv-1.virl.info', 'Gig0-0_Out_uPackets': '60746'} 36 | {'Gig0-0_Out_Octet': '5285721', 'Time': '2017-03-06T03:09:02.042190', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '4034615', 'Gig0-0_In_uPackets': '42713', 'Gig0-0_Out_uPackets': '60768'} 37 | {'Time': '2017-03-06T03:10:01.723442', 'Gig0-0_Out_uPackets': '60789', 'hostname': 'iosv-1.virl.info', 'Gig0-0_Out_Octet': '5287479', 'Gig0-0_In_Octet': '4035985', 'Gig0-0_In_uPackets': '42728'} 38 | {'Gig0-0_In_uPackets': '42740', 'Time': '2017-03-06T03:11:02.393732', 'Gig0-0_Out_uPackets': '60807', 'Gig0-0_In_Octet': '4037063', 'hostname': 'iosv-1.virl.info', 'Gig0-0_Out_Octet': '5288946'} 39 | {'Gig0-0_Out_uPackets': '60828', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_uPackets': '42755', 'Gig0-0_In_Octet': '4038438', 'Time': '2017-03-06T03:12:02.123784', 'Gig0-0_Out_Octet': '5290709'} 40 | {'Gig0-0_In_Octet': '4039813', 'Gig0-0_Out_uPackets': '60849', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_uPackets': '42770', 'Time': '2017-03-06T03:13:01.795363', 'Gig0-0_Out_Octet': '5292472'} 41 | {'Gig0-0_Out_Octet': '5293938', 'Gig0-0_Out_uPackets': '60867', 'Gig0-0_In_uPackets': '42782', 'Gig0-0_In_Octet': '4040892', 'Time': '2017-03-06T03:14:01.476548', 'hostname': 'iosv-1.virl.info'} 42 | {'Time': '2017-03-06T03:15:02.254936', 'hostname': 'iosv-1.virl.info', 'Gig0-0_Out_uPackets': '60888', 'Gig0-0_Out_Octet': '5295699', 'Gig0-0_In_uPackets': '42797', 'Gig0-0_In_Octet': '4042264'} 43 | {'Gig0-0_In_Octet': '4043343', 'Gig0-0_In_uPackets': '42809', 'Gig0-0_Out_uPackets': '60905', 'Time': '2017-03-06T03:16:02.177917', 'Gig0-0_Out_Octet': '5297105', 'hostname': 'iosv-1.virl.info'} 44 | {'Gig0-0_Out_uPackets': '60926', 'hostname': 'iosv-1.virl.info', 'Time': '2017-03-06T03:17:01.881498', 'Gig0-0_In_uPackets': '42824', 'Gig0-0_Out_Octet': '5298863', 'Gig0-0_In_Octet': '4044713'} 45 | {'hostname': 'iosv-1.virl.info', 'Gig0-0_Out_Octet': '5300621', 'Gig0-0_In_Octet': '4046083', 'Time': '2017-03-06T03:18:01.558740', 'Gig0-0_Out_uPackets': '60947', 'Gig0-0_In_uPackets': '42839'} 46 | {'Gig0-0_Out_uPackets': '60965', 'Gig0-0_Out_Octet': '5302088', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '4047161', 'Time': '2017-03-06T03:19:02.253796', 'Gig0-0_In_uPackets': '42851'} 47 | {'Gig0-0_Out_uPackets': '60985', 'Time': '2017-03-06T03:20:01.966435', 'Gig0-0_Out_Octet': '5303791', 'Gig0-0_In_uPackets': '42866', 'Gig0-0_In_Octet': '4048536', 'hostname': 'iosv-1.virl.info'} 48 | {'Gig0-0_In_uPackets': '42878', 'Time': '2017-03-06T03:21:01.639857', 'Gig0-0_Out_uPackets': '61004', 'Gig0-0_Out_Octet': '5305337', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '4049617'} 49 | {'Gig0-0_Out_Octet': '5307097', 'Gig0-0_Out_uPackets': '61025', 'Time': '2017-03-06T03:22:02.552536', 'hostname': 'iosv-1.virl.info', 'Gig0-0_In_Octet': '4050990', 'Gig0-0_In_uPackets': '42893'} 50 | {'Gig0-0_Out_Octet': '5308855', 'hostname': 'iosv-1.virl.info', 'Gig0-0_Out_uPackets': '61046', 'Gig0-0_In_uPackets': '42909', 'Time': '2017-03-06T03:23:02.386113', 'Gig0-0_In_Octet': '4052420'} 51 | -------------------------------------------------------------------------------- /Chapter08/chapter8_graphviz_1.gv: -------------------------------------------------------------------------------- 1 | graph my_network { 2 | core -- distribution; 3 | distribution -- access; 4 | } 5 | -------------------------------------------------------------------------------- /Chapter08/chapter8_gv_1.gv: -------------------------------------------------------------------------------- 1 | graph my_network { 2 | core -- distribution; 3 | distribution -- access1; 4 | distribution -- access2; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter08/chapter8_gv_2.gv: -------------------------------------------------------------------------------- 1 | digraph my_network { 2 | node [shape=box]; 3 | size = "50 30"; 4 | core -> distribution [label="2x10G"]; 5 | distribution -> access1 [label="1G"]; 6 | distribution -> access2 [label="1G"]; 7 | } 8 | -------------------------------------------------------------------------------- /Chapter08/chapter8_ntop_1.py: -------------------------------------------------------------------------------- 1 | # Import modules for CGI handling 2 | import cgi, cgitb 3 | import ntop 4 | 5 | # Parse URL 6 | cgitb.enable(); 7 | 8 | form = cgi.FieldStorage(); 9 | name = form.getvalue('Name', default="Eric") 10 | 11 | version = ntop.version() 12 | os = ntop.os() 13 | uptime = ntop.uptime() 14 | 15 | ntop.printHTMLHeader('Mastering Pyton Networking', 1, 0) 16 | ntop.sendString("Hello, "+ name +"
") 17 | ntop.sendString("Ntop Information: %s %s %s" % (version, os, uptime)) 18 | ntop.printHTMLFooter() 19 | 20 | -------------------------------------------------------------------------------- /Chapter08/chapter8_ntop_2.py: -------------------------------------------------------------------------------- 1 | import ntop, interface, json 2 | 3 | ifnames = [] 4 | try: 5 | for i in range(interface.numInterfaces()): 6 | ifnames.append(interface.name(i)) 7 | 8 | except Exception as inst: 9 | print type(inst) # the exception instance 10 | print inst.args # arguments stored in .args 11 | print inst # __str__ allows args to printed directly 12 | 13 | ntop.printHTMLHeader('Mastering Python Networking', 1, 0) 14 | ntop.sendString("Here are my interfaces:
") 15 | ntop.sendString(json.dumps(ifnames, sort_keys=True, indent=4)) 16 | ntop.printHTMLFooter() 17 | 18 | -------------------------------------------------------------------------------- /Chapter08/chapter8_sflowtool_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys, re 4 | 5 | for line in iter(sys.stdin.readline, ''): 6 | if re.search('agent ', line): 7 | print(line.strip()) 8 | 9 | 10 | -------------------------------------------------------------------------------- /Chapter08/cisco_config_lldp.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Enable LLDP 3 | hosts: "devices" 4 | gather_facts: false 5 | connection: network_cli 6 | 7 | 8 | tasks: 9 | - name: enable LLDP service 10 | ios_lldp: 11 | state: present 12 | 13 | register: output 14 | 15 | - name: show output 16 | debug: 17 | var: output 18 | 19 | -------------------------------------------------------------------------------- /Chapter08/cisco_config_netflow.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Configure NetFlow 3 | hosts: "devices" 4 | gather_facts: false 5 | connection: network_cli 6 | 7 | tasks: 8 | - name: configure netflow export 9 | ios_config: 10 | lines: 11 | - ip flow-export destination 172.16.1.123 5556 vrf Mgmt-intf 12 | - ip flow-export version 5 13 | 14 | - name: configure flow export on Gi0/0 15 | ios_config: 16 | lines: 17 | - ip flow ingress 18 | - ip flow egress 19 | parents: interface GigabitEthernet0/0 20 | 21 | - name: configure flow export on Gi0/1 22 | ios_config: 23 | lines: 24 | - ip flow ingress 25 | - ip flow egress 26 | parents: interface GigabitEthernet0/1 27 | 28 | - name: configure flow export on Gi0/2 29 | ios_config: 30 | lines: 31 | - ip flow ingress 32 | - ip flow egress 33 | parents: interface GigabitEthernet0/2 34 | 35 | - name: Configure NetFlow on edge-devices 36 | hosts: "edge-devices" 37 | gather_facts: false 38 | connection: network_cli 39 | 40 | 41 | tasks: 42 | - name: configure flow export on Gi0/3 43 | ios_config: 44 | lines: 45 | - ip flow ingress 46 | - ip flow egress 47 | parents: interface GigabitEthernet0/3 48 | 49 | - name: configure flow export on Gi0/4 50 | ios_config: 51 | lines: 52 | - ip flow ingress 53 | - ip flow egress 54 | parents: interface GigabitEthernet0/4 55 | 56 | 57 | -------------------------------------------------------------------------------- /Chapter08/cisco_discover_lldp.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Query LLDP Neighbors 3 | hosts: "devices" 4 | gather_facts: false 5 | connection: network_cli 6 | 7 | 8 | tasks: 9 | - name: Query for LLDP Neighbors 10 | ios_command: 11 | commands: show lldp neighbors 12 | 13 | register: output 14 | 15 | - name: show output 16 | debug: 17 | var: output 18 | 19 | - name: copy output to file 20 | copy: content="{{ output.stdout_lines }}" dest="./tmp/{{ inventory_hostname }}_lldp_output.txt" 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter08/cisco_graph_lldp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import glob, re 4 | from graphviz import Digraph, Source 5 | 6 | pattern = re.compile('Gi0/[1234]') 7 | 8 | device_lldp_neighbors = [] 9 | 10 | # walk thru files in ./tmp directory 11 | for file_name in glob.glob('tmp-2/*'): 12 | # device name 13 | device = file_name.split('/')[1].split('_')[0] 14 | print("device: " + device) 15 | with open(file_name, 'r') as f: 16 | for line in f.readlines(): 17 | line = eval(line) #eval the line as list 18 | for item in line[0]: 19 | # only look for GigEth other than Gi0/0 20 | if re.search(pattern, item): 21 | print(" neighbors: " + item.split()[0].split('.')[0]) 22 | device_lldp_neighbors.append((device, item.split()[0].split('.')[0])) 23 | 24 | print("*" * 10) 25 | print("Edges: " + str(device_lldp_neighbors)) 26 | 27 | my_graph = Digraph("My_Network") 28 | my_graph.edge("Client", "r6-edge") 29 | my_graph.edge("r5-tor", "Server") 30 | 31 | # construct the edge relationships 32 | for neighbors in device_lldp_neighbors: 33 | node1, node2 = neighbors 34 | my_graph.edge(node1, node2) 35 | 36 | # Insert arbitrary DOT language commands 37 | # such as the rank=same command 38 | source = my_graph.source 39 | original_text = "digraph My_Network {" 40 | new_text = 'digraph My_Network {\n{rank=same Client "r6-edge"}\n{rank=same r1 r2 r3}\n' 41 | new_source = source.replace(original_text, new_text) 42 | print(new_source) 43 | new_graph = Source(new_source) 44 | new_graph.render("output/chapter8_lldp_graph.gv") 45 | 46 | -------------------------------------------------------------------------------- /Chapter08/host_vars/r1: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.218 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco 10 | -------------------------------------------------------------------------------- /Chapter08/host_vars/r2: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.219 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco 10 | -------------------------------------------------------------------------------- /Chapter08/host_vars/r3: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.220 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco -------------------------------------------------------------------------------- /Chapter08/host_vars/r4: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.221 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco 10 | -------------------------------------------------------------------------------- /Chapter08/host_vars/r5-tor: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.221 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco 10 | -------------------------------------------------------------------------------- /Chapter08/host_vars/r6-edge: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_host: 192.168.2.222 3 | ansible_user: cisco 4 | ansible_ssh_pass: cisco 5 | ansible_connection: network_cli 6 | ansible_network_os: ios 7 | ansbile_become: yes 8 | ansible_become_method: enable 9 | ansible_become_pass: cisco -------------------------------------------------------------------------------- /Chapter08/hosts: -------------------------------------------------------------------------------- 1 | [devices] 2 | r1 3 | r2 4 | r3 5 | r5-tor 6 | r6-edge 7 | 8 | [edge-devices] 9 | r5-tor 10 | r6-edge 11 | -------------------------------------------------------------------------------- /Chapter08/netFlow_v5_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Modified from Brian "devicenull" Rak's example on: 4 | # http://blog.devicenull.org/2013/09/04/python-netflow-v5-parser.html 5 | # 6 | 7 | from __future__ import print_function 8 | import socket, struct 9 | from socket import inet_ntoa 10 | 11 | SIZE_OF_HEADER = 24 12 | SIZE_OF_RECORD = 48 13 | 14 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 15 | sock.bind(('0.0.0.0', 9995)) 16 | 17 | while True: 18 | buf, addr = sock.recvfrom(1500) 19 | 20 | (version, count) = struct.unpack('!HH',buf[0:4]) 21 | (sys_uptime, unix_secs, unix_nsecs, flow_sequence) = struct.unpack('!IIII', buf[4:20]) 22 | (engine_type, engine_id, sampling_interval) = struct.unpack('!BBH', buf[20:24]) 23 | print( "Headers: ", 24 | "\nNetFlow Version: " + str(version), 25 | "\nFlow Count: " + str(count), 26 | "\nSystem Uptime: " + str(sys_uptime), 27 | "\nEpoch Time in seconds: " + str(unix_secs), 28 | "\nEpoch Time in nanoseconds: " + str(unix_nsecs), 29 | "\nSequence counter of total flow: " + str(flow_sequence), 30 | ) 31 | 32 | #Can also use socket.htohl() to convert network to host byte order 33 | #uptime = socket.ntohl(struct.unpack('I',buf[4:8])[0]) 34 | #epochseconds = socket.ntohl(struct.unpack('I',buf[8:12])[0]) 35 | 36 | #Flowdata 37 | nfdata = {} 38 | for i in range(0, count): 39 | try: 40 | base = SIZE_OF_HEADER+(i*SIZE_OF_RECORD) 41 | 42 | data = struct.unpack('!IIIIHH',buf[base+16:base+36]) 43 | input_int, output_int = struct.unpack('!HH', buf[base+12:base+16]) 44 | nfdata[i] = {} 45 | nfdata[i]['saddr'] = inet_ntoa(buf[base+0:base+4]) 46 | nfdata[i]['daddr'] = inet_ntoa(buf[base+4:base+8]) 47 | nfdata[i]['pcount'] = data[0] 48 | nfdata[i]['bcount'] = data[1] 49 | nfdata[i]['stime'] = data[2] 50 | nfdata[i]['etime'] = data[3] 51 | nfdata[i]['sport'] = data[4] 52 | nfdata[i]['dport'] = data[5] 53 | print(i, " {0}:{1} -> {2}:{3} {4} packts {5} bytes".format( 54 | nfdata[i]['saddr'], 55 | nfdata[i]['sport'], 56 | nfdata[i]['daddr'], 57 | nfdata[i]['dport'], 58 | nfdata[i]['pcount'], 59 | nfdata[i]['bcount']), 60 | ) 61 | 62 | except: 63 | print("Failed to parse flow record: " + str(i)) 64 | continue 65 | 66 | print("*" * 10) 67 | -------------------------------------------------------------------------------- /Chapter08/output/chapter8_gv_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter08/output/chapter8_gv_1.png -------------------------------------------------------------------------------- /Chapter08/output/chapter8_gv_2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter08/output/chapter8_gv_2.pdf -------------------------------------------------------------------------------- /Chapter08/output/chapter8_gv_3.gv: -------------------------------------------------------------------------------- 1 | // My Network 2 | digraph { 3 | core 4 | distribution 5 | access1 6 | access2 7 | core -> distribution 8 | distribution -> access1 9 | distribution -> access2 10 | } 11 | -------------------------------------------------------------------------------- /Chapter08/output/chapter8_gv_3.gv.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter08/output/chapter8_gv_3.gv.pdf -------------------------------------------------------------------------------- /Chapter08/output/chapter8_lldp_graph.gv: -------------------------------------------------------------------------------- 1 | digraph My_Network { 2 | {rank=same Client "r6-edge"} 3 | {rank=same r1 r2 r3} 4 | 5 | Client -> "r6-edge" 6 | "r5-tor" -> Server 7 | "r6-edge" -> r2 8 | "r6-edge" -> r1 9 | "r6-edge" -> r3 10 | r2 -> r5 11 | r2 -> r6 12 | r3 -> r5 13 | r3 -> r6 14 | "r5-tor" -> r3 15 | "r5-tor" -> r1 16 | "r5-tor" -> r2 17 | r1 -> r5 18 | r1 -> r6 19 | } 20 | -------------------------------------------------------------------------------- /Chapter08/output/chapter8_lldp_graph.gv.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter08/output/chapter8_lldp_graph.gv.pdf -------------------------------------------------------------------------------- /Chapter08/output/chapter8_lldp_graph.gv_neato.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter08/output/chapter8_lldp_graph.gv_neato.pdf -------------------------------------------------------------------------------- /Chapter08/output/chapter8_lldp_graph.gv_v1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter08/output/chapter8_lldp_graph.gv_v1.pdf -------------------------------------------------------------------------------- /Chapter08/tmp/r1_lldp_output.txt: -------------------------------------------------------------------------------- 1 | [["Capability codes:", " (R) Router, (B) Bridge, (T) Telephone, (C) DOCSIS Cable Device", " (W) WLAN Access Point, (P) Repeater, (S) Station, (O) Other", "", "Device ID Local Intf Hold-time Capability Port ID", "veos01 Gi0/0 120 B Ethernet1", "r2.virl.info Gi0/0 120 R Gi0/0", "r3.virl.info Gi0/0 120 R Gi0/0", "r5.virl.info Gi0/2 120 R Gi0/1", "r5.virl.info Gi0/0 120 R Gi0/0", "r6.virl.info Gi0/0 120 R Gi0/0", "r6.virl.info Gi0/1 120 R Gi0/1", "", "Total entries displayed: 7"]] -------------------------------------------------------------------------------- /Chapter08/tmp/r2_lldp_output.txt: -------------------------------------------------------------------------------- 1 | [["Capability codes:", " (R) Router, (B) Bridge, (T) Telephone, (C) DOCSIS Cable Device", " (W) WLAN Access Point, (P) Repeater, (S) Station, (O) Other", "", "Device ID Local Intf Hold-time Capability Port ID", "veos01 Gi0/0 120 B Ethernet1", "r1.virl.info Gi0/0 120 R Gi0/0", "r5.virl.info Gi0/2 120 R Gi0/2", "r3.virl.info Gi0/0 120 R Gi0/0", "r5.virl.info Gi0/0 120 R Gi0/0", "r6.virl.info Gi0/0 120 R Gi0/0", "r6.virl.info Gi0/1 120 R Gi0/2", "", "Total entries displayed: 7"]] -------------------------------------------------------------------------------- /Chapter08/tmp/r3_lldp_output.txt: -------------------------------------------------------------------------------- 1 | [["Capability codes:", " (R) Router, (B) Bridge, (T) Telephone, (C) DOCSIS Cable Device", " (W) WLAN Access Point, (P) Repeater, (S) Station, (O) Other", "", "Device ID Local Intf Hold-time Capability Port ID", "veos01 Gi0/0 120 B Ethernet1", "r5.virl.info Gi0/2 120 R Gi0/3", "r1.virl.info Gi0/0 120 R Gi0/0", "r2.virl.info Gi0/0 120 R Gi0/0", "r5.virl.info Gi0/0 120 R Gi0/0", "r6.virl.info Gi0/0 120 R Gi0/0", "r6.virl.info Gi0/1 120 R Gi0/3", "", "Total entries displayed: 7"]] -------------------------------------------------------------------------------- /Chapter08/tmp/r5-tor_lldp_output.txt: -------------------------------------------------------------------------------- 1 | [["Capability codes:", " (R) Router, (B) Bridge, (T) Telephone, (C) DOCSIS Cable Device", " (W) WLAN Access Point, (P) Repeater, (S) Station, (O) Other", "", "Device ID Local Intf Hold-time Capability Port ID", "veos01 Gi0/0 120 B Ethernet1", "r1.virl.info Gi0/0 120 R Gi0/0", "r3.virl.info Gi0/3 120 R Gi0/2", "r2.virl.info Gi0/0 120 R Gi0/0", "r3.virl.info Gi0/0 120 R Gi0/0", "r1.virl.info Gi0/1 120 R Gi0/2", "r2.virl.info Gi0/2 120 R Gi0/2", "r6.virl.info Gi0/0 120 R Gi0/0", "", "Total entries displayed: 8"]] -------------------------------------------------------------------------------- /Chapter08/tmp/r6-edge_lldp_output.txt: -------------------------------------------------------------------------------- 1 | [["Capability codes:", " (R) Router, (B) Bridge, (T) Telephone, (C) DOCSIS Cable Device", " (W) WLAN Access Point, (P) Repeater, (S) Station, (O) Other", "", "Device ID Local Intf Hold-time Capability Port ID", "veos01 Gi0/0 120 B Ethernet1", "r1.virl.info Gi0/0 120 R Gi0/0", "r2.virl.info Gi0/2 120 R Gi0/1", "r2.virl.info Gi0/0 120 R Gi0/0", "r1.virl.info Gi0/1 120 R Gi0/1", "r3.virl.info Gi0/0 120 R Gi0/0", "r3.virl.info Gi0/3 120 R Gi0/1", "r5.virl.info Gi0/0 120 R Gi0/0", "", "Total entries displayed: 8"]] -------------------------------------------------------------------------------- /Chapter09/TestApp/app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, url_for, jsonify, request 2 | from flask_sqlalchemy import SQLAlchemy 3 | 4 | app = Flask(__name__) 5 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///network.db' 6 | db = SQLAlchemy(app) 7 | 8 | @app.route('/') 9 | def home(): 10 | return "Hello Python Netowrking!" 11 | 12 | 13 | class ValidationError(ValueError): 14 | pass 15 | 16 | 17 | class Device(db.Model): 18 | __tablename__ = 'devices' 19 | id = db.Column(db.Integer, primary_key=True) 20 | hostname = db.Column(db.String(64), unique=True) 21 | loopback = db.Column(db.String(120), unique=True) 22 | mgmt_ip = db.Column(db.String(120), unique=True) 23 | role = db.Column(db.String(64)) 24 | vendor = db.Column(db.String(64)) 25 | os = db.Column(db.String(64)) 26 | 27 | def get_url(self): 28 | return url_for('get_device', id=self.id, _external=True) 29 | 30 | def export_data(self): 31 | return { 32 | 'self_url': self.get_url(), 33 | 'hostname': self.hostname, 34 | 'loopback': self.loopback, 35 | 'mgmt_ip': self.mgmt_ip, 36 | 'role': self.role, 37 | 'vendor': self.vendor, 38 | 'os': self.os 39 | } 40 | 41 | def import_data(self, data): 42 | try: 43 | self.hostname = data['hostname'] 44 | self.loopback = data['loopback'] 45 | self.mgmt_ip = data['mgmt_ip'] 46 | self.role = data['role'] 47 | self.vendor = data['vendor'] 48 | self.os = data['os'] 49 | except KeyError as e: 50 | raise ValidationError('Invalid device: missing ' + e.args[0]) 51 | return self 52 | 53 | 54 | @app.route('/devices/', methods=['GET']) 55 | def get_devices(): 56 | return jsonify({'device': [device.get_url() 57 | for device in Device.query.all()]}) 58 | 59 | 60 | @app.route('/devices/', methods=['GET']) 61 | def get_device(id): 62 | return jsonify(Device.query.get_or_404(id).export_data()) 63 | 64 | 65 | @app.route('/devices/', methods=['POST']) 66 | def new_device(): 67 | device = Device() 68 | device.import_data(request.json) 69 | db.session.add(device) 70 | db.session.commit() 71 | return jsonify({}), 201, {'Location': device.get_url()} 72 | 73 | 74 | @app.route('/devices/', methods=['PUT']) 75 | def edit_device(id): 76 | device = Device.query.get_or_404(id) 77 | device.import_data(request.json) 78 | db.session.add(device) 79 | db.session.commit() 80 | return jsonify({}) 81 | 82 | 83 | -------------------------------------------------------------------------------- /Chapter09/TestApp/app/network.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/77a03fd4e05818497ac8b0c8e10eabd5a447e2b1/Chapter09/TestApp/app/network.db -------------------------------------------------------------------------------- /Chapter09/TestApp/main.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | 3 | -------------------------------------------------------------------------------- /Chapter09/TestApp/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.1.1 2 | Flask-HTTPAuth==3.3.0 3 | Flask-SQLAlchemy==2.4.1 4 | Jinja2==2.10.1 5 | MarkupSafe==1.1.1 6 | Pygments==2.4.2 7 | SQLAlchemy==1.3.9 8 | Werkzeug==0.16.0 9 | httpie==1.0.3 10 | itsdangerous==1.1.0 11 | python-dateutil==2.8.0 12 | requests==2.20.1 -------------------------------------------------------------------------------- /Chapter09/TestApp/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | app="docker.test" 3 | docker build -t ${app} . 4 | docker run -d -p 8000:80 \ 5 | --name=${app} \ 6 | -v $PWD:/app ${app} 7 | -------------------------------------------------------------------------------- /Chapter09/TestApp/uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | module = main 3 | callable = app 4 | master = true 5 | 6 | -------------------------------------------------------------------------------- /Chapter09/chapter9_1.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | app = Flask(__name__) 3 | 4 | @app.route('/') 5 | def hello_networkers(): 6 | return 'Hello Networkers!' 7 | 8 | if __name__ == '__main__': 9 | app.run(host='0.0.0.0', debug=True) 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter09/chapter9_2.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | app = Flask(__name__) 3 | 4 | @app.route('/') 5 | def index(): 6 | return 'You are at index()' 7 | 8 | @app.route('/routers/') 9 | def routers(): 10 | return 'You are at routers()' 11 | 12 | if __name__ == '__main__': 13 | app.run(host='0.0.0.0', debug=True) 14 | 15 | 16 | -------------------------------------------------------------------------------- /Chapter09/chapter9_3.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | app = Flask(__name__) 3 | 4 | @app.route('/routers/') 5 | def router(hostname): 6 | return 'You are at %s' % hostname 7 | 8 | @app.route('/routers//interface/') 9 | def interface(hostname, interface_number): 10 | return 'You are at %s interface %d' % (hostname, interface_number) 11 | 12 | if __name__ == '__main__': 13 | app.run(host='0.0.0.0', debug=True) 14 | 15 | 16 | -------------------------------------------------------------------------------- /Chapter09/chapter9_4.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, url_for 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('//list_interfaces') 6 | def device(hostname): 7 | if hostname in routers: 8 | return 'Listing interfaces for %s' % hostname 9 | else: 10 | return 'Invalid hostname' 11 | 12 | routers = ['r1', 'r2', 'r3'] 13 | for router in routers: 14 | with app.test_request_context(): 15 | print(url_for('device', hostname=router)) 16 | 17 | if __name__ == '__main__': 18 | app.run(host='0.0.0.0', debug=True) 19 | 20 | 21 | -------------------------------------------------------------------------------- /Chapter09/chapter9_5.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify 2 | app = Flask(__name__) 3 | 4 | @app.route('/routers//interface/') 5 | def interface(hostname, interface_number): 6 | return jsonify(name=hostname, interface=interface_number) 7 | 8 | if __name__ == '__main__': 9 | app.run(host='0.0.0.0', debug=True) 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter09/chapter9_6.py: -------------------------------------------------------------------------------- 1 | # This example referenced Miguel Grinberg's code on Github: 2 | # https://github.com/miguelgrinberg/oreilly-flask-apis-video/commit/98855d48f52f4dc0f9728c841bdd0645810d708e 3 | # 4 | 5 | from flask import Flask, url_for, jsonify, request 6 | from flask_sqlalchemy import SQLAlchemy 7 | 8 | app = Flask(__name__) 9 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///network.db' 10 | db = SQLAlchemy(app) 11 | 12 | 13 | class ValidationError(ValueError): 14 | pass 15 | 16 | 17 | class Device(db.Model): 18 | __tablename__ = 'devices' 19 | id = db.Column(db.Integer, primary_key=True) 20 | hostname = db.Column(db.String(64), unique=True) 21 | loopback = db.Column(db.String(120), unique=True) 22 | mgmt_ip = db.Column(db.String(120), unique=True) 23 | role = db.Column(db.String(64)) 24 | vendor = db.Column(db.String(64)) 25 | os = db.Column(db.String(64)) 26 | 27 | def get_url(self): 28 | return url_for('get_device', id=self.id, _external=True) 29 | 30 | def export_data(self): 31 | return { 32 | 'self_url': self.get_url(), 33 | 'hostname': self.hostname, 34 | 'loopback': self.loopback, 35 | 'mgmt_ip': self.mgmt_ip, 36 | 'role': self.role, 37 | 'vendor': self.vendor, 38 | 'os': self.os 39 | } 40 | 41 | def import_data(self, data): 42 | try: 43 | self.hostname = data['hostname'] 44 | self.loopback = data['loopback'] 45 | self.mgmt_ip = data['mgmt_ip'] 46 | self.role = data['role'] 47 | self.vendor = data['vendor'] 48 | self.os = data['os'] 49 | except KeyError as e: 50 | raise ValidationError('Invalid device: missing ' + e.args[0]) 51 | return self 52 | 53 | 54 | @app.route('/devices/', methods=['GET']) 55 | def get_devices(): 56 | return jsonify({'device': [device.get_url() 57 | for device in Device.query.all()]}) 58 | 59 | 60 | @app.route('/devices/', methods=['GET']) 61 | def get_device(id): 62 | return jsonify(Device.query.get_or_404(id).export_data()) 63 | 64 | 65 | @app.route('/devices/', methods=['POST']) 66 | def new_device(): 67 | device = Device() 68 | device.import_data(request.json) 69 | db.session.add(device) 70 | db.session.commit() 71 | return jsonify({}), 201, {'Location': device.get_url()} 72 | 73 | 74 | @app.route('/devices/', methods=['PUT']) 75 | def edit_device(id): 76 | device = Device.query.get_or_404(id) 77 | device.import_data(request.json) 78 | db.session.add(device) 79 | db.session.commit() 80 | return jsonify({}) 81 | 82 | 83 | if __name__ == '__main__': 84 | db.create_all() 85 | app.run(host='0.0.0.0', debug=True) 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /Chapter09/chapter9_7.py: -------------------------------------------------------------------------------- 1 | # This example referenced Miguel Grinberg's code on Github: 2 | # https://github.com/miguelgrinberg/oreilly-flask-apis-video/commit/98855d48f52f4dc0f9728c841bdd0645810d708e 3 | # 4 | 5 | from flask import Flask, url_for, jsonify, request 6 | from flask_sqlalchemy import SQLAlchemy 7 | from chapter9_pexpect_1 import show_version 8 | 9 | app = Flask(__name__) 10 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///network.db' 11 | db = SQLAlchemy(app) 12 | 13 | class ValidationError(ValueError): 14 | pass 15 | 16 | 17 | class Device(db.Model): 18 | __tablename__ = 'devices' 19 | id = db.Column(db.Integer, primary_key=True) 20 | hostname = db.Column(db.String(64), unique=True) 21 | loopback = db.Column(db.String(120), unique=True) 22 | mgmt_ip = db.Column(db.String(120), unique=True) 23 | role = db.Column(db.String(64)) 24 | vendor = db.Column(db.String(64)) 25 | os = db.Column(db.String(64)) 26 | 27 | def get_url(self): 28 | return url_for('get_device', id=self.id, _external=True) 29 | 30 | def export_data(self): 31 | return { 32 | 'self_url': self.get_url(), 33 | 'hostname': self.hostname, 34 | 'loopback': self.loopback, 35 | 'mgmt_ip': self.mgmt_ip, 36 | 'role': self.role, 37 | 'vendor': self.vendor, 38 | 'os': self.os 39 | } 40 | 41 | def import_data(self, data): 42 | try: 43 | self.hostname = data['hostname'] 44 | self.loopback = data['loopback'] 45 | self.mgmt_ip = data['mgmt_ip'] 46 | self.role = data['role'] 47 | self.vendor = data['vendor'] 48 | self.os = data['os'] 49 | except KeyError as e: 50 | raise ValidationError('Invalid device: missing ' + e.args[0]) 51 | return self 52 | 53 | 54 | @app.route('/devices/', methods=['GET']) 55 | def get_devices(): 56 | return jsonify({'device': [device.get_url() 57 | for device in Device.query.all()]}) 58 | 59 | @app.route('/devices/', methods=['GET']) 60 | def get_device(id): 61 | return jsonify(Device.query.get_or_404(id).export_data()) 62 | 63 | 64 | @app.route('/devices//version', methods=['GET']) 65 | def get_device_version(id): 66 | device = Device.query.get_or_404(id) 67 | hostname = device.hostname 68 | ip = device.mgmt_ip 69 | prompt = hostname+"#" 70 | result = show_version(hostname, prompt, ip, 'cisco', 'cisco') 71 | return jsonify({"version": str(result)}) 72 | 73 | @app.route('/devices//version', methods=['GET']) 74 | def get_role_version(device_role): 75 | device_id_list = [device.id for device in Device.query.all() if device.role == device_role] 76 | result = {} 77 | for id in device_id_list: 78 | device = Device.query.get_or_404(id) 79 | hostname = device.hostname 80 | ip = device.mgmt_ip 81 | prompt = hostname + "#" 82 | device_result = show_version(hostname, prompt, ip, 'cisco', 'cisco') 83 | result[hostname] = str(device_result) 84 | return jsonify(result) 85 | 86 | @app.route('/devices/', methods=['POST']) 87 | def new_device(): 88 | device = Device() 89 | device.import_data(request.json) 90 | db.session.add(device) 91 | db.session.commit() 92 | return jsonify({}), 201, {'Location': device.get_url()} 93 | 94 | @app.route('/devices/', methods=['PUT']) 95 | def edit_device(id): 96 | device = Device.query.get_or_404(id) 97 | device.import_data(request.json) 98 | db.session.add(device) 99 | db.session.commit() 100 | return jsonify({}) 101 | 102 | 103 | if __name__ == '__main__': 104 | db.create_all() 105 | app.run(host='0.0.0.0', debug=True) 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /Chapter09/chapter9_8.py: -------------------------------------------------------------------------------- 1 | # This example referenced Miguel Grinberg's code on Github: 2 | # https://github.com/miguelgrinberg/oreilly-flask-apis-video/blob/master/camera/camera.py 3 | # 4 | 5 | from flask import Flask, url_for, jsonify, request,\ 6 | make_response, copy_current_request_context 7 | from flask_sqlalchemy import SQLAlchemy 8 | from chapter9_pexpect_1 import show_version 9 | import uuid 10 | import functools 11 | from threading import Thread 12 | 13 | app = Flask(__name__) 14 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///network.db' 15 | db = SQLAlchemy(app) 16 | 17 | background_tasks = {} 18 | app.config['AUTO_DELETE_BG_TASKS'] = True 19 | 20 | 21 | class ValidationError(ValueError): 22 | pass 23 | 24 | 25 | class Device(db.Model): 26 | __tablename__ = 'devices' 27 | id = db.Column(db.Integer, primary_key=True) 28 | hostname = db.Column(db.String(64), unique=True) 29 | loopback = db.Column(db.String(120), unique=True) 30 | mgmt_ip = db.Column(db.String(120), unique=True) 31 | role = db.Column(db.String(64)) 32 | vendor = db.Column(db.String(64)) 33 | os = db.Column(db.String(64)) 34 | 35 | def get_url(self): 36 | return url_for('get_device', id=self.id, _external=True) 37 | 38 | def export_data(self): 39 | return { 40 | 'self_url': self.get_url(), 41 | 'hostname': self.hostname, 42 | 'loopback': self.loopback, 43 | 'mgmt_ip': self.mgmt_ip, 44 | 'role': self.role, 45 | 'vendor': self.vendor, 46 | 'os': self.os 47 | } 48 | 49 | def import_data(self, data): 50 | try: 51 | self.hostname = data['hostname'] 52 | self.loopback = data['loopback'] 53 | self.mgmt_ip = data['mgmt_ip'] 54 | self.role = data['role'] 55 | self.vendor = data['vendor'] 56 | self.os = data['os'] 57 | except KeyError as e: 58 | raise ValidationError('Invalid device: missing ' + e.args[0]) 59 | return self 60 | 61 | 62 | def background(f): 63 | """Decorator that runs the wrapped function as a background task. It is 64 | assumed that this function creates a new resource, and takes a long time 65 | to do so. The response has status code 202 Accepted and includes a Location 66 | header with the URL of a task resource. Sending a GET request to the task 67 | will continue to return 202 for as long as the task is running. When the task 68 | has finished, a status code 303 See Other will be returned, along with a 69 | Location header that points to the newly created resource. The client then 70 | needs to send a DELETE request to the task resource to remove it from the 71 | system.""" 72 | @functools.wraps(f) 73 | def wrapped(*args, **kwargs): 74 | # The background task needs to be decorated with Flask's 75 | # copy_current_request_context to have access to context globals. 76 | @copy_current_request_context 77 | def task(): 78 | global background_tasks 79 | try: 80 | # invoke the wrapped function and record the returned 81 | # response in the background_tasks dictionary 82 | background_tasks[id] = make_response(f(*args, **kwargs)) 83 | except: 84 | # the wrapped function raised an exception, return a 500 85 | # response 86 | background_tasks[id] = make_response(internal_server_error()) 87 | 88 | # store the background task under a randomly generated identifier 89 | # and start it 90 | global background_tasks 91 | id = uuid.uuid4().hex 92 | background_tasks[id] = Thread(target=task) 93 | background_tasks[id].start() 94 | 95 | # return a 202 Accepted response with the location of the task status 96 | # resource 97 | return jsonify({}), 202, {'Location': url_for('get_task_status', id=id)} 98 | return wrapped 99 | 100 | 101 | @app.route('/devices/', methods=['GET']) 102 | def get_devices(): 103 | return jsonify({'device': [device.get_url() 104 | for device in Device.query.all()]}) 105 | 106 | @app.route('/devices/', methods=['GET']) 107 | def get_device(id): 108 | return jsonify(Device.query.get_or_404(id).export_data()) 109 | 110 | 111 | @app.route('/devices//version', methods=['GET']) 112 | @background 113 | def get_device_version(id): 114 | device = Device.query.get_or_404(id) 115 | hostname = device.hostname 116 | ip = device.mgmt_ip 117 | prompt = hostname+"#" 118 | result = show_version(hostname, prompt, ip, 'cisco', 'cisco') 119 | return jsonify({"version": str(result)}) 120 | 121 | @app.route('/devices//version', methods=['GET']) 122 | @background 123 | def get_role_version(device_role): 124 | device_id_list = [device.id for device in Device.query.all() if device.role == device_role] 125 | result = {} 126 | for id in device_id_list: 127 | device = Device.query.get_or_404(id) 128 | hostname = device.hostname 129 | ip = device.mgmt_ip 130 | prompt = hostname + "#" 131 | device_result = show_version(hostname, prompt, ip, 'cisco', 'cisco') 132 | result[hostname] = str(device_result) 133 | return jsonify(result) 134 | 135 | @app.route('/devices/', methods=['POST']) 136 | def new_device(): 137 | device = Device() 138 | device.import_data(request.json) 139 | db.session.add(device) 140 | db.session.commit() 141 | return jsonify({}), 201, {'Location': device.get_url()} 142 | 143 | @app.route('/devices/', methods=['PUT']) 144 | def edit_device(id): 145 | device = Device.query.get_or_404(id) 146 | device.import_data(request.json) 147 | db.session.add(device) 148 | db.session.commit() 149 | return jsonify({}) 150 | 151 | 152 | @app.route('/status/', methods=['GET']) 153 | def get_task_status(id): 154 | """Query the status of an asynchronous task.""" 155 | # obtain the task and validate it 156 | global background_tasks 157 | rv = background_tasks.get(id) 158 | if rv is None: 159 | return not_found(None) 160 | 161 | # if the task object is a Thread object that means that the task is still 162 | # running. In this case return the 202 status message again. 163 | if isinstance(rv, Thread): 164 | return jsonify({}), 202, {'Location': url_for('get_task_status', id=id)} 165 | 166 | # If the task object is not a Thread then it is assumed to be the response 167 | # of the finished task, so that is the response that is returned. 168 | # If the application is configured to auto-delete task status resources once 169 | # the task is done then the deletion happens now, if not the client is 170 | # expected to send a delete request. 171 | if app.config['AUTO_DELETE_BG_TASKS']: 172 | del background_tasks[id] 173 | return rv 174 | 175 | 176 | 177 | if __name__ == '__main__': 178 | db.create_all() 179 | app.run(host='0.0.0.0', debug=True) 180 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /Chapter09/chapter9_9.py: -------------------------------------------------------------------------------- 1 | # This example referenced Miguel Grinberg's code on Github: 2 | # https://github.com/miguelgrinberg/oreilly-flask-apis-video 3 | # 4 | 5 | from flask import Flask, url_for, jsonify, request,\ 6 | make_response, copy_current_request_context, g 7 | from flask_sqlalchemy import SQLAlchemy 8 | from chapter9_pexpect_1 import show_version 9 | import uuid 10 | import functools 11 | from threading import Thread 12 | from werkzeug.security import generate_password_hash, check_password_hash 13 | from flask_httpauth import HTTPBasicAuth 14 | 15 | app = Flask(__name__) 16 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///network.db' 17 | db = SQLAlchemy(app) 18 | auth = HTTPBasicAuth() 19 | 20 | background_tasks = {} 21 | app.config['AUTO_DELETE_BG_TASKS'] = True 22 | 23 | 24 | class ValidationError(ValueError): 25 | pass 26 | 27 | # The two password function came with Flask Werkzeug 28 | class User(db.Model): 29 | __tablename__ = 'users' 30 | id = db.Column(db.Integer, primary_key=True) 31 | username = db.Column(db.String(64), index=True) 32 | password_hash = db.Column(db.String(128)) 33 | 34 | def set_password(self, password): 35 | self.password_hash = generate_password_hash(password) 36 | 37 | def verify_password(self, password): 38 | return check_password_hash(self.password_hash, password) 39 | 40 | 41 | class Device(db.Model): 42 | __tablename__ = 'devices' 43 | id = db.Column(db.Integer, primary_key=True) 44 | hostname = db.Column(db.String(64), unique=True) 45 | loopback = db.Column(db.String(120), unique=True) 46 | mgmt_ip = db.Column(db.String(120), unique=True) 47 | role = db.Column(db.String(64)) 48 | vendor = db.Column(db.String(64)) 49 | os = db.Column(db.String(64)) 50 | 51 | def get_url(self): 52 | return url_for('get_device', id=self.id, _external=True) 53 | 54 | def export_data(self): 55 | return { 56 | 'self_url': self.get_url(), 57 | 'hostname': self.hostname, 58 | 'loopback': self.loopback, 59 | 'mgmt_ip': self.mgmt_ip, 60 | 'role': self.role, 61 | 'vendor': self.vendor, 62 | 'os': self.os 63 | } 64 | 65 | def import_data(self, data): 66 | try: 67 | self.hostname = data['hostname'] 68 | self.loopback = data['loopback'] 69 | self.mgmt_ip = data['mgmt_ip'] 70 | self.role = data['role'] 71 | self.vendor = data['vendor'] 72 | self.os = data['os'] 73 | except KeyError as e: 74 | raise ValidationError('Invalid device: missing ' + e.args[0]) 75 | return self 76 | 77 | 78 | def background(f): 79 | """Decorator that runs the wrapped function as a background task. It is 80 | assumed that this function creates a new resource, and takes a long time 81 | to do so. The response has status code 202 Accepted and includes a Location 82 | header with the URL of a task resource. Sending a GET request to the task 83 | will continue to return 202 for as long as the task is running. When the task 84 | has finished, a status code 303 See Other will be returned, along with a 85 | Location header that points to the newly created resource. The client then 86 | needs to send a DELETE request to the task resource to remove it from the 87 | system.""" 88 | @functools.wraps(f) 89 | def wrapped(*args, **kwargs): 90 | # The background task needs to be decorated with Flask's 91 | # copy_current_request_context to have access to context globals. 92 | @copy_current_request_context 93 | def task(): 94 | global background_tasks 95 | try: 96 | # invoke the wrapped function and record the returned 97 | # response in the background_tasks dictionary 98 | background_tasks[id] = make_response(f(*args, **kwargs)) 99 | except: 100 | # the wrapped function raised an exception, return a 500 101 | # response 102 | background_tasks[id] = make_response(internal_server_error()) 103 | 104 | # store the background task under a randomly generated identifier 105 | # and start it 106 | global background_tasks 107 | id = uuid.uuid4().hex 108 | background_tasks[id] = Thread(target=task) 109 | background_tasks[id].start() 110 | 111 | # return a 202 Accepted response with the location of the task status 112 | # resource 113 | return jsonify({}), 202, {'Location': url_for('get_task_status', id=id)} 114 | return wrapped 115 | 116 | # g is the context request object from Flask 117 | @auth.verify_password 118 | def verify_password(username, password): 119 | g.user = User.query.filter_by(username=username).first() 120 | if g.user is None: 121 | return False 122 | return g.user.verify_password(password) 123 | 124 | @app.before_request 125 | @auth.login_required 126 | def before_request(): 127 | pass 128 | 129 | # from HTTPAuath extension 130 | @auth.error_handler 131 | def unathorized(): 132 | response = jsonify({'status': 401, 'error': 'unahtorized', 133 | 'message': 'please authenticate'}) 134 | response.status_code = 401 135 | return response 136 | 137 | 138 | @app.route('/devices/', methods=['GET']) 139 | def get_devices(): 140 | return jsonify({'device': [device.get_url() 141 | for device in Device.query.all()]}) 142 | 143 | @app.route('/devices/', methods=['GET']) 144 | def get_device(id): 145 | return jsonify(Device.query.get_or_404(id).export_data()) 146 | 147 | 148 | @app.route('/devices//version', methods=['GET']) 149 | @background 150 | def get_device_version(id): 151 | device = Device.query.get_or_404(id) 152 | hostname = device.hostname 153 | ip = device.mgmt_ip 154 | prompt = hostname+"#" 155 | result = show_version(hostname, prompt, ip, 'cisco', 'cisco') 156 | return jsonify({"version": str(result)}) 157 | 158 | @app.route('/devices//version', methods=['GET']) 159 | @background 160 | def get_role_version(device_role): 161 | device_id_list = [device.id for device in Device.query.all() if device.role == device_role] 162 | result = {} 163 | for id in device_id_list: 164 | device = Device.query.get_or_404(id) 165 | hostname = device.hostname 166 | ip = device.mgmt_ip 167 | prompt = hostname + "#" 168 | device_result = show_version(hostname, prompt, ip, 'cisco', 'cisco') 169 | result[hostname] = str(device_result) 170 | return jsonify(result) 171 | 172 | @app.route('/devices/', methods=['POST']) 173 | def new_device(): 174 | device = Device() 175 | device.import_data(request.json) 176 | db.session.add(device) 177 | db.session.commit() 178 | return jsonify({}), 201, {'Location': device.get_url()} 179 | 180 | @app.route('/devices/', methods=['PUT']) 181 | def edit_device(id): 182 | device = Device.query.get_or_404(id) 183 | device.import_data(request.json) 184 | db.session.add(device) 185 | db.session.commit() 186 | return jsonify({}) 187 | 188 | 189 | @app.route('/status/', methods=['GET']) 190 | def get_task_status(id): 191 | """Query the status of an asynchronous task.""" 192 | # obtain the task and validate it 193 | global background_tasks 194 | rv = background_tasks.get(id) 195 | if rv is None: 196 | return not_found(None) 197 | 198 | # if the task object is a Thread object that means that the task is still 199 | # running. In this case return the 202 status message again. 200 | if isinstance(rv, Thread): 201 | return jsonify({}), 202, {'Location': url_for('get_task_status', id=id)} 202 | 203 | # If the task object is not a Thread then it is assumed to be the response 204 | # of the finished task, so that is the response that is returned. 205 | # If the application is configured to auto-delete task status resources once 206 | # the task is done then the deletion happens now, if not the client is 207 | # expected to send a delete request. 208 | if app.config['AUTO_DELETE_BG_TASKS']: 209 | del background_tasks[id] 210 | return rv 211 | 212 | 213 | 214 | if __name__ == '__main__': 215 | db.create_all() 216 | app.run(host='0.0.0.0', debug=True) 217 | 218 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /Chapter09/chapter9_db_1.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_sqlalchemy import SQLAlchemy 3 | 4 | # Create Flask application, load configuration, and create 5 | # the SQLAlchemy object 6 | app = Flask(__name__) 7 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///network.db' 8 | db = SQLAlchemy(app) 9 | 10 | # This is the database model object 11 | class Device(db.Model): 12 | __tablename__ = 'devices' 13 | id = db.Column(db.Integer, primary_key=True) 14 | hostname = db.Column(db.String(120), index=True) 15 | vendor = db.Column(db.String(40)) 16 | 17 | def __init__(self, hostname, vendor): 18 | self.hostname = hostname 19 | self.vendor = vendor 20 | 21 | def __repr__(self): 22 | return '' % self.hostname 23 | 24 | 25 | if __name__ == '__main__': 26 | db.create_all() 27 | r1 = Device('lax-dc1-core1', 'Juniper') 28 | r2 = Device('sfo-dc1-core1', 'Cisco') 29 | db.session.add(r1) 30 | db.session.add(r2) 31 | db.session.commit() 32 | 33 | -------------------------------------------------------------------------------- /Chapter09/chapter9_pexpect_1.py: -------------------------------------------------------------------------------- 1 | import pexpect 2 | 3 | 4 | def show_version(device, prompt, ip, username, password): 5 | device_prompt = prompt 6 | child = pexpect.spawn('telnet ' + ip) 7 | child.expect('Username:') 8 | child.sendline(username) 9 | child.expect('Password:') 10 | child.sendline(password) 11 | child.expect(device_prompt) 12 | child.sendline('show version | i V') 13 | child.expect(device_prompt) 14 | result = child.before 15 | child.sendline('exit') 16 | return device, result 17 | 18 | if __name__ == '__main__': 19 | username = 'cisco' 20 | password = 'cisco' 21 | print(show_version('iosv-1', 'iosv-1#', '172.16.1.225', username, password)) 22 | print(show_version('iosv-2', 'iosv-2#', '172.16.1.226', username, password)) 23 | 24 | -------------------------------------------------------------------------------- /Chapter09/chapter9_request_1.py: -------------------------------------------------------------------------------- 1 | import requests, time 2 | 3 | server = 'http://192.168.2.126:5000' 4 | endpoint = '/devices/1/version' 5 | 6 | # First request to get the new resource 7 | r = requests.get(server+endpoint) 8 | resource = r.headers['location'] 9 | print("Status: {} Resource: {}".format(r.status_code, resource)) 10 | 11 | # Second request to get the resource status 12 | r = requests.get(server+"/"+resource) 13 | print("Immediate Status Query to Resource: " + str(r.status_code)) 14 | 15 | print("Sleep for 2 seconds") 16 | time.sleep(2) 17 | # Third request to get the resource status 18 | r = requests.get(server+"/"+resource) 19 | print("Status after 2 seconds: " + str(r.status_code)) 20 | 21 | 22 | -------------------------------------------------------------------------------- /Chapter09/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2022.9.14 2 | charset-normalizer==2.1.1 3 | click==8.1.3 4 | commonmark==0.9.1 5 | defusedxml==0.7.1 6 | Flask==2.2.2 7 | Flask-HTTPAuth==4.7.0 8 | Flask-SQLAlchemy==2.5.1 9 | greenlet==1.1.3 10 | httpie==3.2.1 11 | idna==3.4 12 | itsdangerous==2.1.2 13 | Jinja2==3.1.2 14 | MarkupSafe==2.1.1 15 | multidict==6.0.2 16 | pexpect==4.8.0 17 | ptyprocess==0.7.0 18 | Pygments==2.13.0 19 | PySocks==1.7.1 20 | requests==2.28.1 21 | requests-toolbelt==0.9.1 22 | rich==12.5.1 23 | SQLAlchemy==1.4.41 24 | urllib3==1.26.12 25 | Werkzeug==2.2.2 26 | -------------------------------------------------------------------------------- /Chapter10/async_count.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # example from https://realpython.com/async-io-python/#the-asyncio-package-and-asyncawait countasync.py 3 | 4 | import asyncio 5 | 6 | async def count(): 7 | print("One") 8 | await asyncio.sleep(1) 9 | print("Two") 10 | 11 | async def main(): 12 | await asyncio.gather(count(), count(), count()) 13 | 14 | if __name__ == "__main__": 15 | import time 16 | s = time.perf_counter() 17 | asyncio.run(main()) 18 | elapsed = time.perf_counter() - s 19 | print(f"Completed in {elapsed:0.2f} seconds.") 20 | 21 | -------------------------------------------------------------------------------- /Chapter10/asyncio_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import asyncio 3 | 4 | async def main(): 5 | print('Hello ...') 6 | await asyncio.sleep(1) 7 | print('... World!') 8 | await asyncio.sleep(2) 9 | print('... and again.') 10 | 11 | 12 | asyncio.run(main()) 13 | 14 | -------------------------------------------------------------------------------- /Chapter10/multiprocess_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Modified from https://docs.python.org/3/library/multiprocessing.html 3 | from multiprocessing import Process 4 | import os 5 | 6 | # Get process information 7 | def process_info(): 8 | print('process id:', os.getpid()) 9 | 10 | # Worker function 11 | def worker(number): 12 | print(f'Worker number {number}') 13 | process_info() 14 | 15 | 16 | if __name__ == '__main__': 17 | for i in range(5): 18 | p = Process(target=worker, args=(i,)) 19 | p.start() 20 | 21 | -------------------------------------------------------------------------------- /Chapter10/scrapli/scrapli_example_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Modified from https://github.com/carlmontanari/scrapli 3 | from scrapli import Scrapli 4 | 5 | device = { 6 | "host": "192.168.2.50", 7 | "auth_username": "cisco", 8 | "auth_password": "cisco", 9 | "auth_strict_key": False, 10 | "ssh_config_file": True, 11 | "platform": "cisco_nxos", 12 | } 13 | 14 | conn = Scrapli(**device) 15 | conn.open() 16 | response = conn.send_command("show version") 17 | print(response.result) 18 | 19 | 20 | -------------------------------------------------------------------------------- /Chapter10/scrapli/scrapli_example_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Modified from 3 | # https://github.com/carlmontanari/scrapli/blob/main/examples/async_usage/async_multiple_connections.py 4 | import asyncio 5 | from scrapli.driver.core import AsyncNXOSDriver 6 | 7 | 8 | async def gather_cor_device_version(ip, username, password): 9 | device = { 10 | "host": ip, 11 | "auth_username": username, 12 | "auth_password": password, 13 | "auth_strict_key": False, 14 | "ssh_config_file": True, 15 | "transport": "asyncssh", 16 | "driver": AsyncNXOSDriver 17 | } 18 | 19 | driver = device.pop("driver") 20 | conn = driver(**device) 21 | await conn.open() 22 | response = await conn.send_command("show version") 23 | await conn.close() 24 | return response 25 | 26 | async def main(): 27 | results = await asyncio.gather( 28 | gather_cor_device_version('192.168.2.50', 'cisco', 'cisco'), 29 | gather_cor_device_version('192.168.2.60', 'cisco', 'cisco') 30 | ) 31 | for result in results: 32 | print(result.result) 33 | 34 | 35 | if __name__ == "__main__": 36 | import time 37 | s = time.perf_counter() 38 | asyncio.run(main()) 39 | elapsed = time.perf_counter() - s 40 | print(f"Completed in {elapsed:0.2f} seconds.") 41 | -------------------------------------------------------------------------------- /Chapter10/scrapli/scrapli_example_3_async.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Modified from 3 | # https://github.com/carlmontanari/scrapli/blob/main/examples/async_usage/async_multiple_connections.py 4 | import asyncio 5 | from scrapli.driver.core import AsyncNXOSDriver 6 | 7 | 8 | async def gather_cor_device_version(ip, username, password): 9 | device = { 10 | "host": ip, 11 | "auth_username": username, 12 | "auth_password": password, 13 | "auth_strict_key": False, 14 | "ssh_config_file": True, 15 | "transport": "asyncssh", 16 | "driver": AsyncNXOSDriver 17 | } 18 | 19 | driver = device.pop("driver") 20 | conn = driver(**device) 21 | await conn.open() 22 | response = await conn.send_command("show version") 23 | await conn.close() 24 | return response 25 | 26 | async def main(): 27 | results = await asyncio.gather( 28 | gather_cor_device_version('192.168.2.50', 'cisco', 'cisco'), 29 | gather_cor_device_version('192.168.2.60', 'cisco', 'cisco'), 30 | gather_cor_device_version('192.168.2.50', 'cisco', 'cisco'), 31 | gather_cor_device_version('192.168.2.60', 'cisco', 'cisco'), 32 | gather_cor_device_version('192.168.2.50', 'cisco', 'cisco'), 33 | gather_cor_device_version('192.168.2.60', 'cisco', 'cisco'), 34 | gather_cor_device_version('192.168.2.50', 'cisco', 'cisco'), 35 | gather_cor_device_version('192.168.2.60', 'cisco', 'cisco'), 36 | ) 37 | return results 38 | 39 | 40 | if __name__ == "__main__": 41 | import time 42 | s = time.perf_counter() 43 | asyncio.run(main()) 44 | elapsed = time.perf_counter() - s 45 | print(f"Completed in {elapsed:0.2f} seconds.") 46 | -------------------------------------------------------------------------------- /Chapter10/scrapli/scrapli_example_3_sync.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Modified from 3 | # https://github.com/carlmontanari/scrapli/blob/main/examples/async_usage/async_multiple_connections.py 4 | import asyncio 5 | # from scrapli.driver.core import Paramiko 6 | from scrapli.driver import GenericDriver 7 | 8 | 9 | def gather_cor_device_version(ip, username, password): 10 | device = { 11 | "host": ip, 12 | "auth_username": username, 13 | "auth_password": password, 14 | "auth_strict_key": False, 15 | "ssh_config_file": True, 16 | "driver": GenericDriver 17 | } 18 | 19 | driver = device.pop("driver") 20 | conn = driver(**device) 21 | conn.open() 22 | response = conn.send_command("show version") 23 | conn.close() 24 | return response 25 | 26 | def main(): 27 | results = [] 28 | for device in [ 29 | '192.168.2.50', 30 | '192.168.2.60', 31 | '192.168.2.50', 32 | '192.168.2.60', 33 | '192.168.2.50', 34 | '192.168.2.60', 35 | '192.168.2.50', 36 | '192.168.2.60', 37 | ]: 38 | results.append(gather_cor_device_version(device, 'cisco', 'cisco')) 39 | return results 40 | 41 | 42 | if __name__ == "__main__": 43 | import time 44 | s = time.perf_counter() 45 | main() 46 | elapsed = time.perf_counter() - s 47 | print(f"Completed in {elapsed:0.2f} seconds.") 48 | -------------------------------------------------------------------------------- /Chapter10/sync_count.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Modified from https://realpython.com/async-io-python/#the-asyncio-package-and-asyncawait countsync.py example 3 | import time 4 | 5 | def count(): 6 | print("One") 7 | time.sleep(1) 8 | print("Two") 9 | 10 | def main(): 11 | count() 12 | count() 13 | count() 14 | 15 | if __name__ == "__main__": 16 | s = time.perf_counter() 17 | main() 18 | elapsed = time.perf_counter() - s 19 | print(f"Completed in {elapsed:0.2f} seconds.") 20 | 21 | -------------------------------------------------------------------------------- /Chapter10/threading_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Modified from https://pymotw.com/3/threading/index.html 3 | import threading 4 | 5 | # Get thread ID 6 | def thread_id(): 7 | print('thread id:', threading.get_ident()) 8 | 9 | # Worker function 10 | def worker(number): 11 | print(f'Worker number {number}') 12 | thread_id() 13 | 14 | 15 | threads = [] 16 | for i in range(5): 17 | t = threading.Thread(target=worker, args=(i,)) 18 | threads.append(t) 19 | t.start() 20 | -------------------------------------------------------------------------------- /Chapter11/Chapter11_1_query_vpc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import json 3 | import boto3 4 | 5 | region = 'us-east-1' 6 | vpc_name = 'mastering_python_networking_demo' 7 | 8 | ec2 = boto3.resource('ec2', region_name=region) 9 | client = boto3.client('ec2') 10 | 11 | 12 | filters = [{'Name': 'tag:Name', 'Values': [vpc_name]}] 13 | 14 | vpcs = list(ec2.vpcs.filter(Filters=filters)) 15 | 16 | for vpc in vpcs: 17 | print(vpc.id) 18 | response = client.describe_vpcs( 19 | VpcIds=[vpc.id] 20 | ) 21 | print(json.dumps(response, sort_keys=True, indent=4)) 22 | 23 | 24 | -------------------------------------------------------------------------------- /Chapter11/Chapter11_2_query_route_tables.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import json, boto3 4 | 5 | region = 'us-east-1' 6 | vpc_name = 'mastering_python_networking_demo' 7 | 8 | ec2 = boto3.resource('ec2', region_name=region) 9 | client = boto3.client('ec2') 10 | 11 | response = client.describe_route_tables() 12 | print(json.dumps(response['RouteTables'][0], sort_keys=True, indent=4)) 13 | 14 | -------------------------------------------------------------------------------- /Chapter11/Chapter11_3_cloud_formation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Create VPC in us-west-1 3 | Resources: 4 | myVPC: 5 | Type: AWS::EC2::VPC 6 | Properties: 7 | CidrBlock: '10.1.0.0/16' 8 | EnableDnsSupport: 'false' 9 | EnableDnsHostnames: 'false' 10 | Tags: 11 | - Key: Name 12 | Value: 'mastering_python_networking_demo_2' 13 | 14 | 15 | -------------------------------------------------------------------------------- /Chapter11/Chapter11_4_cloud_formation_full.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Create subnet in us-west-1 3 | Resources: 4 | myVPC: 5 | Type: AWS::EC2::VPC 6 | Properties: 7 | CidrBlock: '10.1.0.0/16' 8 | EnableDnsSupport: 'false' 9 | EnableDnsHostnames: 'false' 10 | Tags: 11 | - Key: Name 12 | Value: 'mastering_python_networking_demo_2' 13 | 14 | mySubnet: 15 | Type: AWS::EC2::Subnet 16 | Properties: 17 | VpcId: !Ref myVPC 18 | CidrBlock: '10.1.0.0/24' 19 | AvailabilityZone: 'us-west-1a' 20 | Tags: 21 | - Key: Name 22 | Value: 'mpn_demo_subnet_1' 23 | 24 | 25 | -------------------------------------------------------------------------------- /Chapter11/Chapter11_5_security_group.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import boto3 4 | 5 | ec2 = boto3.client('ec2') 6 | 7 | response = ec2.describe_vpcs() 8 | vpc_id = response.get('Vpcs', [{}])[0].get('VpcId', '') 9 | 10 | # Query for security group id 11 | response = ec2.create_security_group(GroupName='mpn_security_group', 12 | Description='mpn_demo_sg', 13 | VpcId=vpc_id) 14 | security_group_id = response['GroupId'] 15 | data = ec2.authorize_security_group_ingress( 16 | GroupId=security_group_id, 17 | IpPermissions=[ 18 | {'IpProtocol': 'tcp', 19 | 'FromPort': 80, 20 | 'ToPort': 80, 21 | 'IpRanges': [{'CidrIp': '0.0.0.0/0'}]}, 22 | {'IpProtocol': 'tcp', 23 | 'FromPort': 22, 24 | 'ToPort': 22, 25 | 'IpRanges': [{'CidrIp': '0.0.0.0/0'}]} 26 | ]) 27 | print('Ingress Successfully Set %s' % data) 28 | 29 | # Describe security group 30 | #response = ec2.describe_security_groups(GroupIds=[security_group_id]) 31 | print(security_group_id) 32 | 33 | -------------------------------------------------------------------------------- /Chapter12/Chapter12_1_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import azure.mgmt.network 4 | from azure.identity import ClientSecretCredential 5 | 6 | credential = ClientSecretCredential( 7 | tenant_id=os.environ.get("AZURE_TENANT_ID"), 8 | client_id=os.environ.get("AZURE_CLIENT_ID"), 9 | client_secret=os.environ.get("AZURE_CLIENT_SECRET") 10 | ) 11 | subscription_id = os.environ.get("SUBSCRIPTION_ID") 12 | network_client = azure.mgmt.network.NetworkManagementClient(credential=credential, subscription_id=subscription_id) 13 | print("Network Management Client API Version: " + network_client.DEFAULT_API_VERSION) 14 | -------------------------------------------------------------------------------- /Chapter12/Chapter12_2_subnet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Reference example: https://github.com/Azure-Samples/azure-samples-python-management/blob/main/samples/network/virtual_network/manage_subnet.py 3 | # 4 | import os 5 | from azure.identity import ClientSecretCredential 6 | import azure.mgmt.network 7 | from azure.identity import DefaultAzureCredential 8 | from azure.mgmt.network import NetworkManagementClient 9 | from azure.mgmt.resource import ResourceManagementClient 10 | 11 | credential = ClientSecretCredential( 12 | tenant_id=os.environ.get("AZURE_TENANT_ID"), 13 | client_id=os.environ.get("AZURE_CLIENT_ID"), 14 | client_secret=os.environ.get("AZURE_CLIENT_SECRET") 15 | ) 16 | subscription_id = os.environ.get("SUBSCRIPTION_ID") 17 | GROUP_NAME = "Mastering-Python-Networking" 18 | VIRTUAL_NETWORK_NAME = "WEST-US-2_VNet_1" 19 | SUBNET = "WEST-US-2_VNet_1_Subnet_2" 20 | network_client = azure.mgmt.network.NetworkManagementClient( 21 | credential=credential, subscription_id=subscription_id) 22 | 23 | # Get subnet 24 | subnet = network_client.subnets.get( 25 | GROUP_NAME, 26 | VIRTUAL_NETWORK_NAME, 27 | SUBNET 28 | ) 29 | print("Get subnet:\n{}".format(subnet)) 30 | 31 | subnet = network_client.subnets.begin_create_or_update( 32 | GROUP_NAME, 33 | VIRTUAL_NETWORK_NAME, 34 | SUBNET, 35 | { 36 | "address_prefix": "192.168.0.128/25" 37 | } 38 | ).result() 39 | print("Create subnet:\n{}".format(subnet)) 40 | -------------------------------------------------------------------------------- /Chapter12/Chapter12_3_vnet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Referenced: https://github.com/Azure-Samples/azure-samples-python-management/blob/main/samples/network/virtual_network/manage_virtual_network_peering.py 4 | # 5 | import os 6 | from azure.identity import ClientSecretCredential 7 | import azure.mgmt.network 8 | from azure.identity import DefaultAzureCredential 9 | from azure.mgmt.network import NetworkManagementClient 10 | from azure.mgmt.resource import ResourceManagementClient 11 | 12 | credential = ClientSecretCredential( 13 | tenant_id=os.environ.get("AZURE_TENANT_ID"), 14 | client_id=os.environ.get("AZURE_CLIENT_ID"), 15 | client_secret=os.environ.get("AZURE_CLIENT_SECRET") 16 | ) 17 | subscription_id = os.environ.get("SUBSCRIPTION_ID") 18 | GROUP_NAME = "Mastering-Python-Networking" 19 | VIRTUAL_NETWORK_NAME = "WEST-US-2_VNet_1" 20 | SUBNET = "WEST-US-2_VNet_1_Subnet_2" 21 | LOCATION = 'eastus' 22 | network_client = azure.mgmt.network.NetworkManagementClient( 23 | credential=credential, subscription_id=subscription_id) 24 | 25 | 26 | def create_vnet(network_client): 27 | vnet_params = { 28 | 'location': LOCATION, 29 | 'address_space': { 30 | 'address_prefixes': ['10.0.0.0/16'] 31 | } 32 | } 33 | creation_result = network_client.virtual_networks.create_or_update( 34 | GROUP_NAME, 35 | 'EAST-US_VNet_1', 36 | vnet_params 37 | ) 38 | return creation_result.result() 39 | 40 | 41 | creation_result = create_vnet(network_client) 42 | print("------------------------------------------------------") 43 | print(creation_result) 44 | input('Press enter to continue...') 45 | 46 | 47 | def create_subnet(network_client): 48 | subnet_params = { 49 | 'address_prefix': '10.0.1.0/24' 50 | } 51 | creation_result = network_client.subnets.create_or_update( 52 | GROUP_NAME, 53 | 'EAST-US_VNet_1', 54 | 'EAST-US_VNet_1_Subnet_1', 55 | subnet_params 56 | ) 57 | 58 | return creation_result.result() 59 | 60 | 61 | creation_result = create_subnet(network_client) 62 | print("------------------------------------------------------") 63 | print(creation_result) 64 | input('Press enter to continue...') 65 | 66 | 67 | -------------------------------------------------------------------------------- /Chapter13/Chapter13_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import requests 3 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 4 | 5 | # disable https verification check warning 6 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 7 | 8 | def current_indices_list(es_host, index_prefix): 9 | current_indices = [] 10 | http_header = {'content-type': 'application/json'} 11 | response = requests.get(es_host + "/_cat/indices/" + index_prefix + "*", headers=http_header, verify=False) 12 | for line in response.text.split('\n'): 13 | if line: 14 | current_indices.append(line.split()[2]) 15 | return current_indices 16 | 17 | if __name__ == "__main__": 18 | username = 'elastic' 19 | password = '-Rel0twWMUk8L-ZtZr=I' 20 | es_host = 'https://'+username+':'+password+'@192.168.2.126:9200' 21 | indices_list = current_indices_list(es_host, 'kibana') 22 | print(indices_list) 23 | 24 | -------------------------------------------------------------------------------- /Chapter13/Chapter13_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from elasticsearch import Elasticsearch 3 | 4 | es_host = Elasticsearch(["https://elastic:-Rel0twWMUk8L-ZtZr=I@192.168.2.126:9200/"], 5 | ca_certs=False, verify_certs=False) 6 | 7 | res = es_host.search(index="kibana*", body={"query": {"match_all": {}}}) 8 | print("Hits Total: " + str(res['hits']['total']['value'])) 9 | 10 | 11 | -------------------------------------------------------------------------------- /Chapter13/Chapter13_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from elasticsearch import Elasticsearch 3 | from datetime import datetime 4 | import argparse 5 | import json 6 | import logging 7 | 8 | # parse options 9 | parser = argparse.ArgumentParser(description='Elsticsearch Query Options') 10 | parser.add_argument("-i", "--index", help="index to query") 11 | parser.add_argument("-q", "--query", help="query file") 12 | 13 | args = parser.parse_args() 14 | 15 | 16 | # logging 17 | today = str(datetime.today()) 18 | logging.basicConfig(filename='elastic_query.log', level=logging.INFO) 19 | 20 | 21 | # load elastic index and query body information 22 | query_file = args.query 23 | with open(query_file) as f: 24 | query_body = json.loads(f.read()) 25 | 26 | 27 | # Elasticsearch instance 28 | es = Elasticsearch(["https://elastic:-Rel0twWMUk8L-ZtZr=I@192.168.2.126:9200/"], 29 | ca_certs=False, verify_certs=False) 30 | 31 | # Query both index and put into dictionary 32 | index = args.index 33 | res = es.search(index=index, body=query_body) 34 | print(res['hits']['total']['value']) 35 | 36 | 37 | -------------------------------------------------------------------------------- /Chapter13/Chapter13_4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from elasticsearch import Elasticsearch 3 | from datetime import datetime 4 | import argparse 5 | import json 6 | import logging 7 | from pprint import pprint 8 | 9 | # parse options 10 | parser = argparse.ArgumentParser(description='Elsticsearch Query Options') 11 | parser.add_argument("-i", "--index", help="index to query") 12 | parser.add_argument("-q", "--query", help="query file") 13 | 14 | args = parser.parse_args() 15 | 16 | 17 | # logging 18 | today = str(datetime.today()) 19 | logging.basicConfig(filename='elastic_query.log', level=logging.INFO) 20 | 21 | 22 | # load elastic index and query body information 23 | query_file = args.query 24 | with open(query_file) as f: 25 | query_body = json.loads(f.read()) 26 | 27 | 28 | # Elasticsearch instance 29 | es = Elasticsearch(["https://elastic:-Rel0twWMUk8L-ZtZr=I@192.168.2.126:9200/"], 30 | ca_certs=False, verify_certs=False) 31 | 32 | # Query both index and put into dictionary 33 | index = args.index 34 | res = es.search(index=index, body=query_body) 35 | print("Total hits: " + str(res['hits']['total']['value'])) 36 | for hit in res['hits']['hits']: 37 | pprint(hit) 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Chapter13/Chapter13_5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from elasticsearch import Elasticsearch 3 | from datetime import datetime 4 | import argparse 5 | import json 6 | import logging 7 | 8 | # parse options 9 | parser = argparse.ArgumentParser(description='Elsticsearch Query Options') 10 | parser.add_argument("-i", "--index", help="index to query") 11 | parser.add_argument("-q", "--query", help="query file") 12 | 13 | args = parser.parse_args() 14 | 15 | 16 | # logging 17 | today = str(datetime.today()) 18 | logging.basicConfig(filename='elastic_query.log', level=logging.INFO) 19 | 20 | 21 | # load elastic index and query body information 22 | query_file = args.query 23 | with open(query_file) as f: 24 | query_body = json.loads(f.read()) 25 | 26 | 27 | # Elasticsearch instance 28 | es = Elasticsearch(["https://elastic:-Rel0twWMUk8L-ZtZr=I@192.168.2.126:9200/"], 29 | ca_certs=False, verify_certs=False) 30 | 31 | # Query both index and put into dictionary 32 | index = args.index 33 | res = es.search(index=index, body=query_body) 34 | print(res['aggregations']['network_bytes_sum']['value']) 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Chapter13/Chapter13_6.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from elasticsearch import Elasticsearch 3 | from datetime import datetime 4 | import argparse 5 | import json 6 | import logging 7 | from pprint import pprint 8 | 9 | # parse options 10 | parser = argparse.ArgumentParser(description='Elsticsearch Query Options') 11 | parser.add_argument("-i", "--index", help="index to query") 12 | parser.add_argument("-q", "--query", help="query file") 13 | 14 | args = parser.parse_args() 15 | 16 | 17 | # logging 18 | today = str(datetime.today()) 19 | logging.basicConfig(filename='elastic_query.log', level=logging.INFO) 20 | 21 | 22 | # load elastic index and query body information 23 | query_file = args.query 24 | with open(query_file) as f: 25 | query_body = json.loads(f.read()) 26 | 27 | 28 | # Elasticsearch instance 29 | es = Elasticsearch(["https://elastic:-Rel0twWMUk8L-ZtZr=I@192.168.2.126:9200/"], 30 | ca_certs=False, verify_certs=False) 31 | 32 | # Query both index and put into dictionary 33 | index = args.index 34 | res = es.search(index=index, body=query_body) 35 | for hit in res['aggregations']['3']['buckets']: 36 | pprint(hit) 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Chapter13/network_configs/simple_config.conf: -------------------------------------------------------------------------------- 1 | input { 2 | udp { 3 | port => 5144 4 | type => "syslog-ios" 5 | } 6 | } 7 | output { 8 | stdout { codec => json } 9 | elasticsearch { 10 | hosts => ["https://192.168.2.126:9200"] 11 | ssl => true 12 | ssl_certificate_verification => false 13 | user => "elastic" 14 | password => "-Rel0twWMUk8L-ZtZr=I" 15 | index => "cisco-syslog-%{+YYYY.MM.dd}" 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /Chapter13/query_body_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "query": { 3 | "match_all": {} 4 | } 5 | } 6 | 7 | -------------------------------------------------------------------------------- /Chapter13/query_body_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "query": { 3 | "bool": { 4 | "filter": [ 5 | { 6 | "range": { 7 | "@timestamp": { 8 | "gte": "now-10m" 9 | } 10 | } 11 | } 12 | ] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter13/query_body_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "query": { 3 | "bool": { 4 | "must": { 5 | "term": { 6 | "source.ip": "192.168.0.1" 7 | } 8 | }, 9 | "filter": [ 10 | { 11 | "range": { 12 | "@timestamp": { 13 | "gte": "now-10m" 14 | } 15 | } 16 | } 17 | ] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter13/query_body_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "aggs": { 3 | "network_bytes_sum": { 4 | "sum": { 5 | "field": "network.bytes" 6 | } 7 | } 8 | }, 9 | "query": { 10 | "bool": { 11 | "must": { 12 | "term": { 13 | "source.ip": "192.168.0.1" 14 | } 15 | }, 16 | "filter": [ 17 | { 18 | "range": { 19 | "@timestamp": { 20 | "gte": "now-10m" 21 | } 22 | } 23 | } 24 | ] 25 | } 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /Chapter13/query_body_5.json: -------------------------------------------------------------------------------- 1 | { 2 | "aggs": { 3 | "3": { 4 | "terms": { 5 | "field": "source.ip", 6 | "order": { 7 | "1": "desc" 8 | }, 9 | "size": 50 10 | }, 11 | "aggs": { 12 | "1": { 13 | "sum": { 14 | "field": "network.bytes" 15 | } 16 | } 17 | } 18 | } 19 | }, 20 | "size": 0, 21 | "_source": { 22 | "excludes": [] 23 | }, 24 | "stored_fields": [ 25 | "*" 26 | ], 27 | "script_fields": {}, 28 | "docvalue_fields": [ 29 | { 30 | "field": "@timestamp", 31 | "format": "date_time" 32 | }, 33 | { 34 | "field": "event.created", 35 | "format": "date_time" 36 | }, 37 | { 38 | "field": "event.end", 39 | "format": "date_time" 40 | }, 41 | { 42 | "field": "event.start", 43 | "format": "date_time" 44 | }, 45 | { 46 | "field": "file.accessed", 47 | "format": "date_time" 48 | }, 49 | { 50 | "field": "file.created", 51 | "format": "date_time" 52 | }, 53 | { 54 | "field": "file.ctime", 55 | "format": "date_time" 56 | }, 57 | { 58 | "field": "file.mtime", 59 | "format": "date_time" 60 | }, 61 | { 62 | "field": "kafka.block_timestamp", 63 | "format": "date_time" 64 | }, 65 | { 66 | "field": "netflow.collection_time_milliseconds", 67 | "format": "date_time" 68 | }, 69 | { 70 | "field": "netflow.exporter.timestamp", 71 | "format": "date_time" 72 | }, 73 | { 74 | "field": "netflow.flow_end_microseconds", 75 | "format": "date_time" 76 | }, 77 | { 78 | "field": "netflow.flow_end_milliseconds", 79 | "format": "date_time" 80 | }, 81 | { 82 | "field": "netflow.flow_end_nanoseconds", 83 | "format": "date_time" 84 | }, 85 | { 86 | "field": "netflow.flow_end_seconds", 87 | "format": "date_time" 88 | }, 89 | { 90 | "field": "netflow.flow_start_microseconds", 91 | "format": "date_time" 92 | }, 93 | { 94 | "field": "netflow.flow_start_milliseconds", 95 | "format": "date_time" 96 | }, 97 | { 98 | "field": "netflow.flow_start_nanoseconds", 99 | "format": "date_time" 100 | }, 101 | { 102 | "field": "netflow.flow_start_seconds", 103 | "format": "date_time" 104 | }, 105 | { 106 | "field": "netflow.max_export_seconds", 107 | "format": "date_time" 108 | }, 109 | { 110 | "field": "netflow.max_flow_end_microseconds", 111 | "format": "date_time" 112 | }, 113 | { 114 | "field": "netflow.max_flow_end_milliseconds", 115 | "format": "date_time" 116 | }, 117 | { 118 | "field": "netflow.max_flow_end_nanoseconds", 119 | "format": "date_time" 120 | }, 121 | { 122 | "field": "netflow.max_flow_end_seconds", 123 | "format": "date_time" 124 | }, 125 | { 126 | "field": "netflow.min_export_seconds", 127 | "format": "date_time" 128 | }, 129 | { 130 | "field": "netflow.min_flow_start_microseconds", 131 | "format": "date_time" 132 | }, 133 | { 134 | "field": "netflow.min_flow_start_milliseconds", 135 | "format": "date_time" 136 | }, 137 | { 138 | "field": "netflow.min_flow_start_nanoseconds", 139 | "format": "date_time" 140 | }, 141 | { 142 | "field": "netflow.min_flow_start_seconds", 143 | "format": "date_time" 144 | }, 145 | { 146 | "field": "netflow.monitoring_interval_end_milli_seconds", 147 | "format": "date_time" 148 | }, 149 | { 150 | "field": "netflow.monitoring_interval_start_milli_seconds", 151 | "format": "date_time" 152 | }, 153 | { 154 | "field": "netflow.observation_time_microseconds", 155 | "format": "date_time" 156 | }, 157 | { 158 | "field": "netflow.observation_time_milliseconds", 159 | "format": "date_time" 160 | }, 161 | { 162 | "field": "netflow.observation_time_nanoseconds", 163 | "format": "date_time" 164 | }, 165 | { 166 | "field": "netflow.observation_time_seconds", 167 | "format": "date_time" 168 | }, 169 | { 170 | "field": "netflow.system_init_time_milliseconds", 171 | "format": "date_time" 172 | }, 173 | { 174 | "field": "process.start", 175 | "format": "date_time" 176 | }, 177 | { 178 | "field": "suricata.eve.flow.end", 179 | "format": "date_time" 180 | }, 181 | { 182 | "field": "suricata.eve.flow.start", 183 | "format": "date_time" 184 | }, 185 | { 186 | "field": "suricata.eve.timestamp", 187 | "format": "date_time" 188 | }, 189 | { 190 | "field": "suricata.eve.tls.notafter", 191 | "format": "date_time" 192 | }, 193 | { 194 | "field": "suricata.eve.tls.notbefore", 195 | "format": "date_time" 196 | } 197 | ], 198 | "query": { 199 | "bool": { 200 | "must": [ 201 | { 202 | "query_string": { 203 | "query": "*", 204 | "analyze_wildcard": true, 205 | "time_zone": "America/Los_Angeles" 206 | } 207 | } 208 | ], 209 | "filter": [ 210 | { 211 | "range": { 212 | "@timestamp": { 213 | "format": "strict_date_optional_time", 214 | "gte": "2019-11-04T03:48:13.218Z", 215 | "lte": "2019-11-05T03:48:13.218Z" 216 | } 217 | } 218 | } 219 | ], 220 | "should": [], 221 | "must_not": [] 222 | } 223 | } 224 | } 225 | 226 | -------------------------------------------------------------------------------- /Chapter14/Chapter14_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # reference: https://stackoverflow.com/questions/38594717/how-do-i-push-new-files-to-github 3 | 4 | from github import Github, InputGitTreeElement 5 | import os 6 | 7 | github_token = os.environ.get('GITHUB_TOKEN') 8 | configs_dir = 'configs' 9 | github_repo = 'TestRepo' 10 | 11 | # Retrieve the list of files in configs directory 12 | file_list = [] 13 | for dirpath, dirname, filenames in os.walk(configs_dir): 14 | for f in filenames: 15 | file_list.append(configs_dir + "/" + f) 16 | 17 | g = Github(github_token) 18 | repo = g.get_user().get_repo(github_repo) 19 | 20 | commit_message = 'add configs' 21 | master_ref = repo.get_git_ref('heads/master') 22 | master_sha = master_ref.object.sha 23 | base_tree = repo.get_git_tree(master_sha) 24 | 25 | element_list = list() 26 | 27 | for entry in file_list: 28 | with open(entry, 'r') as input_file: 29 | data = input_file.read() 30 | element = InputGitTreeElement(entry, '100644', 'blob', data) 31 | element_list.append(element) 32 | 33 | # Create tree and commit 34 | tree = repo.create_git_tree(element_list, base_tree) 35 | parent = repo.get_git_commit(master_sha) 36 | commit = repo.create_git_commit(commit_message, tree, [parent]) 37 | master_ref.edit(commit.sha) 38 | 39 | -------------------------------------------------------------------------------- /Chapter16/chapter15_topology.virl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | flat 5 | 6 | 7 | 8 | 172.16.1.20 9 | ! IOS Config generated on 2018-07-24 00:23 10 | ! by autonetkit_0.24.0 11 | ! 12 | hostname iosv-1 13 | boot-start-marker 14 | boot-end-marker 15 | ! 16 | vrf definition Mgmt-intf 17 | ! 18 | address-family ipv4 19 | exit-address-family 20 | ! 21 | address-family ipv6 22 | exit-address-family 23 | ! 24 | ! 25 | ! 26 | no aaa new-model 27 | ! 28 | ! 29 | ip cef 30 | ipv6 unicast-routing 31 | ipv6 cef 32 | ! 33 | ! 34 | service timestamps debug datetime msec 35 | service timestamps log datetime msec 36 | no service password-encryption 37 | no service config 38 | enable password cisco 39 | ip classless 40 | ip subnet-zero 41 | no ip domain lookup 42 | ip domain name virl.info 43 | crypto key generate rsa modulus 768 44 | ip ssh server algorithm authentication password 45 | username cisco privilege 15 secret cisco 46 | line vty 0 4 47 | transport input ssh telnet 48 | exec-timeout 720 0 49 | password cisco 50 | login local 51 | line con 0 52 | password cisco 53 | ! 54 | no cdp run 55 | ! 56 | ! 57 | interface Loopback0 58 | description Loopback 59 | ip address 192.168.0.3 255.255.255.255 60 | ! 61 | interface GigabitEthernet0/0 62 | description OOB Management 63 | vrf forwarding Mgmt-intf 64 | ! Configured on launch 65 | no ip address 66 | duplex full 67 | speed auto 68 | no shutdown 69 | ! 70 | interface GigabitEthernet0/1 71 | description to host1 72 | ip address 10.0.0.10 255.255.255.252 73 | ip ospf cost 1 74 | duplex full 75 | speed auto 76 | no shutdown 77 | ! 78 | interface GigabitEthernet0/2 79 | description to nx-osv-1 80 | ip address 10.0.0.5 255.255.255.252 81 | ip ospf cost 1 82 | duplex full 83 | speed auto 84 | no shutdown 85 | ! 86 | ! 87 | ! 88 | router ospf 1 89 | network 192.168.0.3 0.0.0.0 area 0 90 | log-adjacency-changes 91 | passive-interface Loopback0 92 | network 10.0.0.8 0.0.0.3 area 0 93 | network 10.0.0.4 0.0.0.3 area 0 94 | ! 95 | ! 96 | router bgp 1 97 | bgp router-id 192.168.0.3 98 | no synchronization 99 | ! ibgp 100 | ! ibgp peers 101 | ! 102 | neighbor 192.168.0.1 remote-as 1 103 | neighbor 192.168.0.1 description iBGP peer nx-osv-1 104 | neighbor 192.168.0.1 update-source Loopback0 105 | ! 106 | ! 107 | ! 108 | address-family ipv4 109 | network 192.168.0.3 mask 255.255.255.255 110 | neighbor 192.168.0.1 activate 111 | exit-address-family 112 | ! 113 | ! 114 | ! 115 | end 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 172.16.1.21 124 | ! NX-OSv Config generated on 2018-07-24 00:23 125 | ! by autonetkit_0.24.0 126 | ! 127 | version 6.2(1) 128 | license grace-period 129 | ! 130 | hostname nx-osv-1 131 | vdc nx-osv-1 id 1 132 | allocate interface Ethernet2/1-48 133 | allocate interface Ethernet3/1-48 134 | limit-resource vlan minimum 16 maximum 4094 135 | limit-resource vrf minimum 2 maximum 4096 136 | limit-resource port-channel minimum 0 maximum 768 137 | limit-resource u4route-mem minimum 96 maximum 96 138 | limit-resource u6route-mem minimum 24 maximum 24 139 | limit-resource m4route-mem minimum 58 maximum 58 140 | limit-resource m6route-mem minimum 8 maximum 8 141 | 142 | feature telnet 143 | 144 | feature ospf 145 | feature bgp 146 | 147 | username adminbackup password 5 ! role network-operator 148 | username admin password 5 $1$KuOSBsvW$Cy0TSD..gEBGBPjzpDgf51 role network-admin 149 | username cisco password 5 $1$Nk7ZkwH0$fyiRmMMfIheqE3BqvcL0C1 role network-operator 150 | username cisco role network-admin 151 | username lab password 5 $1$buoy/oqy$.EXQz8rCn72ii8qtdldj00 role network-admin 152 | no password strength-check 153 | ip domain-lookup 154 | copp profile strict 155 | snmp-server user lab network-admin auth md5 0x5ceb414591539ee35159fca86fdfa101 priv 0x5ceb414591539ee35159fca86fdfa101 localizedkey 156 | snmp-server user admin network-admin auth md5 0x328945d53e05e8e7207f8c20b142f0b7 priv 0x328945d53e05e8e7207f8c20b142f0b7 localizedkey 157 | snmp-server user cisco network-operator auth md5 0x55b3c64a53fb95518e75358ee75e82e9 priv 0x55b3c64a53fb95518e75358ee75e82e9 localizedkey 158 | snmp-server user cisco network-admin 159 | rmon event 1 log trap public description FATAL(1) owner PMON@FATAL 160 | rmon event 2 log trap public description CRITICAL(2) owner PMON@CRITICAL 161 | rmon event 3 log trap public description ERROR(3) owner PMON@ERROR 162 | rmon event 4 log trap public description WARNING(4) owner PMON@WARNING 163 | rmon event 5 log trap public description INFORMATION(5) owner PMON@INFO 164 | 165 | 166 | vlan 1 167 | 168 | vrf context management 169 | hardware forwarding unicast trace 170 | 171 | interface Loopback0 172 | description Loopback 173 | ip address 192.168.0.1/32 174 | ip router ospf 1 area 0 175 | 176 | interface Ethernet2/1 177 | description to iosv-1 178 | ip address 10.0.0.6/30 179 | ip router ospf 1 area 0 180 | duplex full 181 | mac-address fa16.3e00.0001 182 | no shutdown 183 | 184 | interface Ethernet2/2 185 | description to host2 186 | ip address 10.0.0.14/30 187 | ip router ospf 1 area 0 188 | duplex full 189 | mac-address fa16.3e00.0002 190 | no shutdown 191 | 192 | interface mgmt0 193 | description OOB Management 194 | ! Configured on launch 195 | no ip address 196 | duplex full 197 | mac-address fa16.3e00.0003 198 | no shutdown 199 | vrf member management 200 | 201 | 202 | line console 203 | line vty 204 | router ospf 1 205 | router-id 192.168.0.1 206 | router bgp 1 207 | router-id 192.168.0.1 208 | address-family ipv4 unicast 209 | network 192.168.0.1/32 210 | ! 211 | ! iBGP 212 | ! 213 | ! iBGP peers 214 | ! 215 | neighbor 192.168.0.3 remote-as 1 216 | description iBGP peer iosv-1 217 | update-source Loopback0 218 | address-family ipv4 unicast 219 | ! 220 | ! 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 172.16.1.22 230 | #cloud-config 231 | bootcmd: 232 | - ln -s -t /etc/rc.d /etc/rc.local 233 | hostname: host1 234 | manage_etc_hosts: true 235 | runcmd: 236 | - start ttyS0 237 | - systemctl start getty@ttyS0.service 238 | - systemctl start rc-local 239 | - sed -i '/^\s*PasswordAuthentication\s\+no/d' /etc/ssh/sshd_config 240 | - echo "UseDNS no" >> /etc/ssh/sshd_config 241 | - service ssh restart 242 | - service sshd restart 243 | users: 244 | - default 245 | - gecos: User configured by VIRL Configuration Engine 0.23.10 246 | lock-passwd: false 247 | name: cisco 248 | plain-text-passwd: cisco 249 | shell: /bin/bash 250 | ssh-authorized-keys: 251 | - VIRL-USER-SSH-PUBLIC-KEY 252 | sudo: ALL=(ALL) ALL 253 | write_files: 254 | - path: /etc/init/ttyS0.conf 255 | owner: root:root 256 | content: | 257 | # ttyS0 - getty 258 | # This service maintains a getty on ttyS0 from the point the system is 259 | # started until it is shut down again. 260 | start on stopped rc or RUNLEVEL=[12345] 261 | stop on runlevel [!12345] 262 | respawn 263 | exec /sbin/getty -L 115200 ttyS0 vt102 264 | permissions: '0644' 265 | - path: /etc/systemd/system/dhclient@.service 266 | content: | 267 | [Unit] 268 | Description=Run dhclient on %i interface 269 | After=network.target 270 | [Service] 271 | Type=oneshot 272 | ExecStart=/sbin/dhclient %i -pf /var/run/dhclient.%i.pid -lf /var/lib/dhclient/dhclient.%i.lease 273 | RemainAfterExit=yes 274 | owner: root:root 275 | permissions: '0644' 276 | - path: /etc/rc.local 277 | owner: root:root 278 | permissions: '0755' 279 | content: |- 280 | #!/bin/sh 281 | ifconfig eth1 up 10.0.0.9 netmask 255.255.255.252 282 | route add -net 10.0.0.0/8 gw 10.0.0.10 dev eth1 283 | route add -net 192.168.0.0/29 gw 10.0.0.10 dev eth1 284 | exit 0 285 | 286 | 287 | 288 | 289 | 290 | 291 | 172.16.1.23 292 | #cloud-config 293 | bootcmd: 294 | - ln -s -t /etc/rc.d /etc/rc.local 295 | hostname: host2 296 | manage_etc_hosts: true 297 | runcmd: 298 | - start ttyS0 299 | - systemctl start getty@ttyS0.service 300 | - systemctl start rc-local 301 | - sed -i '/^\s*PasswordAuthentication\s\+no/d' /etc/ssh/sshd_config 302 | - echo "UseDNS no" >> /etc/ssh/sshd_config 303 | - service ssh restart 304 | - service sshd restart 305 | users: 306 | - default 307 | - gecos: User configured by VIRL Configuration Engine 0.23.10 308 | lock-passwd: false 309 | name: cisco 310 | plain-text-passwd: cisco 311 | shell: /bin/bash 312 | ssh-authorized-keys: 313 | - VIRL-USER-SSH-PUBLIC-KEY 314 | sudo: ALL=(ALL) ALL 315 | write_files: 316 | - path: /etc/init/ttyS0.conf 317 | owner: root:root 318 | content: | 319 | # ttyS0 - getty 320 | # This service maintains a getty on ttyS0 from the point the system is 321 | # started until it is shut down again. 322 | start on stopped rc or RUNLEVEL=[12345] 323 | stop on runlevel [!12345] 324 | respawn 325 | exec /sbin/getty -L 115200 ttyS0 vt102 326 | permissions: '0644' 327 | - path: /etc/systemd/system/dhclient@.service 328 | content: | 329 | [Unit] 330 | Description=Run dhclient on %i interface 331 | After=network.target 332 | [Service] 333 | Type=oneshot 334 | ExecStart=/sbin/dhclient %i -pf /var/run/dhclient.%i.pid -lf /var/lib/dhclient/dhclient.%i.lease 335 | RemainAfterExit=yes 336 | owner: root:root 337 | permissions: '0644' 338 | - path: /etc/rc.local 339 | owner: root:root 340 | permissions: '0755' 341 | content: |- 342 | #!/bin/sh 343 | ifconfig eth1 up 10.0.0.13 netmask 255.255.255.252 344 | route add -net 10.0.0.0/8 gw 10.0.0.14 dev eth1 345 | route add -net 192.168.0.0/29 gw 10.0.0.14 dev eth1 346 | exit 0 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | -------------------------------------------------------------------------------- /Chapter16/chapter16_10_ping.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess 4 | 5 | host_list = ['www.cisco.com', 'www.google.com'] 6 | 7 | ping_time = [] 8 | 9 | for host in host_list: 10 | p = subprocess.Popen(['ping', '-c', '1', host], stdout=subprocess.PIPE) 11 | result = p.communicate()[0] 12 | host = result.split()[1] 13 | time = result.split()[13] 14 | ping_time.append((host, time)) 15 | 16 | print(ping_time) 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Chapter16/chapter16_1_xml.py: -------------------------------------------------------------------------------- 1 | #!/usr/env/bin python3 2 | 3 | import xml.etree.ElementTree as ET 4 | import pprint 5 | 6 | with open('chapter15_topology.virl', 'rt') as f: 7 | tree = ET.parse(f) 8 | 9 | devices = {} 10 | 11 | for node in tree.findall('./{http://www.cisco.com/VIRL}node'): 12 | name = node.attrib.get('name') 13 | devices[name] = {} 14 | for attr_name, attr_value in sorted(node.attrib.items()): 15 | devices[name][attr_name] = attr_value 16 | 17 | # Custom attributes 18 | devices['iosv-1']['os'] = '15.6(3)M2' 19 | devices['nx-osv-1']['os'] = '7.3(0)D1(1)' 20 | devices['host1']['os'] = '16.04' 21 | devices['host2']['os'] = '16.04' 22 | 23 | pprint.pprint(devices) 24 | 25 | 26 | -------------------------------------------------------------------------------- /Chapter16/chapter16_2_validation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import requests 4 | import json 5 | import xml.etree.ElementTree as ET 6 | import unittest 7 | 8 | # Getting NX-OSv Version with NXAPI 9 | url='http://192.168.2.50/ins' 10 | switchuser='cisco' 11 | switchpassword='cisco' 12 | 13 | myheaders={'content-type':'application/json-rpc'} 14 | payload=[ 15 | { 16 | "jsonrpc": "2.0", 17 | "method": "cli", 18 | "params": { 19 | "cmd": "show version", 20 | "version": 1.2 21 | }, 22 | "id": 1 23 | } 24 | ] 25 | response = requests.post(url,data=json.dumps(payload), headers=myheaders,auth=(switchuser,switchpassword)).json() 26 | 27 | nxos_version = response['result']['body']['sys_ver_str'] 28 | 29 | # Parse VIRL file, same as in example 1 30 | with open('chapter15_topology.virl', 'rt') as f: 31 | tree = ET.parse(f) 32 | 33 | devices = {} 34 | 35 | for node in tree.findall('./{http://www.cisco.com/VIRL}node'): 36 | name = node.attrib.get('name') 37 | devices[name] = {} 38 | for attr_name, attr_value in sorted(node.attrib.items()): 39 | devices[name][attr_name] = attr_value 40 | 41 | # Custom attributes 42 | devices['iosv-1']['os'] = '15.6(3)M2' 43 | devices['nx-osv-1']['os'] = '7.3(0)D1(1)' 44 | devices['host1']['os'] = '16.04' 45 | devices['host2']['os'] = '16.04' 46 | 47 | # Unittest Test case 48 | class TestNXOSVersion(unittest.TestCase): 49 | def test_version(self): 50 | self.assertEqual(nxos_version, devices['nx-osv-1']['os']) 51 | 52 | if __name__ == '__main__': 53 | unittest.main() 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Chapter16/chapter16_3_test_fail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import requests 4 | import json 5 | import xml.etree.ElementTree as ET 6 | import unittest 7 | 8 | # Getting NX-OSv Version with NXAPI 9 | url='http://192.168.2.50/ins' 10 | switchuser='cisco' 11 | switchpassword='cisco' 12 | 13 | myheaders={'content-type':'application/json-rpc'} 14 | payload=[ 15 | { 16 | "jsonrpc": "2.0", 17 | "method": "cli", 18 | "params": { 19 | "cmd": "show version", 20 | "version": 1.2 21 | }, 22 | "id": 1 23 | } 24 | ] 25 | response = requests.post(url,data=json.dumps(payload), headers=myheaders,auth=(switchuser,switchpassword)).json() 26 | 27 | nxos_version = response['result']['body']['sys_ver_str'] 28 | 29 | # Parse VIRL file, same as in example 1 30 | with open('chapter15_topology.virl', 'rt') as f: 31 | tree = ET.parse(f) 32 | 33 | devices = {} 34 | 35 | for node in tree.findall('./{http://www.cisco.com/VIRL}node'): 36 | name = node.attrib.get('name') 37 | devices[name] = {} 38 | for attr_name, attr_value in sorted(node.attrib.items()): 39 | devices[name][attr_name] = attr_value 40 | 41 | # Custom attributes 42 | devices['iosv-1']['os'] = '15.6(3)M2' 43 | devices['nx-osv-1']['os'] = '7.4(0)D1(1)' 44 | devices['host1']['os'] = '16.04' 45 | devices['host2']['os'] = '16.04' 46 | 47 | # Unittest Test case 48 | class TestNXOSVersion(unittest.TestCase): 49 | def test_version(self): 50 | self.assertEqual(nxos_version, devices['nx-osv-1']['os']) 51 | 52 | if __name__ == '__main__': 53 | unittest.main() 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Chapter16/chapter16_4_unittest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | 5 | class SimpleTest(unittest.TestCase): 6 | def test(self): 7 | one = 'a' 8 | two = 'a' 9 | self.assertEqual(one, two) 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Chapter16/chapter16_5_more_unittest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Examples from https://pymotw.com/3/unittest/index.html#module-unittest 3 | 4 | import unittest 5 | 6 | class Output(unittest.TestCase): 7 | def testPass(self): 8 | return 9 | 10 | def testFail(self): 11 | self.assertFalse(True, 'this is a failed message') 12 | 13 | def testError(self): 14 | raise RuntimeError('Test error!') 15 | 16 | def testAssesrtTrue(self): 17 | self.assertTrue(True) 18 | 19 | def testAssertFalse(self): 20 | self.assertFalse(False) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter16/chapter16_5_more_unittest_mocks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Reference Johannes Fahrenkrug's answer on https://stackoverflow.com/questions/15753390/how-can-i-mock-requests-and-the-response 3 | import requests 4 | import unittest 5 | from unittest import mock 6 | 7 | # Our class making API Call using requests 8 | class MyClass: 9 | def fetch_json(self, url): 10 | response = requests.get(url) 11 | return response.json() 12 | 13 | # This method will be used by the mock to replace requests.get 14 | def mocked_requests_get(*args, **kwargs): 15 | class MockResponse: 16 | def __init__(self, json_data, status_code): 17 | self.json_data = json_data 18 | self.status_code = status_code 19 | 20 | def json(self): 21 | return self.json_data 22 | 23 | if args[0] == 'http://url-1.com/test.json': 24 | return MockResponse({"key1": "value1"}, 200) 25 | elif args[0] == 'http://url-2.com/test.json': 26 | return MockResponse({"key2": "value2"}, 200) 27 | 28 | return MockResponse(None, 404) 29 | 30 | # Our test case class 31 | class MyClassTestCase(unittest.TestCase): 32 | # We patch 'requests.get' with our own method. The mock object is passed in to our test case method. 33 | @mock.patch('requests.get', side_effect=mocked_requests_get) 34 | def test_fetch(self, mock_get): 35 | # Assert requests.get calls 36 | my_class = MyClass() 37 | # call to url-1 38 | json_data = my_class.fetch_json('http://url-1.com/test.json') 39 | self.assertEqual(json_data, {"key1": "value1"}) 40 | # call to url-2 41 | json_data = my_class.fetch_json('http://url-2.com/test.json') 42 | self.assertEqual(json_data, {"key2": "value2"}) 43 | # call to url-3 that we did not mock 44 | json_data = my_class.fetch_json('http://url-3.com/test.json') 45 | self.assertIsNone(json_data) 46 | 47 | 48 | if __name__ == '__main__': 49 | unittest.main() 50 | 51 | -------------------------------------------------------------------------------- /Chapter16/chapter16_6_pytest_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | def test_passing(): 4 | assert(1, 2, 3) == (1, 2, 3) 5 | 6 | def test_failing(): 7 | assert(1, 2, 3) == (3, 2, 1) 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Chapter16/chapter16_7_pytest_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | class router(object): 4 | def __init__(self, hostname=None, os=None, device_type='cisco_ios'): 5 | self.hostname = hostname 6 | self.os = os 7 | self.device_type = device_type 8 | self.interfaces = 24 9 | 10 | def test_defaults(): 11 | r1 = router() 12 | assert r1.hostname == None 13 | assert r1.os == None 14 | assert r1.device_type == 'cisco_ios' 15 | assert r1.interfaces == 24 16 | 17 | def test_non_defaults(): 18 | r2 = router(hostname='lax-r2', os='nxos', device_type='cisco_nxos') 19 | assert r2.hostname == 'lax-r2' 20 | assert r2.os == 'nxos' 21 | assert r2.device_type == 'cisco_nxos' 22 | assert r2.interfaces == 24 23 | 24 | 25 | -------------------------------------------------------------------------------- /Chapter16/chapter16_8_pytest_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import requests 4 | import json 5 | import xml.etree.ElementTree as ET 6 | 7 | # Getting NX-OSv Version with NXAPI 8 | url='http://192.168.2.50/ins' 9 | switchuser='cisco' 10 | switchpassword='cisco' 11 | 12 | myheaders={'content-type':'application/json-rpc'} 13 | payload=[ 14 | { 15 | "jsonrpc": "2.0", 16 | "method": "cli", 17 | "params": { 18 | "cmd": "show version", 19 | "version": 1.2 20 | }, 21 | "id": 1 22 | } 23 | ] 24 | response = requests.post(url,data=json.dumps(payload), headers=myheaders,auth=(switchuser,switchpassword)).json() 25 | 26 | nxos_version = response['result']['body']['sys_ver_str'] 27 | 28 | # Parse VIRL file, same as in example 1 29 | with open('chapter15_topology.virl', 'rt') as f: 30 | tree = ET.parse(f) 31 | 32 | devices = {} 33 | 34 | for node in tree.findall('./{http://www.cisco.com/VIRL}node'): 35 | name = node.attrib.get('name') 36 | devices[name] = {} 37 | for attr_name, attr_value in sorted(node.attrib.items()): 38 | devices[name][attr_name] = attr_value 39 | 40 | # Custom attributes 41 | devices['iosv-1']['os'] = '15.6(3)M2' 42 | devices['nx-osv-1']['os'] = '7.3(0)D1(1)' 43 | devices['host1']['os'] = '16.04' 44 | devices['host2']['os'] = '16.04' 45 | 46 | # pytest test case 47 | def test_version(): 48 | assert devices['nx-osv-1']['os'] == nxos_version 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Chapter16/chapter16_9_pytest_4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import requests 3 | import json 4 | import pytest 5 | 6 | # Getting NX-OSv Version with NXAPI 7 | url='http://172.16.1.21/ins' 8 | switchuser='cisco' 9 | switchpassword='cisco' 10 | 11 | myheaders={'content-type':'application/json-rpc'} 12 | payload=[ 13 | { 14 | "jsonrpc": "2.0", 15 | "method": "cli", 16 | "params": { 17 | "cmd": "show version", 18 | "version": 1.2 19 | }, 20 | "id": 1 21 | } 22 | ] 23 | response = requests.post(url,data=json.dumps(payload), headers=myheaders,auth=(switchuser,switchpassword)).json() 24 | nxos_version = response['result']['body']['sys_ver_str'] 25 | 26 | def test_transaction(): 27 | assert nxos_version != False 28 | 29 | -------------------------------------------------------------------------------- /Chapter16/pyATS/chapter16_11_pyats_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # derived from https://devnet-pubhub-site.s3.amazonaws.com/media/pyats/docs/getting_started/index.html 4 | # 5 | from pyats.topology import loader 6 | 7 | # load testbed 8 | testbed = loader.load('chapter16_pyats_testbed_1.yml') 9 | 10 | # access the device 11 | testbed.devices 12 | lax_edg_r1 = testbed.devices['lax-edg-r1'] 13 | 14 | # establish connectivity 15 | lax_edg_r1.connect() 16 | 17 | # issue command 18 | print(lax_edg_r1.execute('show version')) 19 | 20 | # disconnect 21 | lax_edg_r1.disconnect() 22 | 23 | 24 | -------------------------------------------------------------------------------- /Chapter16/pyATS/chapter16_12_pyats_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # from https://devnet-pubhub-site.s3.amazonaws.com/media/pyats/docs/getting_started/index.html 4 | # 5 | 6 | from pyats import aetest 7 | import re 8 | 9 | class CommonSetup(aetest.CommonSetup): 10 | 11 | @aetest.subsection 12 | def check_topology(self, 13 | testbed, 14 | iosv1_name = 'lax-edg-r1', 15 | nxosv1_name = 'lax-cor-r1'): 16 | ios1 = testbed.devices[iosv1_name] 17 | nxos1 = testbed.devices[nxosv1_name] 18 | 19 | # add them to testscript parameters 20 | self.parent.parameters.update(ios1 = ios1, nxos1 = nxos1) 21 | 22 | # get corresponding links 23 | links = ios1.find_links(nxos1) 24 | 25 | assert len(links) >= 1, 'require one link between ios1 and nxos1' 26 | 27 | 28 | @aetest.subsection 29 | def establish_connections(self, steps, ios1): 30 | with steps.start('Connecting to %s' % ios1.name): 31 | ios1.connect() 32 | 33 | 34 | @aetest.loop(device = ('ios1',)) 35 | class PingTestcase(aetest.Testcase): 36 | 37 | @aetest.test.loop(destination = ('10.0.0.1', '10.0.0.2')) 38 | def ping(self, device, destination): 39 | try: 40 | result = self.parameters[device].ping(destination) 41 | 42 | except Exception as e: 43 | self.failed('Ping {} from device {} failed with error: {}'.format( 44 | destination, 45 | device, 46 | str(e), 47 | ), 48 | goto = ['exit']) 49 | else: 50 | match = re.search(r'Success rate is (?P\d+) percent', result) 51 | success_rate = match.group('rate') 52 | 53 | 54 | class CommonCleanup(aetest.CommonCleanup): 55 | 56 | @aetest.subsection 57 | def disconnect(self, steps, ios1): 58 | with steps.start('Disconnecting from %s' % ios1.name): 59 | ios1.disconnect() 60 | 61 | 62 | if __name__ == '__main__': 63 | import argparse 64 | from pyats.topology import loader 65 | 66 | parser = argparse.ArgumentParser() 67 | parser.add_argument('--testbed', dest = 'testbed', 68 | type = loader.load) 69 | 70 | args, unknown = parser.parse_known_args() 71 | 72 | aetest.main(**vars(args)) 73 | 74 | -------------------------------------------------------------------------------- /Chapter16/pyATS/chapter16_pyats_testbed_1.yml: -------------------------------------------------------------------------------- 1 | testbed: 2 | name: Chapter_16_pyATS 3 | tacacs: 4 | username: cisco 5 | passwords: 6 | tacacs: cisco 7 | enable: cisco 8 | 9 | devices: 10 | lax-edg-r1: 11 | alias: lax-edg-r1 12 | type: ios 13 | connections: 14 | defaults: 15 | class: unicon.Unicon 16 | management: 17 | ip: 192.168.2.51 18 | protocol: ssh 19 | 20 | topology: 21 | lax-edg-r1: 22 | interfaces: 23 | GigabitEthernet0/1: 24 | ipv4: 10.0.0.1/30 25 | link: link-1 26 | type: ethernet 27 | Loopback0: 28 | ipv4: 192.168.0.10/32 29 | link: iosv-1_Loopback0 30 | type: loopback 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Chapter16/pyATS/chapter16_pyats_testbed_2.yml: -------------------------------------------------------------------------------- 1 | testbed: 2 | name: Chapter_16_pyATS 3 | tacacs: 4 | username: cisco 5 | passwords: 6 | tacacs: cisco 7 | enable: cisco 8 | 9 | devices: 10 | lax-edg-r1: 11 | alias: iosv-1 12 | type: ios 13 | connections: 14 | defaults: 15 | class: unicon.Unicon 16 | vty: 17 | ip: 192.168.2.50 18 | protocol: ssh 19 | 20 | lax-cor-r1: 21 | alias: nxosv-1 22 | type: ios 23 | connections: 24 | defaults: 25 | class: unicon.Unicon 26 | vty: 27 | ip: 192.168.2.51 28 | protocol: ssh 29 | 30 | topology: 31 | lax-edg-r1: 32 | interfaces: 33 | GigabitEthernet0/1: 34 | ipv4: 10.0.0.1/30 35 | link: link-1 36 | type: ethernet 37 | Loopback0: 38 | ipv4: 192.168.0.10/32 39 | link: lax-edg-r1_Loopback0 40 | type: loopback 41 | lax-cor-r1: 42 | interfaces: 43 | Eth2/1: 44 | ipv4: 10.0.0.2/30 45 | link: link-1 46 | type: ethernet 47 | Loopback0: 48 | ipv4: 192.168.0.100/32 49 | link: lax-cor-r1_Loopback0 50 | type: loopback 51 | 52 | 53 | -------------------------------------------------------------------------------- /Chapter16/result.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Errata.md: -------------------------------------------------------------------------------- 1 | # Errata and Improvements 2 | 3 | ## Page 173, Chapter 5 4 | The book stated the Nokia SR Linux default username and password are both admin. This is true prior to release 22.11.1. 5 | The username for releases after that version is still admin, but the password is set to NokiaSr1!, please see https://containerlab.dev/manual/kinds/srl/. 6 | 7 | ## CML NX-OSv no longer available in the latest CML 8 | Thank you [Grana2codes](https://github.com/Grana2codes), Paolo G., Jilles C. for pointing out the issue. In the latest CML, NX-OSv is still available for use within CML, but no longer included in teh refplat IOS image (more information [here](https://developer.cisco.com/docs/modeling-labs/#!nx-os/overview)). You can still download the image [here](https://developer.cisco.com/docs/modeling-labs/#!downloading-files-for-cml-installation). Or you can update the image definition from nxosv to nxosv9000 instead. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mastering-Python-Networking-Fourth-Edition 2 | This is the code repository for [Mastering Python Networking Fourth Edition](https://www.amazon.com/Mastering-Python-Networking-frameworks-automation/dp/180323461X/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=&sr=), published by Packt. 3 | 4 | **Utilize Python packages and frameworks for network automation,monitoring, cloud, and management** 5 | 6 | ## About the book 7 | 8 | The new edition is revised and updated with Python 3.10 along with new chapters on AsyncIO and Docker containers. The book will provide examples and guidance to manage your network, improve reliability, and simplify your task. The fourth edition also includes network context examples of Ansible, Flask, Elasticsearch, Git, GitLab and more. 9 | 10 | ## What you will learn 11 | 12 | - Use Python to interact with network devices 13 | - Understand Docker as a tool that you can use for the development and deployment 14 | - Use Python and various other tools to obtain information from the network 15 | - Learn how to use ELK for network data analysis 16 | - Utilize Flask and construct high-level API to interact with in-house applications 17 | - Discover the new AsyncIO feature and its concepts in Python 3 18 | - Explore Test-Driven Development concepts and use PyTest to drive code test coverage 19 | - Understand how GitLab can be used with DevOps practices in networking 20 | 21 | 22 | ## Table of Contents 23 | ### Chapters 24 | 1. Review of TCP/IP Protocol Suite and Python 25 | 2. Low-Level Network Device Interactions 26 | 3. APIs and Intent-Driven Networking 27 | 4. The Python Automation Framework – Ansible 28 | 5. Docker Containers for Network Engineers 29 | 6. Network Security with Python 30 | 7. Network Monitoring with Python – Part 1 31 | 8. Network Monitoring with Python – Part 2 32 | 9. Building Network Web Services with Python 33 | 10. Introduction to Async IO 34 | 11. AWS Cloud Networking 35 | 12. Azure Cloud Networking 36 | 13. Network Data Analysis with Elastic Stack 37 | 14. Working with Git 38 | 15. Continuous Integration with GitLab 39 | 16. Test-Driven Development for Networks 40 | 41 | > If you feel this book is for you, get your [copy](https://www.amazon.com/Mastering-Python-Networking-frameworks-automation/dp/180323461X/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=&sr=) today! Coding 42 | 43 | 44 | ### Following is what you need for this book: ### 45 | 46 | To get the most out of this book, some basic hands-on network operation knowledge and Python knowledge is recommended. Most of the chapters can be read in any order, except for Chapter 4 and Chapter 5, which introduce base technologies that will be used later in the book. Besides the basic software and hardware tools introduced at the beginning of the book, new tools relevant to each of the chapters will be introduced in the respective chapters. 47 | It is highly recommended to follow and practice the examples shown in your network lab. 48 | 49 | ### Important Information 50 | 51 | Refer to the [Errata](https://github.com/PacktPublishing/Mastering-Python-Networking-Fourth-Edition/blob/main/Errata.md) for corrections of typos and other mistakes and improvements like refactoring code. 52 | 53 | 54 | ## Know more on the Discord server Coding 55 | 56 | You can get more engaged on the discord server for more latest updates and discussions in the community at [Discord link as text here](https://packt.link/networkautomationcommunity) 57 | 58 | ## Download a free PDF Coding 59 | 60 | _If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost. Simply click on the link to claim your free PDF._ 61 | [https://packt.link/free-ebook/9781803234618](https://packt.link/free-ebook/9781803234618) Coding 62 | 63 | We also provide a PDF file that has color images of the screenshots/diagrams used in this book at "https://packt.link/D2Ttl" Coding 64 | 65 | 66 | ## Get to Know the Author 67 | 68 | Eric Chou is a seasoned technologist with over 20 years of experience. He has worked on some of the largest networks in the industry while working at Amazon, Azure, and other Fortune 500 companies. Eric is passionate about network automation, Python, DevOps, and helping companies build better security postures. In addition to being the author of Mastering Python Networking (Packt), he is the author or co-author of other top-selling books and highly-rated online classes. Eric is the primary inventor or co-inventor for three U.S. patents in IP telephony and networking. He shares his deep interest in technology through his books, classes, blog, and contributes to some of the popular Python open source projects. 69 | --------------------------------------------------------------------------------