summaryrefslogtreecommitdiff
path: root/frr-evpn-route-watcher/task.py
blob: dfd981e9b2d92af583165ca11ce2063b099cf0ec (plain)
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
import json
import ipaddress
import subprocess
import re
import time

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()