Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
james-scripts/junos-routes.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
159 lines (132 sloc)
5.76 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
from jnpr.junos import Device | |
from jnpr.junos.op.phyport import PhyPortTable | |
from getpass import getpass | |
from lxml import etree | |
from pathlib import Path | |
import json | |
import os | |
import re | |
import logging | |
import sys | |
logger = logging.getLogger("junos-routes") | |
logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s') | |
logger.setLevel(logging.DEBUG) | |
def main(): | |
h = sys.argv[1] # Router management address | |
pw = getpass("Password: ") | |
dev = Device( | |
host=h, | |
user=os.getenv('USER'), | |
passwd=pw | |
) | |
logger.info(f"Connecting to device: {h}") | |
dev.open() | |
# get-route-information | |
logger.debug(f"Facts: {dev.facts!r}") | |
# Get interface information | |
logger.info("Fetching interface information") | |
intf = dev.rpc.get_interface_information(dev_timeout=60) | |
(Path("route-snapshot-debug") / f"{h}-interfaces.xml" ).write_text(etree.tostring(intf, encoding='unicode', pretty_print=True)) | |
# Get all logical interface information | |
intf_by_addr = {} # single-ip => {name: ..., prefix: ...} | |
for logical_intf in intf.findall("./physical-interface/logical-interface"): | |
name = logical_intf.findtext("./name").strip() | |
for afi in logical_intf.findall("./address-family"): | |
family = afi.findtext(".//address-family-name").strip() | |
if family not in ("inet", "inet6"): continue | |
local_addr = afi.findtext(".//ifa-local") # 192.168.0.1 | |
net = afi.findtext(".//ifa-destination") # 192.168.0.0/24, None if loopback | |
if local_addr is None or net is None: | |
continue | |
local_addr = local_addr.strip() | |
net = net.strip() | |
prefix = local_addr + "/" + net.split("/")[1] | |
intf_by_addr[local_addr] = {"name": name, "prefix": prefix} | |
# Get neighbor info | |
neighbors = [] | |
logger.info(f"Fetching neighbors") | |
rpc_nei = dev.rpc.get_bgp_neighbor_information() | |
for nei in rpc_nei.findall('.//bgp-peer'): | |
state = nei.findtext('./peer-state') | |
vrf = nei.findtext('./peer-cfg-rti') | |
local_addr = nei.findtext('./local-address') | |
local_addr = local_addr.split('+')[0] | |
remote_addr = nei.findtext('./peer-address') | |
remote_addr = remote_addr.split('+')[0] | |
local_as = nei.findtext('./local-as') | |
remote_as = nei.findtext('./peer-as') | |
descr = nei.findtext('./description') | |
intf_config = intf_by_addr.get(local_addr, {'name': "UNKNOWN INTF", 'prefix': '0.0.0.0/0'}) | |
if vrf == 'master': | |
vrf = 'RE' | |
nei_info = { | |
'vrf': vrf, | |
'local_addr': local_addr, | |
'local_as': local_as, | |
'remote_addr': remote_addr, | |
'remote_as': remote_as, | |
'description': descr, | |
'intf_prefix': intf_config['prefix'], | |
} | |
# Skip iBGP | |
if local_as == "11537" and remote_as == "11537": | |
continue | |
# Skip things that aren't in a connected state | |
if state != 'Established': | |
continue | |
neighbors.append(nei_info) | |
# print(etree.tostring(reply, encoding='unicode', pretty_print=True)) | |
# routes = dev.rpc.get_route_information(table='inet.0', dev_timeout=60) | |
# Get neighbors | |
logger.info("Looping through neighbors") | |
for nei in neighbors: | |
logger.info(f"Fetching received routes for {nei['vrf']} - {nei['remote_addr']} - {nei['description']}") | |
# Fetch routes | |
rpc_received = dev.rpc.get_route_information( | |
receive_protocol_name="bgp", | |
peer=nei['remote_addr'], # Change to the peer address | |
level="extensive", | |
all="", | |
dev_timeout=60 | |
) | |
# Save to JSON | |
routes = [] | |
for rt in rpc_received.findall('.//route-table/rt'): | |
dest = rt.findtext('./rt-destination') | |
# Skip if it contains both a . and a :, which means it's an l3vpn | |
# this is a super lazy way of filtering out l3vpn routes, and I'm | |
# not sure this is fool proof | |
if re.match(r'.*[.].*[:].*', dest): | |
continue | |
prefix_len = rt.findtext('./rt-prefix-length') | |
prefix = f'{dest}/{prefix_len}' | |
as_path = rt.findtext('.//attr-as-path-effective/attr-value') | |
# aggregator_as = rt.findtext('.//attr-aggregator/aggr-as-number') | |
# aggregator_rid = rt.findtext('.//attr-aggregator/aggr-router-id') | |
communities = [c.text for c in rt.findall('.//communities/community')] | |
med = int(rt.findtext('.//med') or 0) | |
# Cleanup AS-Path | |
as_path = re.sub(r' [IE?]$', '', as_path) | |
# as_path = [int(asn) for asn in as_path.split(" ")[:-1]] | |
# as_path = [asn for asn in as_path.split(" ")[:-1]] | |
route = { | |
'prefix': prefix, | |
'communities': communities, | |
'as-path': as_path, | |
'med': med, | |
} | |
logger.debug(f" got route {route!r}") | |
routes.append(route) | |
base = f"{nei['remote_addr']}-{nei['vrf']}" | |
# Save debug info | |
(Path("route-snapshot-debug") / f'{base}-routes.xml').write_text(etree.tostring(rpc_received, encoding='unicode', pretty_print=True)) | |
# Save BGP neighbor information (neighbor info + routes) | |
peer_data = nei.copy() | |
peer_data['routes'] = routes | |
(Path("route-snapshot") / f'{base}.json').write_text(json.dumps(peer_data, sort_keys=True, indent=2)) | |
# (Path("route-snapshot") / f'{base}-info.json').write_text(json.dumps(nei, sort_keys=True, indent=2)) | |
# (Path("route-snapshot") / f'{base}-routes.json').write_text(json.dumps(routes, sort_keys=True, indent=2)) | |
logger.info("DONE") | |
if __name__ == "__main__": | |
main() |