Last active
September 23, 2026 11:46
-
-
Save kniteli/ad999bf9ad3efdc8d56224132bbd85e3 to your computer and use it in GitHub Desktop.
Script+json data format for generating frr evpn setup with single vxlan device and vlan aware bridge
This file contains hidden or 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/python3 | |
| #todo: turn this into a template to make it easy to consume with ansible | |
| import subprocess | |
| import argparse | |
| import json | |
| import socket | |
| def cmd(command, check=True): | |
| return subprocess.run(command, shell=True, text=True, check=check, stdout=subprocess.PIPE).stdout | |
| def main(local_ip, config_file): | |
| with open(config_file) as f: | |
| parsed = json.loads(f.read()) | |
| interfaces = cmd(f"ip -j link") | |
| parsed_ifs = json.loads(interfaces) | |
| parsed_ifs_names = set([intif["ifname"] for intif in parsed_ifs if "ifname" in intif]) | |
| nodename = socket.gethostname().split('.')[0] | |
| #delete old | |
| cmd(f"ip link del dev {parsed['bridge']['name']}", check=False) | |
| cmd(f"ip link del dev {parsed['bridge']['vxlan_name']}", check=False) | |
| #svi single vxlan | |
| cmd(f"ip link add dev {parsed['bridge']['name']} type bridge stp_state 0 vlan_filtering 1") | |
| cmd(f"ip link set dev {parsed['bridge']['name']} addrgenmode none mtu 9000") | |
| cmd(f"ip link add dev {parsed['bridge']['vxlan_name']} type vxlan dstport 4789 local {local_ip} nolearning external vnifilter") | |
| cmd(f"ip link set dev {parsed['bridge']['vxlan_name']} master {parsed['bridge']['name']} addrgenmode none mtu 8950") | |
| cmd(f"bridge link set dev {parsed['bridge']['vxlan_name']} vlan_tunnel on") | |
| cmd(f"bridge link set dev {parsed['bridge']['vxlan_name']} neigh_suppress on") | |
| cmd(f"bridge link set dev {parsed['bridge']['vxlan_name']} learning off") | |
| cmd(f"bridge vlan del dev {parsed['bridge']['name']} vid 1 self") | |
| cmd(f"bridge vlan del dev {parsed['bridge']['vxlan_name']} vid 1") | |
| cmd(f"ip link set {parsed['bridge']['name']} up") | |
| cmd(f"ip link set {parsed['bridge']['vxlan_name']} up") | |
| for vrf in parsed['vrfs']: | |
| #l3 svi | |
| cmd(f"ip link del {vrf['svi']['name']}", check=False) | |
| cmd(f"ip link del m{vrf['svi']['name']}", check=False) | |
| cmd(f"bridge vlan del dev {parsed['bridge']['name']} vid {vrf['svi']['vlan']} self", check=False) | |
| cmd(f"bridge vlan add dev {parsed['bridge']['name']} vid {vrf['svi']['vlan']} self") | |
| node_hwaddress = vrf['svi']['nodes'][nodename] | |
| cmd(f"ip link add {vrf['svi']['name']} link {parsed['bridge']['name']} address {node_hwaddress} type vlan id {vrf['svi']['vlan']}") | |
| cmd(f"ip link set {vrf['svi']['name']} addrgenmode none master {vrf['name']} mtu 8950") | |
| #create macvlan for shared anycast mac address? | |
| cmd(f"ip link add m{vrf['svi']['name']} link {vrf['svi']['name']} address {vrf['svi']['hwaddr']} type macvlan mode private") | |
| cmd(f"sysctl -w net.ipv4.conf.{vrf['svi']['name']}.arp_accept=2") | |
| cmd(f"sysctl -w net.ipv6.conf.{vrf['svi']['name']}.accept_untracked_na=2") | |
| cmd(f"bridge vni add dev {parsed['bridge']['vxlan_name']} vni {vrf['svi']['vni']}") | |
| cmd(f"bridge vlan add dev {parsed['bridge']['vxlan_name']} vid {vrf['svi']['vlan']}") | |
| cmd(f"bridge vlan add dev {parsed['bridge']['vxlan_name']} vid {vrf['svi']['vlan']} tunnel_info id {vrf['svi']['vni']}") | |
| cmd(f"ip link set {vrf['svi']['name']} up") | |
| cmd(f"ip link set m{vrf['svi']['name']} up") | |
| #l2 vlan to vxlan mappings and bridge ports | |
| for vlan in [vlan for vlan in vrf['vlans'] if (vlan['name'] in parsed_ifs_names) and (nodename in vlan['nodes'])]: | |
| node_ips = vlan['nodes'][nodename] | |
| cmd(f"bridge vlan del dev {parsed['bridge']['name']} vid {vlan['vlan']} self", check=False) | |
| cmd(f"bridge vlan del dev {parsed['bridge']['vxlan_name']} vid {vlan['vlan']}", check=False) | |
| cmd(f"bridge vlan add dev {parsed['bridge']['name']} vid {vlan['vlan']} self") | |
| cmd(f"bridge vlan add dev {parsed['bridge']['vxlan_name']} vid {vlan['vlan']}") | |
| cmd(f"ip link set {vlan['name']} master {parsed['bridge']['name']}") | |
| cmd(f"ip link del vl{vlan['name']}", check=False) | |
| cmd(f"ip link add vl{vlan['name']} link {parsed['bridge']['name']} address {vlan['hwaddr']} type vlan id {vlan['vlan']}") | |
| cmd(f"ip link set vl{vlan['name']} master {vrf['name']} mtu 8950") | |
| cmd(f"sysctl -w net.ipv4.conf.{vlan['name']}.arp_accept=2") | |
| cmd(f"sysctl -w net.ipv6.conf.{vlan['name']}.accept_untracked_na=2") | |
| cmd(f"ip addr replace {node_ips['ipv4']} dev vl{vlan['name']}") | |
| cmd(f"ip addr replace {node_ips['ipv6']} dev vl{vlan['name']}") | |
| cmd(f"ip addr replace {vlan['ipv4']} dev vl{vlan['name']}") | |
| cmd(f"ip addr replace {vlan['ipv6']} dev vl{vlan['name']}") | |
| cmd(f"bridge vlan del dev {vlan['name']} vid 1") | |
| cmd(f"bridge vlan del dev {parsed['bridge']['vxlan_name']} vid {vlan['vlan']}") | |
| cmd(f"bridge vlan del dev {parsed['bridge']['vxlan_name']} vid {vlan['vlan']} tunnel_info id {vlan['vni']}") | |
| cmd(f"bridge vni add dev {parsed['bridge']['vxlan_name']} vni {vlan['vni']}") | |
| cmd(f"bridge vlan add dev {parsed['bridge']['vxlan_name']} vid {vlan['vlan']}") | |
| cmd(f"bridge vlan add dev {parsed['bridge']['vxlan_name']} vid {vlan['vlan']} tunnel_info id {vlan['vni']}") | |
| cmd(f"bridge vlan del dev {vlan['name']} vid {vlan['vlan']} untagged pvid", check=False) | |
| cmd(f"bridge vlan add dev {vlan['name']} vid {vlan['vlan']} untagged pvid") | |
| cmd(f"ip link set vl{vlan['name']} up") | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("local", help="The local ip address") | |
| parser.add_argument("-c", "--config", help="The config file path (json format)", default="/etc/evpn.json") | |
| args = parser.parse_args() | |
| main(args.local, args.config) |
This file contains hidden or 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
| { | |
| "bridge": { | |
| "name": "vxbr0", | |
| "vxlan_name": "vx0" | |
| }, | |
| "vrfs": [ | |
| { | |
| "name": "vrf0", | |
| "svi": { | |
| "name": "vrf0l3", | |
| "vni": 32000, | |
| "vlan": 32, | |
| "hwaddr": "2a:1b:c4:c0:b7:ff", | |
| "nodes": { | |
| "node0": "2a:1b:c4:ff:ad:f8", | |
| "node1": "2a:1b:c4:f1:2b:cb", | |
| "node2": "2a:1b:c4:c4:f3:01", | |
| "node3": "2a:1b:c4:16:0d:66", | |
| "node4": "2a:1b:c4:4f:3d:5c" | |
| } | |
| }, | |
| "vlans": [ | |
| { | |
| "name": "vlan0", | |
| "vni": 32010, | |
| "vlan": 300, | |
| "ipv4": "10.32.0.1/24", | |
| "ipv6": "fddf:3bdb:9eee:2000::1/64", | |
| "hwaddr": "2a:1b:c4:fb:b0:db", | |
| "nodes": { | |
| "node0": { | |
| "ipv4": "10.32.0.3/24", | |
| "ipv6": "fddf:3bdb:9eee:2000::3/64" | |
| }, | |
| "node1": { | |
| "ipv4": "10.32.0.4/24", | |
| "ipv6": "fddf:3bdb:9eee:2000::4/64" | |
| }, | |
| "node2": { | |
| "ipv4": "10.32.0.5/24", | |
| "ipv6": "fddf:3bdb:9eee:2000::5/64" | |
| }, | |
| "node3": { | |
| "ipv4": "10.32.0.6/24", | |
| "ipv6": "fddf:3bdb:9eee:2000::6/64" | |
| } | |
| } | |
| }, | |
| { | |
| "name": "vlan1", | |
| "vni": 32020, | |
| "vlan": 301, | |
| "ipv4": "10.32.10.1/24", | |
| "ipv6": "fddf:3bdb:9eee:20a0::1/64", | |
| "hwaddr": "2a:1b:c4:2d:e8:3f", | |
| "nodes": { | |
| "node0": { | |
| "ipv4": "10.32.10.3/24", | |
| "ipv6": "fddf:3bdb:9eee:20a0::3/64" | |
| }, | |
| "node1": { | |
| "ipv4": "10.32.10.4/24", | |
| "ipv6": "fddf:3bdb:9eee:20a0::4/64" | |
| }, | |
| "node2": { | |
| "ipv4": "10.32.10.5/24", | |
| "ipv6": "fddf:3bdb:9eee:20a0::5/64" | |
| }, | |
| "node3": { | |
| "ipv4": "10.32.10.6/24", | |
| "ipv6": "fddf:3bdb:9eee:20a0::6/64" | |
| } | |
| } | |
| }, | |
| { | |
| "name": "vlan3", | |
| "vni": 32030, | |
| "vlan": 302, | |
| "ipv4": "10.32.20.1/24", | |
| "ipv6": "fddf:3bdb:9eee:2014::1/64", | |
| "hwaddr": "2a:1b:c4:2d:e8:3e", | |
| "nodes": { | |
| "node4": { | |
| "ipv4": "10.32.20.2/24", | |
| "ipv6": "fddf:3bdb:9eee:2014::2/64" | |
| } | |
| } | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "vrf1", | |
| "svi": { | |
| "name": "vrf1l3", | |
| "vni": 16000, | |
| "vlan": 16, | |
| "hwaddr": "2a:1b:c4:c0:b7:ff", | |
| "nodes": { | |
| "node0": "2a:1b:c4:d4:23:b4", | |
| "node1": "2a:1b:c4:c2:92:51", | |
| "node2": "2a:1b:c4:ba:2b:6d", | |
| "node3": "2a:1b:c4:b1:e3:f8", | |
| "node4": "2a:1b:c4:69:e3:7c" | |
| } | |
| }, | |
| "vlans": [ | |
| { | |
| "name": "vlan10", | |
| "vni": 16010, | |
| "vlan": 400, | |
| "ipv4": "10.16.0.1/24", | |
| "ipv6": "fddf:3bdb:9eee:1000::1/64", | |
| "hwaddr": "2a:1b:c4:2f:f4:79", | |
| "nodes": { | |
| "node0": { | |
| "ipv4": "10.16.0.3/24", | |
| "ipv6": "fddf:3bdb:9eee:1000::3/64" | |
| }, | |
| "node1": { | |
| "ipv4": "10.16.0.4/24", | |
| "ipv6": "fddf:3bdb:9eee:1000::4/64" | |
| }, | |
| "node2": { | |
| "ipv4": "10.16.0.5/24", | |
| "ipv6": "fddf:3bdb:9eee:1000::5/64" | |
| }, | |
| "node3": { | |
| "ipv4": "10.16.0.6/24", | |
| "ipv6": "fddf:3bdb:9eee:1000::6/64" | |
| } | |
| } | |
| }, | |
| { | |
| "name": "vlan11", | |
| "vni": 16020, | |
| "vlan": 401, | |
| "ipv4": "10.16.10.1/24", | |
| "ipv6": "fddf:3bdb:9eee:10a0::1/64", | |
| "hwaddr": "2a:1b:c4:4b:01:3e", | |
| "nodes": { | |
| "node4": { | |
| "ipv4": "10.16.10.3/24", | |
| "ipv6": "fddf:3bdb:9eee:10a0::3/64" | |
| } | |
| } | |
| } | |
| ] | |
| } | |
| ] | |
| } |
This file contains hidden or 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
| frr version 10.2.1 | |
| frr defaults traditional | |
| hostname node0 | |
| log syslog informational | |
| service integrated-vtysh-config | |
| ! | |
| ip router-id 10.0.0.70 | |
| ! | |
| vrf vrf0 | |
| vni 32000 | |
| exit-vrf | |
| ! | |
| vrf vrf1 | |
| vni 16000 | |
| exit-vrf | |
| ! | |
| interface lo | |
| ip address 10.0.0.70/32 | |
| ipv6 address fd04:bf63::70/128 | |
| ipv6 ospf6 area 0.0.0.0 | |
| ipv6 ospf6 passive | |
| exit | |
| ! | |
| interface uplink1 | |
| ipv6 ospf6 area 0.0.0.0 | |
| ipv6 ospf6 network point-to-point | |
| exit | |
| ! | |
| interface vrf0 | |
| ip address 10.0.0.70/32 | |
| ipv6 address fd04:bf63::70/128 | |
| exit | |
| ! | |
| interface vrf1 | |
| ip address 10.0.0.70/32 | |
| ipv6 address fd04:bf63::70/128 | |
| exit | |
| ! | |
| ! my switch only supports vxlan ipv4, which explains the gymnastics here | |
| router bgp 65000 | |
| bgp router-id 10.0.0.70 | |
| no bgp ebgp-requires-policy | |
| no bgp default ipv4-unicast | |
| neighbor EVPN peer-group | |
| neighbor EVPN remote-as internal | |
| neighbor EVPN update-source 10.0.0.70 | |
| neighbor EVPN capability extended-nexthop | |
| neighbor PEERS peer-group | |
| neighbor PEERS remote-as internal | |
| neighbor PEERS update-source fd04:bf63::70 | |
| neighbor PEERS capability dynamic | |
| neighbor PEERS capability extended-nexthop | |
| neighbor 10.0.0.27 peer-group EVPN | |
| neighbor 10.0.0.30 peer-group EVPN | |
| neighbor 10.0.0.50 peer-group EVPN | |
| neighbor 10.0.0.60 peer-group EVPN | |
| neighbor fd04:bf63::27 peer-group PEERS | |
| neighbor fd04:bf63::30 peer-group PEERS | |
| neighbor fd04:bf63::50 peer-group PEERS | |
| neighbor fd04:bf63::60 peer-group PEERS | |
| ! | |
| address-family ipv4 unicast | |
| redistribute connected | |
| neighbor PEERS activate | |
| exit-address-family | |
| ! | |
| address-family ipv6 unicast | |
| redistribute connected | |
| neighbor PEERS activate | |
| exit-address-family | |
| ! | |
| address-family l2vpn evpn | |
| neighbor EVPN activate | |
| advertise-all-vni | |
| advertise-svi-ip | |
| exit-address-family | |
| exit | |
| ! | |
| router bgp 65000 vrf vrf0 | |
| bgp router-id 10.0.0.70 | |
| no bgp network import-check | |
| ! | |
| address-family ipv4 unicast | |
| redistribute connected | |
| exit-address-family | |
| ! | |
| address-family ipv6 unicast | |
| redistribute connected | |
| exit-address-family | |
| ! | |
| address-family l2vpn evpn | |
| advertise ipv4 unicast | |
| advertise ipv6 unicast | |
| rd 10.0.0.70:32 | |
| route-target import 65000:32000 | |
| route-target export 65000:32000 | |
| exit-address-family | |
| exit | |
| ! | |
| router bgp 65000 vrf vrf1 | |
| bgp router-id 10.0.0.70 | |
| ! | |
| address-family ipv4 unicast | |
| redistribute connected | |
| exit-address-family | |
| ! | |
| address-family ipv6 unicast | |
| redistribute connected | |
| exit-address-family | |
| ! | |
| address-family l2vpn evpn | |
| advertise ipv4 unicast | |
| advertise ipv6 unicast | |
| rd 10.0.0.70:16 | |
| route-target import 65000:16000 | |
| route-target export 65000:16000 | |
| exit-address-family | |
| exit | |
| ! | |
| router ospf6 | |
| redistribute connected | |
| exit | |
| ! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment