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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
#!/usr/bin/env python3
import json
import ipaddress
import subprocess
def vtysh(cmd):
print(f"@: vtysh -c {cmd}")
return subprocess.run([
"vtysh",
"-c",
cmd
], capture_output=True).stdout
def get_frr_vrfs():
"""
gets the list of VNIs declared in the evpn config
"""
print("I: Listing evpn VNIs")
show_evpn_vni = vtysh("sh evpn vni json")
return json.loads(show_evpn_vni)
def get_frr_arp_cache(vni):
"""
gets the list of atp-cache entries in the given VNI
"""
print(f"I: Listing evpn arp-cache for VNI {vni}")
show_evpn_arp_cache = vtysh(f"sh evpn arp-cache vni {vni} json")
return json.loads(show_evpn_arp_cache)
def get_current_routes():
"""
gets the list of routes currently applied using frr-evpn-route-watcher
"""
command = [
"ip",
"route",
"show",
"proto",
"frr-evpn-route-watcher"
]
process4 = subprocess.run(command, capture_output=True)
process6 = subprocess.run(command + ["-6"], capture_output=True)
routes = process4.stdout.splitlines() + process6.stdout.splitlines()
out = {}
for route in routes:
# <ip> dev <vrf> scope link
parts = route.split(" ")
if len(parts) == 5:
valid = parts[1] == "dev" \
and parts[3] == "scope" \
and parts[4] == "link"
if valid:
ipraw = parts[0]
vrf = parts[2]
ip = ipaddress.ip_address(ipraw)
out[ip] = vrf
print(f"D: Found existing route for {ip}")
return out
def add_route_vrf(ip, vrf):
"""
Add a route using the ip route command that points to a vrf interface
"""
print(f"I: Adding route for {ip} -> {vrf}")
subprocess.run([
"ip",
"route",
"add",
str(ip),
"dev",
vrf,
"proto",
"frr-evpn-route-watcher"
])
def remove_route_vrf(ip):
"""removes a route to a given ip"""
print(f"I: Removing route to {ip}")
subprocess.run(["ip", "route", "del", str(ip)])
def main():
print("frr-evpn-route-watcher by Matthieu P. <m@mpgn.dev>")
# Get all the L2 VRFs
vrfs = [
vrf
for vrf in get_frr_vrfs()
if vrf['type'] == "L2"
]
# get all the current routes
to_remove = get_current_routes()
for vrf in vrfs:
vrfName = vrf['tenantVrf']
vni = vrf['vni']
arp_cache = get_frr_arp_cache(vni)
for entry in arp_cache.keys():
local = type({}) == type(arp_cache[entry]) and \
arp_cache[entry]['type'] == 'local'
if local:
ip = ipaddress.ip_address(entry)
present = ip in to_remove
# if the route already exists
if present:
# if the route points to the correct VRF interface
valid = to_remove[ip] == vrfName
# if it's not, we delete the old route and
# create a new correct one.
if not valid:
remove_route_vrf(ip)
add_route_vrf(ip, vrfName)
else:
# if the route doesn't exist, we simply create it
add_route_vrf(ip, vrfName)
# since the route muse say we remove it from the to_remove list
to_remove.pop(ip)
for remove in to_remove.keys():
remove_route_vrf(remove)
if __name__ == "__main__":
main()
|