1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
#!/usr/bin/env python3
import json
import ipaddress
import subprocess
import re
def get_frr_vrfs():
"""Gets the list of vrf VNIs configured in FRR"""
process = subprocess.run(["vtysh", "-c", "show vrf vni json"], capture_output=True)
return json.loads(process.stdout)["vrfs"]
def get_frr_evpn_info():
"""Lists all the routes learned via evpn"""
process = subprocess.run(
["vtysh", "-c", "show ip bgp l2vpn evpn json"], capture_output=True
)
return json.loads(process.stdout)
def add_route_vrf(ip, vrf):
"""Add a route using the ip route command"""
subprocess.run(
["ip", "route", "add", str(ip), "dev", vrf, "metric", "0"],
)
def remove_route_vrf(ip):
subprocess.run(["ip", "route", "del", str(ip)])
def currently_routed(vrfs_names):
process = subprocess.run(["ip", "route"], capture_output=True, text=True)
routed_ips = []
for route in process.stdout.splitlines():
parts = route.split(" ")
if len(parts) == 6:
addr, dev, vrf, scope, link = (
parts[0],
parts[1],
parts[2],
parts[3],
parts[4],
)
addr = parse_ip(addr)
if (
addr != None
and dev == "dev"
and vrf in vrfs_names
and scope == "scope"
and link == "link"
):
routed_ips.append(addr)
return routed_ips
def parse_ip(str):
try:
return ipaddress.ip_network(str)
except ValueError:
return False
def get_vrfs():
return {str(vrf["vni"]): vrf["vrf"] for vrf in get_frr_vrfs()}
def resolve_routes():
json = get_frr_evpn_info()
vrfs = get_vrfs()
# The local router-id to search for IPs in the evpn routes information
localRouterId = json["bgpLocalRouterId"]
localAS = json["localAS"]
currentlyRouted = currently_routed([vrfs[vrf] for vrf in vrfs])
for jsonKey in json:
rd = jsonKey.split(":")
if len(rd) == 2:
peer, _ = rd
if parse_ip(peer) and peer == localRouterId:
rdObject = json[jsonKey]
for route in rdObject:
matches = re.findall(r"(?:\[([^\]]*)\]:?)", route)
if len(matches) == 6:
route_type = matches[0]
if route_type == "2":
ip = parse_ip(matches[5])
if ip != None and not ip.is_link_local:
extendedCommunities = rdObject[route]["paths"][0][
"extendedCommunity"
]["string"]
rts = re.finditer(
f"RT:{localAS}:(\d+)", extendedCommunities
)
# For all the matches we have in the extentendCommunity value
for rt in rts:
vni = rt.group(1)
if vni in vrfs:
vrf = vrfs[vni]
if ip in currentlyRouted:
currentlyRouted.remove(ip)
break
print(
f"wanting to add route {ip} to {vrf} ({vni}) vrf"
)
add_route_vrf(ip, vrf)
break
for remove in currentlyRouted:
print(f"removing route for {remove}")
remove_route_vrf(remove)
resolve_routes()
|