network/roles/exit/templates/frr.conf.j2
Timotej Lazar 5a9f0ac26a exit: strip own AS prefix from routes received by firewalls
For some reason routes with own ASN are not imported into default VRF.
Maybe also others. These routes forward packets through the firewalls.
As long as both exits are up this is not a problem, because routes
going to peer exit don’t include this exit’s own ASN.

If the peer goes down, all remaining routes sent by firewalls have our
own ASN and are not imported into default VRF, so L3 servers lose
connectivity to internal networks.

If the exit strips own ASN from received routes, importing works OK.
We strip both our and peer’s ASNs to keep path lengths the same.

This has involved an indecent amount of poking knobs and knobbing
pokes and it might cause other issues elsewhere.
2024-09-21 16:32:28 +02:00

478 lines
16 KiB
Django/Jinja

{% 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
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
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
# prefix list for outside networks
{% for prefix in vrf_prefixes | selectattr('vrf.name', '==', 'outside')
| sort(attribute='family.value') | sort(attribute='vlan.vid') %}
{% if prefix.family.value == 4 %}
ip prefix-list outside permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr('prefix') }}
{% else %}
ipv6 prefix-list outside permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr('prefix') }}
{% endif %}
{% endfor %}
# 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 20
match ip address prefix-list office
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 40
match ip address prefix-list vpn
route-map default-import permit 41
match ipv6 address prefix-list vpn
route-map default-import permit 50
match ip address prefix-list outside
route-map default-import permit 51
match ipv6 address prefix-list outside
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 20
match ip address prefix-list office
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 40
match ip address prefix-list vpn
route-map firewall->outside permit 41
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 }}
set as-path exclude {{ asn.asn }} {{ hostvars[peer].asn.asn }}
call firewall->inside
route-map firewall-{{ loop.index }}->outside permit 1
set tag {{ loop.index }}
set weight {{ 100 * loop.index }}
set as-path exclude {{ asn.asn }} {{ hostvars[peer].asn.asn }}
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 office
route-map me->peer.4 permit 121
match ipv6 address prefix-list office
route-map me->peer.4 permit 130
match ip address prefix-list nat
route-map me->peer.4 permit 140
match ip address prefix-list vpn
route-map me->peer.4 permit 141
match ipv6 address prefix-list vpn
# 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 office
route-map peer.4->me permit 121
match ipv6 address prefix-list office
route-map peer.4->me permit 130
match ip address prefix-list nat
route-map peer.4->me permit 140
match ip address prefix-list vpn
route-map peer.4->me permit 141
match ipv6 address prefix-list vpn