[DAY#10 PyATS Series] Parsing and Normalizing ARP Tables (Multi-Vendor) using pyATS (Vendor-Agnostic) [Python for Network Engineer]
Table of Contents
Introduction: Why ARP Table Normalization Matters
Welcome to Day 10 of the 101 Days of pyATS (Vendor‑Agnostic) series!
Today, we’re focusing on a critical under-the-hood element of L2‑L3 connectivity: parsing and normalizing ARP tables across Cisco, Arista, Palo Alto, and FortiGate devices using pyATS and Genie. For any Python for Network Engineer, being able to extract, standardize, and validate ARP entries is key to:
- Detecting duplicate IPs or stale entries
- Automating network inventory and host audits
- Troubleshooting connectivity issues with precision
- Ensuring programmatic consistency across vendors
By the end of today’s tutorial, you’ll have a reusable, vendor-agnostic script that:
- Connects to diverse devices
- Collects ARP tables via structured output
- Normalizes the data (IP ↔ MAC ↔ Interface)
- Runs ping tests to verify host reachability
Ready to level up your network automation? Let’s dive in!
Topology Overview
Here’s our lab layout for Day 10:

Connectivity:
- Management VM (pyATS) can SSH into all devices
- Devices share a common L2 segment with a host (
Host1 192.168.50.10
) - We will extract ARP entries from each device to look for that host’s MAC/IP
Why Vendor‑Agnostic Testing Matters
In heterogeneous network environments, inconsistency is a constant risk. Each OS uses slightly different CLI and ARP table formats:
- Cisco IOS-XE:
show ip arp
- Arista EOS:
show ip arp
(similar format) - Palo Alto PAN-OS:
show arp all
- FortiGate FortiOS:
execute arp list
Without a vendor‑agnostic approach, scripts quickly become brittle. pyATS simplifies this by:
- Parsing structured data via Genie parsers
- Normalizing entries into unified Python dictionaries
- Making downstream processing—like ping tests or audits—consistent
The result? A cleaner, deterministic workflow that scales across vendors and use cases.
Topology & Communications
Device | OS | MGMT IP | ARP Command |
---|---|---|---|
Cisco R1 | IOS‑XE | 10.100.0.1 | show ip arp |
Arista R2 | EOS | 10.100.0.2 | show ip arp |
Palo Alto PA1 | PAN‑OS | 10.100.0.3 | show arp all |
FortiGate FG1 | FortiOS | 10.100.0.4 | execute arp list |
Host1 | Linux VM | 192.168.50.10 | — |
Workflow:
- Parse ARP entries across devices
- Normalize to
{"ip":..., "mac":..., "intf":...}
- Ping each IP to verify reachability
- Log results and generate consolidated report
Workflow Script
Below is a simplified arp_normalize.py
script using pyATS and Genie:
from genie.testbed import load import re import json def normalize_arp(parsed_arp, os_type): normalized = [] for entry in parsed_arp: ip = entry.get('address') or entry.get('ip') mac = entry.get('hardware_address') or entry.get('mac') intf = entry.get('interface') or entry.get('ifname') normalized.append({'ip': ip, 'mac': mac, 'interface': intf}) return normalized def main(): testbed = load('testbed.yml') for name, dev in testbed.devices.items(): print(f"\n== {name} ({dev.os}) ==") dev.connect() if dev.os in ['iosxe', 'eos']: parsed = dev.parse('show ip arp')['arp_table'] elif dev.os == 'panos': parsed = dev.parse('show arp all')['arp_table'] elif dev.os == 'fortios': entries = dev.parse('execute arp list')['entries'] parsed = entries.values() else: print("Unsupported OS!") dev.disconnect() continue arp_list = normalize_arp(parsed, dev.os) print("Normalized entries:", json.dumps(arp_list, indent=2)) # Ping each IP for x in arp_list: ip = x['ip'] result = dev.ping(ip, count=2) print(f"Ping {ip}: {'' if result else ''}") dev.disconnect() if __name__ == "__main__": main()
Explanation by Line
Lines | Code Snippet | Purpose |
---|---|---|
1–3 | Imports | Load pyATS Genie parser |
5–11 | normalize_arp() function | Standardizes ARP entries |
13–33 | main() | Script orchestration |
15 | testbed = load(...) | Load YAML testbed |
17–32 | Loop through devices | Extract, normalize, ping |
19–28 | OS-specific parsing logic | Unified list output |
30–31 | dev.ping() test | Validate reachability |
testbed.yml Example
testbed: name: arp-parse-lab devices: cisco_r1: os: iosxe type: router connections: cli: protocol: ssh ip: 10.100.0.1 arista_r2: os: eos type: switch connections: cli: protocol: ssh ip: 10.100.0.2 paloalto_pa1: os: panos type: firewall connections: cli: protocol: ssh ip: 10.100.0.3 fortigate_fg1: os: fortios type: firewall connections: cli: protocol: ssh ip: 10.100.0.4
Multi-Vendor CLI Screenshots
Cisco IOS-XE:
R1# show ip arp Protocol Address Age Hardware Addr Type Interface Internet 192.168.50.10 2 0050.56c0.0008 ARPA Gig1/0/1
Arista EOS:
R2# show ip arp Address Age MAC Interface 192.168.50.10 1 0050.56c0.0008 Ethernet1
Palo Alto PAN‑OS:
> show arp all Address MAC Interface 192.168.50.10 00:50:56:c0:00:08 ethernet1/1
FortiGate FortiOS:
# execute arp list Address Hardware Addr Interface 192.168.50.10 00:50:56:c0:00:08 port1
FAQs
1: Why should I parse and normalize ARP tables using pyATS instead of just reading raw CLI output?
Answer:
Raw CLI outputs are vendor-specific and inconsistent. Each platform (Cisco, Arista, Palo Alto, Fortinet) formats ARP outputs differently, which complicates automation and correlation. Parsing with pyATS transforms this output into a consistent Python dictionary format, allowing you to standardize ARP table data, automate device reachability checks, build correlation maps, and integrate into CI pipelines without manually adjusting for each vendor’s output format.
2: What does “normalizing ARP data” mean in a vendor-agnostic context?
Answer:
Normalization refers to converting diverse outputs from different platforms into a uniform schema. For example, a Cisco device might display MACs in aaaa.bbbb.cccc
format while Arista might use AA:BB:CC:DD:EE:FF
. Using pyATS and Genie parsers, you can normalize:
- MAC address formats
- Interface naming conventions
- VLAN IDs or interface zones
This ensures your ARP tables across all vendors follow the same structure, making it easier to build vendor-agnostic scripts and dashboards.
3: What pyATS command is used to parse the ARP table of a device?
Answer:
The most commonly used method is:
parsed_arp = device.parse('show ip arp')
This uses the built-in Genie parser to convert the ARP CLI output into a Python dictionary. For non-Cisco platforms, you can use:
parsed_arp = device.parse('show arp') # Works on Arista, Palo Alto
In some cases, you might need to create a custom parser for Fortinet or legacy platforms if no built-in parser is available.
4: How can I verify if the parser worked correctly or failed?
Answer:
Use structured error handling around your parser call:
try: arp_table = device.parse('show ip arp') except SchemaEmptyParserError: print("ARP table is empty or not returned correctly.") except Exception as e: print(f"Parsing failed: {str(e)}")
You can also print the dictionary structure and verify keys like 'interfaces'
, 'mac_address'
, 'ip'
, 'age'
, etc., to confirm the data is parsed correctly.
5: How can I use the parsed ARP table data in real-world operations?
Answer:
Parsed and normalized ARP data can be used to:
- Validate L2-L3 neighbor connectivity
- Build IP-to-MAC-to-interface mappings
- Detect IP duplication or spoofing
- Feed into ping test automation (ping each ARP IP)
- Integrate with inventory or IPAM systems
- Run diff against historical ARP data to detect anomalies
6: Is the parsing process the same across Cisco, Arista, Fortinet, and Palo Alto?
Answer:
The parsing logic in your Python script remains the same, but the command syntax and parser availability vary by vendor:
- Cisco:
show ip arp
(well-supported by Genie) - Arista:
show arp
(supported via EOS Genie plugins) - Palo Alto: CLI commands like
show arp all
or XML API (might need custom parser) - Fortinet: ARP table parsing may require REST API or CLI with regular expression pre-processing
So, yes, the methodology is unified, but you may need vendor-specific adjustments to handle edge cases or unsupported features.
7: Can I extend Genie to parse unsupported ARP outputs?
Answer:
Absolutely. pyATS allows you to write your own custom parsers if your device or command is not supported. You can subclass from genie.metaparser.MetaParser
, define your schema using Schema
classes, and implement your own regex logic in the cli()
method. This is particularly useful for Palo Alto and Fortinet platforms where built-in support might be limited.
Once registered, your parser integrates seamlessly with the device.parse()
method—maintaining vendor-agnostic compatibility across your scripts.
YouTube Link
Watch the Complete Python for Network Engineer: Parsing and Normalizing ARP Tables (Multi-Vendor) using pyATS (Vendor-Agnostic) [Python for Network Engineer] Lab Demo & Explanation on our channel:
Join Our Training
Want to master pyATS, Genie, Netmiko, Ansible, APIs, and multi-vendor automation from scratch?
Trainer Sagar Dhawan is conducting a 3-month, instructor-led program designed for Network Engineers who want to transition into automation confidently.
Our curriculum blends:
- Python for Network Engineer
- Real-world use cases (Cisco/Arista/Fortinet/Palo Alto)
- pyATS + Genie Labs
- CI/CD Integration
- APIs and Data Models
View Course Outline → https://course.networkjourney.com/python-ansible-api-cisco-devnet-for-network-engineers/
Enroll Now & Future‑Proof Your Career
Email: info@networkjourney.com
WhatsApp / Call: +91 97395 21088
Seats are limited. Secure yours today and transform your automation journey.