{% set lo_address = interfaces | selectattr('name', '==', 'lo') | map(attribute='ip_addresses') | first | selectattr('role') | selectattr('role.value', '==', 'loopback') | map(attribute='address') %} {% set inside_vrfs = interfaces | selectattr('parent') | selectattr('parent.name', '==', 'bridge') | selectattr('vrf') | map(attribute='vrf.name') | reject('==', 'outside') | sort | unique %} frr defaults datacenter log syslog informational service integrated-vtysh-config # Route to the outside world. vrf outside ip route 0.0.0.0/0 {{ (interfaces | selectattr('name', '==', iface_uplink) | first).custom_fields.gateway.address | ipaddr('address') }} {{ iface_uplink }} ipv6 route ::/0 fe80::2 {{ iface_uplink }} # Route installation into kernel fails (rarely) without this option. # It is not documented anywhere and appears to be a Cumulus extension. zebra nexthop proto only router-id {{ lo_address | ipv4 | first | ipaddr('address') }} # Default VRF. router bgp {{ asn.asn }} bgp bestpath as-path multipath-relax neighbor fabric peer-group neighbor fabric remote-as external neighbor fabric capability extended-nexthop neighbor peerlink.4094 interface remote-as external neighbor peerlink.4094 capability extended-nexthop neighbor peerlink.4094 bfd 3 150 150 {% for iface in ifaces_fabric %} neighbor {{ iface }} interface peer-group fabric neighbor {{ iface }} bfd 3 150 150 {% endfor %} address-family ipv4 unicast redistribute connected route-map loopback neighbor fabric soft-reconfiguration inbound neighbor fabric route-map fabric->default in neighbor fabric route-map default->fabric out import vrf outside import vrf route-map default-import exit-address-family address-family ipv6 unicast redistribute connected route-map loopback neighbor fabric activate neighbor fabric soft-reconfiguration inbound neighbor fabric route-map fabric->default in neighbor fabric route-map default->fabric out import vrf outside import vrf route-map default-import exit-address-family address-family l2vpn evpn advertise-all-vni advertise-default-gw neighbor fabric activate neighbor peerlink.4094 activate exit-address-family # Outside VRF. Direct route to the world, everything else goes to the firewall. router bgp {{ asn.asn }} vrf outside bgp bestpath as-path multipath-relax neighbor peerlink.4 interface remote-as external neighbor peerlink.4 capability extended-nexthop neighbor peerlink.4 bfd 3 150 150 neighbor firewall peer-group neighbor firewall remote-as external neighbor firewall capability extended-nexthop {% for iface in ifaces_firewall %} neighbor {{ iface }}.4 interface peer-group firewall neighbor {{ iface }}.4 bfd 3 150 150 {% endfor %} address-family ipv4 unicast neighbor peerlink.4 soft-reconfiguration inbound neighbor peerlink.4 route-map peer.4->me in neighbor peerlink.4 route-map me->peer.4 out neighbor firewall allowas-in 1 neighbor firewall default-originate neighbor firewall soft-reconfiguration inbound neighbor firewall route-map outside->firewall out {% for iface in ifaces_firewall %} neighbor {{ iface }}.4 route-map firewall-{{ loop.index }}->outside in {% endfor %} redistribute static redistribute connected route-map loopback-outside import vrf default import vrf route-map outside-import exit-address-family address-family ipv6 unicast neighbor peerlink.4 activate neighbor peerlink.4 allowas-in origin neighbor peerlink.4 soft-reconfiguration inbound neighbor peerlink.4 route-map peer.4->me in neighbor peerlink.4 route-map me->peer.4 out neighbor firewall activate neighbor firewall allowas-in 1 neighbor firewall default-originate neighbor firewall soft-reconfiguration inbound neighbor firewall route-map outside->firewall out {% for iface in ifaces_firewall %} neighbor {{ iface }}.4 route-map firewall-{{ loop.index }}->outside in {% endfor %} redistribute static redistribute connected route-map loopback-outside import vrf default import vrf route-map outside-import exit-address-family # Inside VRF. Default route via firewall. Direct routes to servers and offices. router bgp {{ asn.asn }} vrf inside bgp bestpath as-path multipath-relax neighbor peerlink.2 interface remote-as external neighbor peerlink.2 capability extended-nexthop neighbor peerlink.2 bfd 3 150 150 neighbor firewall peer-group neighbor firewall remote-as external neighbor firewall capability extended-nexthop {% for iface in ifaces_firewall %} neighbor {{ iface }}.2 interface peer-group firewall neighbor {{ iface }}.2 bfd 3 150 150 {% endfor %} address-family ipv4 unicast neighbor peerlink.2 soft-reconfiguration inbound neighbor peerlink.2 route-map peer.2->me in neighbor peerlink.2 route-map me->peer.2 out neighbor firewall allowas-in 1 neighbor firewall soft-reconfiguration inbound neighbor firewall route-map inside->firewall out {% for iface in ifaces_firewall %} neighbor {{ iface }}.2 route-map firewall-{{ loop.index }}->inside in {% endfor %} redistribute connected route-map loopback-inside {% for vrf in inside_vrfs %} import vrf {{ vrf }} {% endfor %} import vrf default import vrf route-map inside-import exit-address-family address-family ipv6 unicast neighbor peerlink.2 activate neighbor peerlink.2 soft-reconfiguration inbound neighbor peerlink.2 route-map peer.2->me in neighbor peerlink.2 route-map me->peer.2 out neighbor firewall activate neighbor firewall allowas-in 1 neighbor firewall soft-reconfiguration inbound neighbor firewall route-map inside->firewall out {% for iface in ifaces_firewall %} neighbor {{ iface }}.2 route-map firewall-{{ loop.index }}->inside in {% endfor %} redistribute connected route-map loopback-inside {% for vrf in inside_vrfs %} import vrf {{ vrf }} {% endfor %} import vrf default import vrf route-map inside-import exit-address-family {% for vrf in vrfs.values() | selectattr('name', 'in', inside_vrfs) %} router bgp {{ asn.asn }} vrf {{ vrf.name }} bgp bestpath as-path multipath-relax address-family ipv4 unicast redistribute connected import vrf inside {% if vrf.custom_fields.imports %} {% for import in vrf.custom_fields.imports %} import vrf {{ import.name }} {% endfor %} import vrf route-map {{ vrf.name }}-import {% else %} import vrf route-map office-import {% endif %} exit-address-family address-family ipv6 unicast redistribute connected import vrf inside {% if vrf.custom_fields.imports %} {% for import in vrf.custom_fields.imports %} import vrf {{ import.name }} {% endfor %} import vrf route-map {{ vrf.name }}-import {% else %} import vrf route-map office-import {% endif %} exit-address-family {% endfor %} # Prefix lists. ip prefix-list default permit 0.0.0.0/0 ipv6 prefix-list default permit ::/0 ip prefix-list fabric permit 10.34.0.0/24 ge 32 ipv6 prefix-list fabric permit 2001:1470:fffd:3400::/64 ge 128 # common prefix list for all inside networks {% for prefix in vrf_prefixes | selectattr('vrf.name', 'in', inside_vrfs) | sort(attribute='family.value') | sort(attribute='vlan.vid') %} {% if prefix.family.value == 4 %} ip prefix-list office permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr('prefix') }} {% else %} ipv6 prefix-list office permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr('prefix') }} {% endif %} {% endfor %} # individual prefix lists for each inside network {% for prefix in vrf_prefixes | selectattr('vrf.name', 'in', inside_vrfs) | sort(attribute='family.value') | sort(attribute='vrf.name') %} {% if prefix.family.value == 4 %} ip prefix-list {{ prefix.vrf.name }} permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr('prefix') }} {% else %} ipv6 prefix-list {{ prefix.vrf.name }} permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr('prefix') }} {% endif %} {% endfor %} {% if wg_net is defined %} ip prefix-list vpn permit {{ wg_net | ipaddr('subnet') }} {% endif %} {% if wg_net6 is defined %} ipv6 prefix-list vpn permit {{ wg_net6 | ipaddr('subnet') }} {% endif %} ip prefix-list nat permit {{ wg_ip | ipaddr('host') }} {% for network in nat %} ip prefix-list nat permit {{ network }} {% endfor %} {% for prefix in bgp_prefixes | sort(attribute='family.value') %} {% if prefix.family.value == 4 %} ip prefix-list dc permit {{ prefix.prefix }} ge 32 {% else %} ipv6 prefix-list dc permit {{ prefix.prefix }} ge 64 {% endif %} {% endfor %} # Route maps for redistributing own IPs from various VRFs. route-map loopback permit 1 match interface lo route-map loopback-inside permit 1 match interface inside route-map loopback-outside permit 1 match interface outside # Route maps for importing between VRFs. route-map default-import permit 10 match ip address prefix-list default route-map default-import permit 11 match ipv6 address prefix-list default route-map default-import permit 21 match ipv6 address prefix-list office route-map default-import permit 30 match ip address prefix-list nat route-map default-import permit 31 match ipv6 address prefix-list vpn route-map outside-import permit 10 match ip address prefix-list dc route-map outside-import permit 11 match ipv6 address prefix-list dc route-map office-import permit 10 match ip address prefix-list default route-map office-import permit 11 match ipv6 address prefix-list default route-map inside-import permit 20 match ip address prefix-list office route-map inside-import permit 21 match ipv6 address prefix-list office {% for vrf in vrfs.values() | selectattr('custom_fields.imports') %} route-map {{ vrf.name }}-import permit 10 match ip address prefix-list default route-map {{ vrf.name }}-import permit 11 match ipv6 address prefix-list default {% for import in vrf.custom_fields.imports %} route-map {{ vrf.name }}-import permit {{ 100 + 10*loop.index0 }} match ip address prefix-list {{ import.name }} route-map {{ vrf.name }}-import permit {{ 101 + 10*loop.index0 }} match ipv6 address prefix-list {{ import.name }} {% endfor %} {% endfor %} # Route maps for advertised and received routes. # Default VRF ↔ fabric. route-map default->fabric permit 10 match ip address prefix-list default route-map default->fabric permit 11 match ipv6 address prefix-list default route-map default->fabric permit 20 match ip address prefix-list fabric route-map fabric->default permit 10 match ip address prefix-list fabric route-map fabric->default permit 20 match ip address prefix-list dc route-map fabric->default permit 21 match ipv6 address prefix-list dc # Inside VRF ↔ firewall. route-map inside->firewall permit 1 match interface lo route-map inside->firewall permit 20 match ip address prefix-list office route-map inside->firewall permit 21 match ipv6 address prefix-list office route-map firewall->inside permit 1 match ip address prefix-list fabric route-map firewall->inside permit 2 match ipv6 address prefix-list fabric route-map firewall->inside permit 10 match ip address prefix-list default route-map firewall->inside permit 11 match ipv6 address prefix-list default # Outside VRF ↔ firewall. route-map outside->firewall permit 10 match ip address prefix-list default route-map outside->firewall permit 11 match ipv6 address prefix-list default route-map firewall->outside permit 1 match ip address prefix-list fabric route-map firewall->outside permit 2 match ipv6 address prefix-list fabric route-map firewall->outside permit 21 match ipv6 address prefix-list office route-map firewall->outside permit 30 match ip address prefix-list nat route-map firewall->outside permit 31 match ipv6 address prefix-list vpn # Tag routes from each firewall. Set weight for primary to 200 and secondary to 100. {% for firewall in ifaces_firewall %} route-map firewall-{{ loop.index }}->inside permit 1 set tag {{ loop.index }} set weight {{ 100 * loop.index }} call firewall->inside route-map firewall-{{ loop.index }}->outside permit 1 set tag {{ loop.index }} set weight {{ 100 * loop.index }} call firewall->outside {% endfor %} # Backup routes over peer link are announced to the peer with BGP # metrics 190 and 90. These values are copied to weights by receiving # peer, to be used alongside local routes with weights 200 and 100. # These are the route maps for peerlink in the inside VRF. {% for firewall in ifaces_firewall %} {% set metric = 100 * loop.index - 10 %} route-map me->peer.2 permit {{ loop.index }} match tag {{ loop.index }} on-match goto 100 set metric {{ metric }} route-map peer.2->me permit {{ loop.index }} match metric {{ metric }} on-match goto 100 set weight {{ metric }} {% endfor %} # Advertised backup routes for paths that go through the firewall # (default route). route-map me->peer.2 permit 110 match ip address prefix-list default route-map me->peer.2 permit 111 match ipv6 address prefix-list default # Received backup routes (same as above). route-map peer.2->me permit 110 match ip address prefix-list default route-map peer.2->me permit 111 match ipv6 address prefix-list default # These are the route maps for peerlink in the outside VRF. {% for firewall in ifaces_firewall %} {% set metric = 100 * loop.index - 10 %} route-map me->peer.4 permit {{ loop.index }} match tag {{ loop.index }} on-match goto 100 set metric {{ metric }} route-map peer.4->me permit {{ loop.index }} match metric {{ metric }} on-match goto 100 set weight {{ metric }} {% endfor %} # Backup routes for uplink and paths that go through the firewall # (default route and NAT/IPv6 addresses for office networks). route-map me->peer.4 permit 110 match ip address prefix-list default route-map me->peer.4 permit 111 match ipv6 address prefix-list default route-map me->peer.4 permit 120 match ip address prefix-list nat route-map me->peer.4 permit 121 match ipv6 address prefix-list vpn route-map me->peer.4 permit 131 match ipv6 address prefix-list office # Received backup routes (same as above). route-map peer.4->me permit 110 match ip address prefix-list default route-map peer.4->me permit 111 match ipv6 address prefix-list default route-map peer.4->me permit 120 match ip address prefix-list nat route-map peer.4->me permit 121 match ipv6 address prefix-list vpn route-map peer.4->me permit 131 match ipv6 address prefix-list office