EVPN: All roads lead to the firewall | telcokwaks.comEVPN: All roads lead to the firewall<br>10 July 2026 · 8 min · 1640 words · Fifi | Translations:Fr
Table of Contents<br>Introduction#<br>In the past, I have worked with proprietary firewalls (Stormshield, Arkoon, Netasq). The experience was rather mixed:<br>the whole thing is hard to debug, updates are gated behind licenses, prices are excessive, and there are many technical limitations.<br>So many reasons to rule out proprietary gear for this kind of design, in favor of a Linux-based solution (FRR, BGP, nftables): open source, innovation, and independence.<br>The idea: a hub-and-spoke design where the firewalls play the role of hub, and nobody talks to anyone without going through them.<br>Overview#<br>Here is a simplified diagram of this design:
On the network’s routers, we instantiate a hub services VRF (HUBSVC in the configuration). This VRF holds only the default routes originated by the firewalls.<br>A second VRF is present: the spoke VRF. It serves as the home VRF for the client interfaces we want to isolate.<br>It lets us export, over EVPN, the various destinations reachable through the firewall. When machines in this VRF want to talk to other machines, their traffic is drawn in thanks to a leak of the hub services default routes.<br>In our diagram, the interface between r1 and riri is in the spoke VRF: when riri wants to communicate with loulou, it follows the hub services default route.<br>The firewall brings up IPv4 and IPv6 unicast eBGP sessions for spoke services (SPKSVC) and for hub services: EVPN stops at the spine. We could have used a single EVPN session between the firewall and the spine; keeping separate unicast sessions decouples network management from firewall management, for instance when different teams handle each.<br>Here is an example of traffic between riri and loulou:
For redundancy, this design can include several firewalls: the return traffic may then not take the same firewall as the outbound path. This kind of case rules out stateful firewalling: you would have to either synchronize conntrack across all firewalls (with conntrackd), or give up on state. The second option is the one chosen here, detailed in the Filtering section.
The EVPN mechanics#<br>The whole isolation trick lies in the route-targets of the spoke VRF. Here is the relevant FRR configuration on r1:<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br>22<br>23<br>24<br>25<br>26<br>27<br>28<br>29<br>30<br>31<br>32<br>33<br>34<br>35<br>36<br>37<br>38<br>39<br>40<br>41<br>42<br>! spoke VRF: hosts the client interfaces, bound to VNI 110 (L3VNI)<br>vrf spoke<br>vni 110<br>exit-vrf<br>! BGP instance for the spoke VRF, in the fabric AS (65010)<br>router bgp 65010 vrf spoke<br>address-family ipv4 unicast<br>! advertise the directly connected networks (the client subnets)<br>redistribute connected<br>! import the default from the HUBSVC VRF, filtered by the route-map (0.0.0.0/0 only)<br>import vrf route-map LEAK_DEFAULT4<br>import vrf HUBSVC<br>exit-address-family<br>address-family ipv6 unicast<br>redistribute connected<br>! same in IPv6: only ::/0 is allowed to leak from HUBSVC<br>import vrf route-map LEAK_DEFAULT6<br>import vrf HUBSVC<br>exit-address-family<br>address-family l2vpn evpn<br>rd 192.0.2.3:110<br>! sentinel import RT: matches nothing, so r1 learns no other spoke<br>route-target import 65010:65000<br>! export RT: used to generate the spoke services VRF<br>route-target export 65010:110<br>! advertise the VRF's IPv4/IPv6 prefixes into EVPN (type-5 routes)<br>advertise ipv4 unicast<br>advertise ipv6 unicast<br>exit-address-family<br>ip prefix-list DEFAULT4 seq 5 permit 0.0.0.0/0<br>ipv6 prefix-list DEFAULT6 seq 5 permit ::/0<br>route-map LEAK_DEFAULT4 permit 10<br>match ip address prefix-list DEFAULT4<br>route-map LEAK_DEFAULT6 permit 10<br>match ipv6 address prefix-list DEFAULT6
Three things to note:<br>The spoke VRF exports its networks with route-target 65010:110. The spine imports them into a spoke services VRF and advertises them over eBGP to the firewall: the firewall therefore knows every spoke destination.<br>The spoke VRF imports 65010:65000, a “sentinel” route-target that deliberately matches nothing. Without it, FRR would automatically import its own export route-target, and each spoke would learn the other spokes’ networks. This line is what guarantees there is no direct route between r1 and r2.<br>The default route comes in via import vrf HUBSVC, filtered by a route-map that only lets the default through: 0.0.0.0/0 in IPv4 (LEAK_DEFAULT4) and ::/0 in IPv6 (LEAK_DEFAULT6).<br>As a result, on r1, the spoke VRF’s routing table is minimal:<br>r1# show ip route vrf spoke<br>VRF spoke:<br>B>* 0.0.0.0/0 [20/0] via 192.0.2.1 (vrf HUBSVC) onlink, weight 1, 00:13:37<br>C>* 198.51.100.0/28 is directly connected, eth2, 00:13:42
r1# show ipv6 route vrf spoke<br>VRF spoke:<br>B>* ::/0 [20/0] via 2001:db8:ffff::1 (vrf HUBSVC) onlink, weight 1, 00:13:37<br>C>* 2001:db8:a::/64 is directly connected, eth2, 00:13:42
loulou’s networks (198.51.100.16/28 and 2001:db8:b::/64) appear nowhere: the only way out is the default route, and it leads to the firewall.<br>Filtering#<br>Here,...