├── requirements.txt ├── print_token.py ├── path_trace_prep.py ├── env_lab.py ├── LICENSE ├── README.md └── path_trace.py /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | urllib3 3 | -------------------------------------------------------------------------------- /print_token.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | from env_lab import apicem 3 | import requests 4 | import json 5 | import urllib3 6 | from requests.auth import HTTPBasicAuth 7 | 8 | 9 | # Silence the insecure warning due to SSL Certificate 10 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 11 | 12 | headers = { 13 | 'content-type': "application/json", 14 | 'x-auth-token': "" 15 | } 16 | 17 | def apic_login(host, username, password): 18 | """ 19 | Use the REST API to Log into an DNA_CENTER and retrieve token 20 | """ 21 | url = "https://{}/api/system/v1/auth/token".format(host) 22 | 23 | # Make Login request and return the response body 24 | response = requests.request("POST", url, auth=HTTPBasicAuth(username, password), 25 | headers=headers, verify=False) 26 | 27 | # print the token 28 | print(response.text) 29 | 30 | apic_login(apicem['host'], apicem['username'], apicem['password']) 31 | -------------------------------------------------------------------------------- /path_trace_prep.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from env_lab import apicem 4 | from time import sleep 5 | import json 6 | import requests 7 | import sys 8 | import urllib3 9 | from requests.auth import HTTPBasicAuth 10 | 11 | # Silence the insecure warning due to SSL Certificate 12 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 13 | 14 | headers = { 15 | 'content-type': "application/json", 16 | 'x-auth-token': "" 17 | } 18 | 19 | def apic_login(host, username, password): 20 | """ 21 | Use the REST API to Log into an DNA_CENTER and retrieve token 22 | """ 23 | url = "https://{}/api/system/v1/auth/token".format(host) 24 | 25 | # Make Login request and return the response body 26 | response = requests.request("POST", url, auth=HTTPBasicAuth(username, password), 27 | headers=headers, verify=False) 28 | # print response 29 | return response.json()["Token"] 30 | 31 | 32 | # Entry point for program 33 | if __name__ == '__main__': 34 | # Setup Arg Parse for Command Line parameters 35 | import argparse 36 | parser = argparse.ArgumentParser() 37 | 38 | # Command Line Parameters for Source and Destination IP 39 | parser.add_argument("source_ip", help = "Source IP Address") 40 | parser.add_argument("destination_ip", help = "Destination IP Address") 41 | args = parser.parse_args() 42 | 43 | # Get Source and Destination IPs from Command Line 44 | source_ip = args.source_ip 45 | destination_ip = args.destination_ip 46 | 47 | # Print Starting message 48 | print("Running Troubleshooting Script for ") 49 | print(" Source IP: {} ".format(source_ip)) 50 | print(" Destination IP: {}".format(destination_ip)) 51 | print("") 52 | 53 | # Log into the dna-EM Controller to get Ticket 54 | login = apic_login(apicem["host"], apicem["username"], apicem["password"]) 55 | -------------------------------------------------------------------------------- /env_lab.py: -------------------------------------------------------------------------------- 1 | """Set the Environment Information Needed to Access Your Lab! 2 | 3 | The provided sample code in this repository will reference this file to get the 4 | information needed to connect to your lab backend. You provide this info here 5 | once and the scripts in this repository will access it as needed by the lab. 6 | 7 | 8 | Copyright (c) 2018 Cisco and/or its affiliates. 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | """ 28 | 29 | 30 | # User Input 31 | 32 | # Please select the lab environment that you will be using today 33 | # sandbox - Cisco DevNet Always-On / Reserved Sandboxes 34 | # express - Cisco DevNet Express Lab Backend 35 | # custom - Your Own "Custom" Lab Backend 36 | ENVIRONMENT_IN_USE = "sandbox" 37 | 38 | # Custom Lab Backend 39 | # apicem = { 40 | # "host": "192.168.139.73", 41 | # "username": "admin", 42 | # "password": "Cisco123" 43 | # } 44 | 45 | # End User Input 46 | 47 | 48 | # Set the 'Environment Variables' based on the lab environment in use 49 | if ENVIRONMENT_IN_USE == "sandbox": 50 | apicem = { 51 | "host": "sandboxdnac.cisco.com", 52 | "port": 443, 53 | "username": "devnetuser", 54 | "password": "Cisco123!" 55 | } 56 | 57 | # Values for the Always On IOS XE Sandbox 58 | IOS_XE_1 = { 59 | "host": "ios-xe-mgmt.cisco.com", 60 | "username": "root", 61 | "password": "D_Vay!_10&", 62 | "netconf_port": 10000, 63 | "restconf_port": 9443, 64 | "ssh_port": 8181 65 | } 66 | 67 | # Values for the Reservable IOS XE Sandbox 68 | IOS_XE_2 = { 69 | "host": "10.10.20.48", 70 | "username": "cisco", 71 | "password": "cisco_1234!", 72 | "netconf_port": 830, 73 | "restconf_port": 443, 74 | "ssh_port": 22 75 | } 76 | 77 | # Values for the Always On NX-OS Sandbox 78 | NXOS_1 = { 79 | "host": "sbx-nxos-mgmt.cisco.com", 80 | "username": "admin", 81 | "password": "Admin_1234!", 82 | "netconf_port": 10000, 83 | "restconf_port": 443, 84 | "nxapi_port": 80, 85 | "ssh_port": 8181 86 | } 87 | 88 | elif ENVIRONMENT_IN_USE == "express": 89 | DNA_CENTER = { 90 | "host": "sandboxdnac.cisco.com", 91 | "port": 443, 92 | "username": "devnetuser", 93 | "password": "Cisco123!" 94 | } 95 | 96 | NFVIS_SERVER = { 97 | "host": "198.18.134.46", 98 | "port": 443, 99 | "username": "admin", 100 | "password": "C1sco12345_" 101 | } 102 | 103 | # Values for the CSR1 from the dCloud Pod 104 | IOS_XE_1 = { 105 | "host": "198.18.134.11", 106 | "username": "admin", 107 | "password": "C1sco12345", 108 | "netconf_port": 830, 109 | "restconf_port": 443, 110 | "ssh_port": 22 111 | } 112 | 113 | # Values for the CSR2 from the dCloud Pod 114 | IOS_XE_2 = { 115 | "host": "198.18.134.12", 116 | "username": "admin", 117 | "password": "C1sco12345", 118 | "netconf_port": 830, 119 | "restconf_port": 443, 120 | "ssh_port": 22 121 | } 122 | 123 | # Values for the Always On NX-OS Sandbox 124 | NXOS_1 = { 125 | "host": "sbx-nxos-mgmt.cisco.com", 126 | "username": "admin", 127 | "password": "Admin_1234!", 128 | "netconf_port": 10000, 129 | "restconf_port": 443, 130 | "nxapi_port": 80, 131 | "ssh_port": 8181 132 | } 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CISCO SAMPLE CODE LICENSE 2 | Version 1.0 3 | Copyright (c) 2017 Cisco and/or its affiliates 4 | 5 | These terms govern this Cisco example or demo source code and its 6 | associated documentation (together, the "Sample Code"). By downloading, 7 | copying, modifying, compiling, or redistributing the Sample Code, you 8 | accept and agree to be bound by the following terms and conditions (the 9 | "License"). If you are accepting the License on behalf of an entity, you 10 | represent that you have the authority to do so (either you or the entity, 11 | "you"). Sample Code is not supported by Cisco TAC and is not tested for 12 | quality or performance. This is your only license to the Sample Code and 13 | all rights not expressly granted are reserved. 14 | 15 | 1. LICENSE GRANT: Subject to the terms and conditions of this License, 16 | Cisco hereby grants to you a perpetual, worldwide, non-exclusive, non- 17 | transferable, non-sublicensable, royalty-free license to copy and 18 | modify the Sample Code in source code form, and compile and 19 | redistribute the Sample Code in binary/object code or other executable 20 | forms, in whole or in part, solely for use with Cisco products and 21 | services. For interpreted languages like Java and Python, the 22 | executable form of the software may include source code and 23 | compilation is not required. 24 | 25 | 2. CONDITIONS: You shall not use the Sample Code independent of, or to 26 | replicate or compete with, a Cisco product or service. Cisco products 27 | and services are licensed under their own separate terms and you shall 28 | not use the Sample Code in any way that violates or is inconsistent 29 | with those terms (for more information, please visit: 30 | www.cisco.com/go/terms. 31 | 32 | 3. OWNERSHIP: Cisco retains sole and exclusive ownership of the Sample 33 | Code, including all intellectual property rights therein, except with 34 | respect to any third-party material that may be used in or by the 35 | Sample Code. Any such third-party material is licensed under its own 36 | separate terms (such as an open source license) and all use must be in 37 | full accordance with the applicable license. This License does not 38 | grant you permission to use any trade names, trademarks, service 39 | marks, or product names of Cisco. If you provide any feedback to Cisco 40 | regarding the Sample Code, you agree that Cisco, its partners, and its 41 | customers shall be free to use and incorporate such feedback into the 42 | Sample Code, and Cisco products and services, for any purpose, and 43 | without restriction, payment, or additional consideration of any kind. 44 | If you initiate or participate in any litigation against Cisco, its 45 | partners, or its customers (including cross-claims and counter-claims) 46 | alleging that the Sample Code and/or its use infringe any patent, 47 | copyright, or other intellectual property right, then all rights 48 | granted to you under this License shall terminate immediately without 49 | notice. 50 | 51 | 4. LIMITATION OF LIABILITY: CISCO SHALL HAVE NO LIABILITY IN CONNECTION 52 | WITH OR RELATING TO THIS LICENSE OR USE OF THE SAMPLE CODE, FOR 53 | DAMAGES OF ANY KIND, INCLUDING BUT NOT LIMITED TO DIRECT, INCIDENTAL, 54 | AND CONSEQUENTIAL DAMAGES, OR FOR ANY LOSS OF USE, DATA, INFORMATION, 55 | PROFITS, BUSINESS, OR GOODWILL, HOWEVER CAUSED, EVEN IF ADVISED OF THE 56 | POSSIBILITY OF SUCH DAMAGES. 57 | 58 | 5. DISCLAIMER OF WARRANTY: SAMPLE CODE IS INTENDED FOR EXAMPLE PURPOSES 59 | ONLY AND IS PROVIDED BY CISCO "AS IS" WITH ALL FAULTS AND WITHOUT 60 | WARRANTY OR SUPPORT OF ANY KIND. TO THE MAXIMUM EXTENT PERMITTED BY 61 | LAW, ALL EXPRESS AND IMPLIED CONDITIONS, REPRESENTATIONS, AND 62 | WARRANTIES INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTY OR 63 | CONDITION OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON- 64 | INFRINGEMENT, SATISFACTORY QUALITY, NON-INTERFERENCE, AND ACCURACY, 65 | ARE HEREBY EXCLUDED AND EXPRESSLY DISCLAIMED BY CISCO. CISCO DOES NOT 66 | WARRANT THAT THE SAMPLE CODE IS SUITABLE FOR PRODUCTION OR COMMERCIAL 67 | USE, WILL OPERATE PROPERLY, IS ACCURATE OR COMPLETE, OR IS WITHOUT 68 | ERROR OR DEFECT. 69 | 70 | 6. GENERAL: This License shall be governed by and interpreted in 71 | accordance with the laws of the State of California, excluding its 72 | conflict of laws provisions. You agree to comply with all applicable 73 | United States export laws, rules, and regulations. If any provision of 74 | this License is judged illegal, invalid, or otherwise unenforceable, 75 | that provision shall be severed and the rest of the License shall 76 | remain in full force and effect. No failure by Cisco to enforce any of 77 | its rights related to the Sample Code or to a breach of this License 78 | in a particular situation will act as a waiver of such rights. In the 79 | event of any inconsistencies with any other terms, this License shall 80 | take precedence. 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DNAC Path Trace 2 | 3 | Path trace traces a path through the network from host A to host B. This ability to assure that the correct network services have been put in place. Starting with complete end to end visibility, across each segment or hop over the network and the interactions/connections between them. You can also provide TCP/UDP port information and the controller will look at points in the network where there is multiple paths and let you know which of the possible paths will be used for this flow. In addition you can get device and interface statistics along the active path. 4 | 5 | By automating this duty with an assurance solution that provides comprehensive visibility into the network devices and state, and the context to comprehend how to further automatically configure all the devices in the network path to achieve the required performance Intent-based networking provides a continuous loop framework to deliver ongoing protection and alignment to security policy and compliance requirements. The large scale of IoT (Internet of Things) and cloud need such artificial intelligence and machine learning to help analyse the large quantities of data generated from the network and to recommend the appropriate action. 6 | 7 | With the DNA Center Platform APIs, you can put path trace to work wherever, whenever you want. 8 | 9 | ## Getting started 10 | 11 | These instructions will get you a copy of the Python code for DNAC path trace up and running on your local machine for development and testing purposes. 12 | 13 | ## Requirements 14 | 15 | - Python 2.7.10 or higher 16 | - Python 3.6.3 or higher 17 | - "git" command line tools 18 | - Homebrew (Mac OS X) 19 | 20 | ## Mac OS X Installation 21 | 22 | ``` 23 | git installation - https://git-scm.com/download/mac 24 | ``` 25 | ``` 26 | homebrew installation - ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)" 27 | ``` 28 | ``` 29 | Python 3.6 installation - https://www.python.org/downloads/release/python-364/ 30 | Python 2.7 installation - https://www.python.org/downloads/release/python-2714/ 31 | Python pip installation 32 | curl -o get-pip.py https://bootstrap.pypa.io/get-pip.py 33 | sudo python get-pip.py 34 | ``` 35 | Command Line Developer Tools Installation. After running command, complete installation using the GUI. 36 | ``` 37 | xcode-select --install 38 | ``` 39 | 40 | ## Windows Installation 41 | ``` 42 | git installation - https://git-scm.com/download/win 43 | Python 3.6 installation - https://www.python.org/downloads/release/python-364/ 44 | Python 2.7 installation - https://www.python.org/downloads/release/python-2714/ 45 | Be sure to check box for "Add Python to PATH" during the installer 46 | ``` 47 | 48 | ## 'GIT' this code 49 | 50 | All of the code and examples for this lesson is located in the 'add me here' directory. Clone and access it with the following commands: 51 | 52 | ``` 53 | git clone https://github.com/CiscoDevNet/dnac-python-path-trace 54 | cd dnac-python-path-trace 55 | ``` 56 | Use pip to install the necessary requirements 57 | ``` 58 | pip install -r requirements.txt 59 | ``` 60 | 61 | ### Code features 62 | 63 | - An easy-to-use single Python script for monitoring network health, identifying issue root causes, and helping to remediating issues. 64 | - Troubleshoots issues along the network path. Checking network state, performance levels, and security status, providing context based on locations and devices and realigning resources in order to meet service levels or compliance regulations. 65 | - Provides contextual data analysis, important before, during, and after network changes or deployments to help ensure that the network is delivering the desired results throughout and after the process is complete. 66 | - The machine learning is important in order to create a baseline of performance and security, rapidly identifying any deviation from normal. 67 | 68 | 69 | ## DNAC API reference 70 | 71 | If you look at the Python code for our script, you will see the API calls used. 72 | 73 | - `https://{}/api/system/v1/auth/token` Gets and encapsulates user identity and role information as a single value that RBAC-governed APIs use to make access-control decisions. 74 | - `https://{}/api/v1/network-device` Gets the list of first 500 network devices sorted lexicographically based on host name. It can be filtered using management IP address, mac address, hostname and location name. 75 | - `https://{}/api/v1/interface` Gets every interface on every network device. Whilst you can get a list of all interfaces via an API call, it is often more useful to get a subset of them. For example those that are attached to a specific network-device. 76 | - `https://{}/api/v1//host` You can use the host API to get the name of a host, the ID of the VLAN that the host uses, the IP address of the host, the MAC address of the host, the IP address of the network device to which host is connected, and more. 77 | - `https://{}/api/v1/flow-analysis` The path trace endpoint API to trace a path between two IP addresses. The function will wait for analysis to complete, and return the results 78 | 79 | ## Running path trace Python scripts 80 | 81 | The script `path_trace.py` requires two arguments to see how this works run the `path_trace_prep.py` script. In this script we use `Argparse` is a built in Python module which makes easy to write user-friendly command-line interfaces. The program defines what arguments it requires. Argparse will figure out how to parse those out of sys.argv. We will use Argparse to input our host source and destination IP addresses. 82 | 83 | ``` 84 | python path_trace.py -h 85 | usage: path_trace.py [-h] source_ip destination_ip 86 | 87 | positional arguments: 88 | source_ip Source IP Address 89 | destination_ip Destination IP Address 90 | 91 | optional arguments: 92 | -h, --help show this help message and exit 93 | ``` 94 | 95 | The source and destination IP address of the hosts. The Source and destination can also be addresses of interfaces on network-devices as well. 96 | 97 | - Source: Enter the IP address from which you want the trace to start 98 | - Destination: Enter the IP address, hostname, username, or application name at which you want the trace to end 99 | 100 | :warning: Path traces from the a router's loopback interface or a wireless controller's management interface are not supported 101 | 102 | ## Example Path trace script 103 | 104 | ``` 105 | python path_trace.py 10.10.22.98 10.10.22.114 106 | Running Troubleshooting Script for 107 | Source IP: 10.10.22.98 108 | Destination IP: 10.10.22.114 109 | 110 | Source Host Details: 111 | ------------------------- 112 | Host Name: Unavailable 113 | Network Type: wired 114 | Connected Network Device: 10.10.22.66 115 | Connected Interface Name: TenGigabitEthernet1/0/1 116 | VLAN: 1 117 | Host IP: 10.10.22.98 118 | Host MAC: c8:4c:75:68:b2:c0 119 | Host Sub Type: UNKNOWN 120 | 121 | Destination Host Details: 122 | ------------------------- 123 | Host Name: Unavailable 124 | Network Type: wired 125 | Connected Network Device: 10.10.22.70 126 | Connected Interface Name: TenGigabitEthernet1/0/24 127 | VLAN: 1 128 | Host IP: 10.10.22.114 129 | Host MAC: 00:1e:13:a5:b9:40 130 | Host Sub Type: UNKNOWN 131 | 132 | Source Host Network Connection Details: 133 | --------------------------------------------- 134 | Device Hostname: cat_9k_1.abc.inc 135 | Management IP: 10.10.22.66 136 | Device Location: None 137 | Device Type: Cisco Catalyst 9300 Switch 138 | Platform Id: C9300-24UX 139 | Device Role: ACCESS 140 | Serial Number: FCW2136L0AK 141 | Software Version: 16.6.1 142 | Up Time: 144 days, 21:42:05.16 143 | Reachability Status: Reachable 144 | Error Code: None 145 | Error Description: None 146 | 147 | Attached Interface: 148 | -------------------- 149 | Port Name: TenGigabitEthernet1/0/1 150 | Interface Type: Physical 151 | Admin Status: UP 152 | Operational Status: up 153 | Media Type: 100/1000/2.5G/5G/10GBaseTX 154 | Speed: 1000000 155 | Duplex Setting: FullDuplex 156 | Port Mode: access 157 | Interface VLAN: 1 158 | Voice VLAN: None 159 | 160 | Destination Host Network Connection Details: 161 | --------------------------------------------- 162 | Device Hostname: cat_9k_2.abc.inc 163 | Management IP: 10.10.22.70 164 | Device Location: None 165 | Device Type: Cisco Catalyst 9300 Switch 166 | Platform Id: C9300-24UX 167 | Device Role: ACCESS 168 | Serial Number: FCW2140L039 169 | Software Version: 16.6.1 170 | Up Time: 144 days, 21:39:55.40 171 | Reachability Status: Reachable 172 | Error Code: None 173 | Error Description: None 174 | 175 | Attached Interface: 176 | -------------------- 177 | Port Name: TenGigabitEthernet1/0/24 178 | Interface Type: Physical 179 | Admin Status: UP 180 | Operational Status: up 181 | Media Type: 100/1000/2.5G/5G/10GBaseTX 182 | Speed: 1000000 183 | Duplex Setting: FullDuplex 184 | Port Mode: access 185 | Interface VLAN: 1 186 | Voice VLAN: None 187 | 188 | Running Flow Analysis from 10.10.22.98 to 10.10.22.114 189 | ------------------------------------------------------- 190 | Flow analysis not complete yet, waiting 5 seconds 191 | Number of Hops from Source to Destination: 5 192 | () 193 | Flow Details: 194 | **************************************** 195 | Hop 1: Network Device cat_9k_1.abc.inc 196 | Device IP: 10.10.22.66 197 | Device Role: ACCESS 198 | () 199 | Ingress Interface 200 | -------------------- 201 | Port Name: TenGigabitEthernet1/0/1 202 | Interface Type: Physical 203 | Admin Status: UP 204 | Operational Status: up 205 | Media Type: 100/1000/2.5G/5G/10GBaseTX 206 | Speed: 1000000 207 | Duplex Setting: FullDuplex 208 | Port Mode: access 209 | Interface VLAN: 1 210 | Voice VLAN: None 211 | 212 | Egress Interface 213 | -------------------- 214 | Port Name: TenGigabitEthernet1/1/1 215 | Interface Type: Physical 216 | Admin Status: UP 217 | Operational Status: up 218 | Media Type: unknown 219 | Speed: 10000000 220 | Duplex Setting: FullDuplex 221 | Port Mode: routed 222 | Interface VLAN: None 223 | Voice VLAN: None 224 | 225 | **************************************** 226 | Hop 2: Network Device cs3850.abc.inc 227 | Device IP: 10.10.22.69 228 | Device Role: DISTRIBUTION 229 | () 230 | Ingress Interface 231 | -------------------- 232 | Port Name: TenGigabitEthernet1/1/2 233 | Interface Type: Physical 234 | Admin Status: UP 235 | Operational Status: up 236 | Media Type: SFP-10GBase-CX1 237 | Speed: 10000000 238 | Duplex Setting: FullDuplex 239 | Port Mode: routed 240 | Interface VLAN: None 241 | Voice VLAN: None 242 | 243 | Egress Interface 244 | -------------------- 245 | Port Name: TenGigabitEthernet1/1/3 246 | Interface Type: Physical 247 | Admin Status: UP 248 | Operational Status: up 249 | Media Type: SFP-10GBase-CX1 250 | Speed: 10000000 251 | Duplex Setting: FullDuplex 252 | Port Mode: routed 253 | Interface VLAN: None 254 | Voice VLAN: None 255 | 256 | **************************************** 257 | Hop 3: Network Device cat_9k_2.abc.inc 258 | Device IP: 10.10.22.70 259 | Device Role: ACCESS 260 | () 261 | Ingress Interface 262 | -------------------- 263 | Port Name: TenGigabitEthernet1/1/1 264 | Interface Type: Physical 265 | Admin Status: UP 266 | Operational Status: up 267 | Media Type: unknown 268 | Speed: 10000000 269 | Duplex Setting: FullDuplex 270 | Port Mode: routed 271 | Interface VLAN: None 272 | Voice VLAN: None 273 | 274 | Egress Interface 275 | -------------------- 276 | Port Name: TenGigabitEthernet1/0/24 277 | Interface Type: Physical 278 | Admin Status: UP 279 | Operational Status: up 280 | Media Type: 100/1000/2.5G/5G/10GBaseTX 281 | Speed: 1000000 282 | Duplex Setting: FullDuplex 283 | Port Mode: access 284 | Interface VLAN: 1 285 | Voice VLAN: None 286 | ``` 287 | 288 | ## Acknowledgements 289 | 290 | - Hank Preston - :e-mail: hapresto@cisco.com 291 | - Adam Radford - :e-mail: aradford@cisco.com 292 | -------------------------------------------------------------------------------- /path_trace.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from env_lab import apicem 4 | from time import sleep 5 | import json 6 | import requests 7 | import sys 8 | import urllib3 9 | from requests.auth import HTTPBasicAuth 10 | 11 | # Silence the insecure warning due to SSL Certificate 12 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 13 | 14 | headers = { 15 | 'content-type': "application/json", 16 | 'x-auth-token': "" 17 | } 18 | 19 | 20 | def apic_login(host, username, password): 21 | """ 22 | Use the REST API to Log into an DNA_CENTER and retrieve token 23 | """ 24 | url = "https://{}/api/system/v1/auth/token".format(host) 25 | # payload = {"username": username, "password": password} 26 | 27 | # Make Login request and return the response body 28 | response = requests.request("POST", url, auth=HTTPBasicAuth(username, password), 29 | headers=headers, verify=False) 30 | # print response 31 | return response.json()["Token"] 32 | 33 | 34 | def host_list(apic, ticket, ip=None, mac=None, name=None): 35 | """ 36 | Use the REST API to retrieve the list of hosts. 37 | Optional parameters to filter by: 38 | IP address 39 | MAC address 40 | Hostname 41 | """ 42 | url = "https://{}/api/v1/host".format(apic) 43 | headers["x-auth-token"] = ticket 44 | filters = [] 45 | 46 | # Add filters if provided 47 | if ip: 48 | filters.append("hostIp={}".format(ip)) 49 | if mac: 50 | filters.append("hostMac={}".format(mac)) 51 | if name: 52 | filters.append("hostName={}".format(name)) 53 | if len(filters) > 0: 54 | url += "?" + "&".join(filters) 55 | 56 | # Make API request and return the response body 57 | response = requests.request("GET", url, headers=headers, verify=False) 58 | return response.json()["response"] 59 | 60 | 61 | def verify_single_host(host, ip): 62 | """ 63 | Simple function to verify only a single host returned from query. 64 | If no hosts, or multiple hosts are returned, an error message is printed 65 | and the program exits. 66 | """ 67 | if len(host) == 0: 68 | print("Error: No host with IP address {} was found".format(ip)) 69 | sys.exit(1) 70 | if len(host) > 1: 71 | print("Error: Multiple hosts with IP address {} were found".format(ip)) 72 | print(json.dumps(host, indent=2)) 73 | sys.exit(1) 74 | 75 | 76 | def print_host_details(host): 77 | """ 78 | Print to screen interesting details about a given host. 79 | Input Paramters are: 80 | host_desc: string to describe this host. Example "Source" 81 | host: dictionary object of a host returned from APIC-EM 82 | Standard Output Details: 83 | Host Name (hostName) - If available 84 | Host IP (hostIp) 85 | Host MAC (hostMac) 86 | Network Type (hostType) - wired/wireless 87 | Host Sub Type (subType) 88 | VLAN (vlanId) 89 | Connected Network Device (connectedNetworkDeviceIpAddress) 90 | 91 | Wired Host Details: 92 | Connected Interface Name (connectedInterfaceName) 93 | 94 | Wireless Host Details: 95 | Connected AP Name (connectedAPName) 96 | """ 97 | # If optional host details missing, add as "Unavailable" 98 | if "hostName" not in host.keys(): 99 | host["hostName"] = "Unavailable" 100 | 101 | # Print Standard Details 102 | print("Host Name: {}".format(host["hostName"])) 103 | print("Network Type: {}".format(host["hostType"])) 104 | print("Connected Network Device: {}".format(host["connectedNetworkDeviceIpAddress"])) # noqa: E501 105 | 106 | # Print Wired/Wireless Details 107 | if host["hostType"] == "wired": 108 | print("Connected Interface Name: {}".format(host["connectedInterfaceName"])) # noqa: E501 109 | if host["hostType"] == "wireless": 110 | print("Connected AP Name: {}".format(host["connectedAPName"])) 111 | 112 | # Print More Standard Details 113 | print("VLAN: {}".format(host["vlanId"])) 114 | print("Host IP: {}".format(host["hostIp"])) 115 | print("Host MAC: {}".format(host["hostMac"])) 116 | print("Host Sub Type: {}".format(host["subType"])) 117 | 118 | # Blank line at the end 119 | print("") 120 | 121 | 122 | def network_device_list(apic, ticket, id=None): 123 | """ 124 | Use the REST API to retrieve the list of network devices. 125 | If a device id is provided, return only that device 126 | """ 127 | url = "https://{}/api/v1/network-device".format(apic) 128 | headers["x-auth-token"] = ticket 129 | 130 | # Change URL to single device given an id 131 | if id: 132 | url += "/{}".format(id) 133 | 134 | # Make API request and return the response body 135 | response = requests.request("GET", url, headers=headers, verify=False) 136 | 137 | # Always return a list object, even if single device for consistency 138 | if id: 139 | return [response.json()["response"]] 140 | 141 | return response.json()["response"] 142 | 143 | 144 | def interface_details(apic, ticket, id): 145 | """ 146 | Use the REST API to retrieve details about an interface based on id. 147 | """ 148 | url = "https://{}/api/v1/interface/{}".format(apic, id) 149 | headers["x-auth-token"] = ticket 150 | 151 | response = requests.request("GET", url, headers=headers, verify=False) 152 | return response.json()["response"] 153 | 154 | 155 | def print_network_device_details(network_device): 156 | """ 157 | Print to screen interesting details about a network device. 158 | Input Paramters are: 159 | network_device: dict object of a network device returned from APIC-EM 160 | Standard Output Details: 161 | Device Hostname (hostname) 162 | Management IP (managementIpAddress) 163 | Device Location (locationName) 164 | Device Type (type) 165 | Platform Id (platformId) 166 | Device Role (role) 167 | Serial Number (serialNumber) 168 | Software Version (softwareVersion) 169 | Up Time (upTime) 170 | Reachability Status (reachabilityStatus) 171 | Error Code (errorCode) 172 | Error Description (errorDescription) 173 | """ 174 | 175 | # Print Standard Details 176 | print("Device Hostname: {}".format(network_device["hostname"])) 177 | print("Management IP: {}".format(network_device["managementIpAddress"])) 178 | print("Device Location: {}".format(network_device["locationName"])) 179 | print("Device Type: {}".format(network_device["type"])) 180 | print("Platform Id: {}".format(network_device["platformId"])) 181 | print("Device Role: {}".format(network_device["role"])) 182 | print("Serial Number: {}".format(network_device["serialNumber"])) 183 | print("Software Version: {}".format(network_device["softwareVersion"])) 184 | print("Up Time: {}".format(network_device["upTime"])) 185 | print("Reachability Status: {}".format(network_device["reachabilityStatus"])) # noqa: E501 186 | print("Error Code: {}".format(network_device["errorCode"])) 187 | print("Error Description: {}".format(network_device["errorDescription"])) 188 | 189 | # Blank line at the end 190 | print("") 191 | 192 | 193 | def print_interface_details(interface): 194 | """ 195 | Print to screen interesting details about an interface. 196 | Input Paramters are: 197 | interface: dictionary object of an interface returned from APIC-EM 198 | Standard Output Details: 199 | Port Name - (portName) 200 | Interface Type (interfaceType) - Physical/Virtual 201 | Admin Status - (adminStatus) 202 | Operational Status (status) 203 | Media Type - (mediaType) 204 | Speed - (speed) 205 | Duplex Setting (duplex) 206 | Port Mode (portMode) - access/trunk/routed 207 | Interface VLAN - (vlanId) 208 | Voice VLAN - (voiceVlan) 209 | """ 210 | 211 | # Print Standard Details 212 | print("Port Name: {}".format(interface["portName"])) 213 | print("Interface Type: {}".format(interface["interfaceType"])) 214 | print("Admin Status: {}".format(interface["adminStatus"])) 215 | print("Operational Status: {}".format(interface["status"])) 216 | print("Media Type: {}".format(interface["mediaType"])) 217 | print("Speed: {}".format(interface["speed"])) 218 | print("Duplex Setting: {}".format(interface["duplex"])) 219 | print("Port Mode: {}".format(interface["portMode"])) 220 | print("Interface VLAN: {}".format(interface["vlanId"])) 221 | print("Voice VLAN: {}".format(interface["voiceVlan"])) 222 | 223 | # Blank line at the end 224 | print("") 225 | 226 | 227 | def run_flow_analysis(apic, ticket, source_ip, destination_ip): 228 | """ 229 | Use the REST API to initiate a Flow Analysis (Path Trace) from a given 230 | source_ip to destination_ip. Function will wait for analysis to complete, 231 | and return the results. 232 | """ 233 | base_url = "https://{}/api/v1/flow-analysis".format(apic) 234 | headers["x-auth-token"] = ticket 235 | 236 | # initiate flow analysis 237 | body = {"destIP": destination_ip, "sourceIP": source_ip} 238 | initiate_response = requests.post(base_url, headers=headers, verify=False, 239 | json=body) 240 | 241 | # Verify successfully initiated. If not error and exit 242 | if initiate_response.status_code != 202: 243 | print("Error: Flow Analysis Initiation Failed") 244 | print(initiate_response.text) 245 | sys.exit(1) 246 | 247 | # Check status of analysis and wait until completed 248 | flowAnalysisId = initiate_response.json()["response"]["flowAnalysisId"] 249 | detail_url = base_url + "/{}".format(flowAnalysisId) 250 | detail_response = requests.get(detail_url, headers=headers, verify=False) 251 | while not detail_response.json()["response"]["request"]["status"] == "COMPLETED": # noqa: E501 252 | print("Flow analysis not complete yet, waiting 5 seconds") 253 | sleep(5) 254 | detail_response = requests.get(detail_url, headers=headers, 255 | verify=False) 256 | 257 | # Return the flow analysis details 258 | return detail_response.json()["response"] 259 | 260 | 261 | def print_flow_analysis_details(flow_analysis): 262 | """ 263 | Print to screen interesting details about the flow analysis. 264 | Input Parameters are: 265 | flow_analysis: dictionary object of a flow analysis returned from APIC-EM 266 | """ 267 | hops = flow_analysis["networkElementsInfo"] 268 | 269 | print("Number of Hops from Source to Destination: {}".format(len(hops))) 270 | print() 271 | 272 | # Print Details per hop 273 | print("Flow Details: ") 274 | # Hop 1 (index 0) and the last hop (index len - 1) represent the endpoints 275 | for i, hop in enumerate(hops): 276 | if i == 0 or i == len(hops) - 1: 277 | continue 278 | 279 | print("*" * 40) 280 | print("Hop {}: Network Device {}".format(i, hop["name"])) 281 | # If the hop is "UNKNOWN" continue along 282 | if hop["name"] == "UNKNOWN": 283 | print() 284 | continue 285 | 286 | print("Device IP: {}".format(hop["ip"])) 287 | print("Device Role: {}".format(hop["role"])) 288 | 289 | # If type is an Access Point, skip interface details 290 | if hop["type"] == "Unified AP": 291 | continue 292 | print() 293 | 294 | # Step 4: Are there any problems along the path? 295 | # Print details about each interface 296 | print("Ingress Interface") 297 | print("-" * 20) 298 | ingress = interface_details(apicem["host"], login, 299 | hop["ingressInterface"]["physicalInterface"]["id"]) # noqa: E501 300 | print_interface_details(ingress) 301 | print("Egress Interface") 302 | print("-" * 20) 303 | egress = interface_details(apicem["host"], login, 304 | hop["egressInterface"]["physicalInterface"]["id"]) # noqa: E501 305 | print_interface_details(egress) 306 | 307 | # Print blank line at end 308 | print("") 309 | 310 | # Entry point for program 311 | if __name__ == '__main__': 312 | # Setup Arg Parse for Command Line parameters 313 | import argparse 314 | parser = argparse.ArgumentParser() 315 | 316 | # Command Line Parameters for Source and Destination IP 317 | parser.add_argument("source_ip", help = "Source IP Address") 318 | parser.add_argument("destination_ip", help = "Destination IP Address") 319 | args = parser.parse_args() 320 | 321 | # Get Source and Destination IPs from Command Line 322 | source_ip = args.source_ip 323 | destination_ip = args.destination_ip 324 | 325 | # Print Starting message 326 | print("Running Troubleshooting Script for ") 327 | print(" Source IP: {} ".format(source_ip)) 328 | print(" Destination IP: {}".format(destination_ip)) 329 | print("") 330 | 331 | # Log into the APIC-EM Controller to get Ticket 332 | login = apic_login(apicem["host"], apicem["username"], apicem["password"]) 333 | 334 | # Step 1: Identify involved hosts 335 | # Retrieve Host Details from APIC-EM 336 | source_host = host_list(apicem["host"], login, 337 | ip=source_ip) 338 | destination_host = host_list(apicem["host"], login, 339 | ip=destination_ip) 340 | 341 | # Verify single host found for each IP 342 | verify_single_host(source_host, source_ip) 343 | verify_single_host(destination_host, destination_ip) 344 | 345 | # Print Out Host details 346 | print("Source Host Details:") 347 | print("-" * 25) 348 | print_host_details(source_host[0]) 349 | 350 | print("Destination Host Details:") 351 | print("-" * 25) 352 | print_host_details(destination_host[0]) 353 | 354 | # Step 2: Where are they in the network? 355 | # Retrieve and Print Source Device Details from APIC-EM 356 | source_host_net_device = network_device_list(apicem["host"], 357 | login, 358 | id=source_host[0]["connectedNetworkDeviceId"]) # noqa: E501 359 | print("Source Host Network Connection Details:") 360 | print("-" * 45) 361 | print_network_device_details(source_host_net_device[0]) 362 | # If Host is wired, collect interface details 363 | if source_host[0]["hostType"] == "wired": 364 | source_host_interface = interface_details(apicem["host"], 365 | login, 366 | id=source_host[0]["connectedInterfaceId"]) # noqa: E501 367 | print("Attached Interface:") 368 | print("-" * 20) 369 | print_interface_details(source_host_interface) 370 | 371 | destination_host_net_device = network_device_list(apicem["host"], 372 | login, 373 | id=destination_host[0]["connectedNetworkDeviceId"]) # noqa: E501 374 | print("Destination Host Network Connection Details:") 375 | print("-" * 45) 376 | print_network_device_details(destination_host_net_device[0]) 377 | # If Host is wired, collect interface details 378 | if destination_host[0]["hostType"] == "wired": 379 | destination_host_interface = interface_details(apicem["host"], 380 | login, 381 | id=destination_host[0]["connectedInterfaceId"]) # noqa: E501 382 | print("Attached Interface:") 383 | print("-" * 20) 384 | print_interface_details(destination_host_interface) 385 | 386 | # Step 3: What path does the traffic take? 387 | # Step 4: Are there any problems along the path? 388 | # Run a Flow Analysis for Source/Destionation 389 | print("Running Flow Analysis from {} to {}".format(source_ip, destination_ip)) # noqa: E501 390 | print("-" * 55) 391 | flow_analysis = run_flow_analysis(apicem["host"], login, 392 | source_ip, 393 | destination_ip) 394 | 395 | # Print Out Details 396 | print_flow_analysis_details(flow_analysis) 397 | --------------------------------------------------------------------------------