Déploiement MPLS sur les LNS Mikrotik

💡
Toutes les configurations sont commit sur mon github : https://github.com/Nathan0510/Blog

Avec le changement des PE, on se doit de pouvoir proposer les mêmes offres DATA qu'avec les Cisco :

    • Internet : Publique + NAT
    • MPLS
    • SDWAN (Hors cadre LNS)

Les accès Internet ont été vu dans l'ancien épisode. Il nous reste plus qu'à voir comment proposer du MPLS à nos clients avec les Mikrotiks 😄

Configuration

On va créer la VRF Customer1.

Tout d'abord, il nous faut une loopback :

#LNS1
/interface bridge
add name=Lo_Customer1
/ip address
add address=100.101.0.1 interface=Lo_Customer1

On crée une liste d'interface et on place la loopback du client dedans :

#LNS1
/interface list
add name=VRF-Customer1
/interface list member
add interface=Lo_Customer1 list=VRF-Customer1

Ensuite, on peut créer la VRF :

#LNS1
/ip vrf
add interfaces=VRF-Customer1 name=Customer1

A l'heure actuelle, on a créer une liste d'interface qu'on a placé dans la VRF cliente. Cette liste est dynamique ! Les clients PPP vont pouvoir monter dedans.

Et maintenant le PPP :

#LNS1
/ppp profile
add change-tcp-mss=yes interface-list=VRF-Customer1 local-address=100.101.0.1 name=Profile-Customer1 use-compression=no use-encryption=no use-mpls=no

Cette ligne va permettre de créer un profile PPP nommé Customer1. On va l'utiliser dans la configuration de l'user dans le freeradius.

Et on déclare le L3VPN MPLS dans le BGP :

#LNS1
/routing bgp vpn
add disabled=no export.redistribute=connected,static,bgp .route-targets=64555:64560 import.route-targets=64555:64560 instance=BGP_BACKBONE \
    label-allocation-policy=per-vrf name=Customer1 route-distinguisher=64555:64560 vrf=Customer1

Bon c'est plutôt mal ! Regardons ce que ca donne avec un utilisateur configuré comme ca dans le radius :

#RADIUS
test1@naruto.ninja   Cleartext-Password := "nathan"
        Service-Type := Framed-User,
        Framed-Protocol := PPP,
        Framed-IP-Address := 100.64.0.1,
        Framed-IP-Netmask := 255.255.255.255,
        Framed-Route := "10.0.0.0/24 100.64.0.1",
        Mikrotik-Group := "Profile-Customer1"

Le PPP monte bien :

#LNS1
[admin@PE-LNS1] /ppp/active> pr
Flags: R - RADIUS
Columns: NAME, SERVICE, CALLER-ID, ADDRESS, UPTIME
#   NAME                SERVICE  CALLER-ID          ADDRESS     UPTIME
0 R test1@naruto.ninja  pppoe    50:00:00:47:00:00  100.64.0.1  10s

La table de routage de la VRF Customer1 :

#LNS1
[admin@PE-LNS1] > /ip route/print where routing-table=Customer1
Flags: D - DYNAMIC; A - ACTIVE; c - CONNECT, b - BGP
Columns: DST-ADDRESS, GATEWAY, ROUTING-TABLE, DISTANCE
    DST-ADDRESS     GATEWAY                               ROUTING-TABLE  DISTANCE
DAc 100.64.0.1/32   @Customer1  Customer1             0
DAc 100.101.0.1/32  Lo_Customer1@Customer1                Customer1             0

La framed route ? Wesh ? Elle est où ?

#LNS1
[admin@PE-LNS1] > /ip route/print where dst-address=10.0.0.0/24
Flags: D - DYNAMIC; I - INACTIVE; v - VPN
Columns: DST-ADDRESS, GATEWAY, ROUTING-TABLE, DISTANCE
    DST-ADDRESS  GATEWAY     ROUTING-TABLE  DISTANCE
DIv 10.0.0.0/24  100.64.0.1  main                  1

Elle monte dans la VRF main .....

Et oui ! Mikrotik ne prend pas en charge les framed-routes dans des VRF 😦

Comment faire ? On ne va pas pouvoir proposer de MPLS aux clients ?

La solution à tous nos problèmes : BGP

On va monter des session eBGP entre les CPE et les PE !

Pensons industrialisation et automatisation :

    • Un BGP listen range par VRF
    • Redistribute connected,static sur les CPE
    • Principe de nominal/backup

Tout d'abord, on commence par créer une template BGP sur les PE qui va servir pour tous les clients L3VPN MPLS :

#LNS1
/routing bgp template
add hold-time=1m keepalive-time=30s name=L3VPN-Customer output.default-originate=always

Ensuite, on peut configurer le BGP listen range :

#LNS1
/routing bgp connection
add instance=BGP_BACKBONE local.address=100.101.0.1 .role=ebgp name=Customer1 remote.address=100.64.0.0/16 templates=L3VPN-Customer vrf=Customer1

On autorise toute la plage 100.64.0.0/16 (Subnet de management des CPE) à pouvoir monter un peering avec l'IP 100.101.0.1 (Loopback de la VRF) dans la VRF Customer1.

Et .... c'est tout comme configuration ! Sah quel plaisir de réfléchir deux minutes en amont 😄

Du côté des CPE, on crée la route map qui va permettre de faire varier l'AS PATH dans les annonces. Plus un AS PATH est grand, moins la route est prioritaire.

#NOMINAL
/routing filter rule
add chain=RM_PRINCIPAL rule="if (dst in 10.0.0.0/8 || dst in 172.16.0.0/12 || dst in 192.168.0.0/16) { set bgp-path-prepend 1; accept }"

#BACKUP
/routing filter rule
add chain=RM_BACKUP rule="if (dst in 10.0.0.0/8 || dst in 172.16.0.0/12 || dst in 192.168.0.0/16) {set bgp-path-prepend 5; accept}"
#NOMINAL
/routing bgp connection
add as=64560 local.role=ebgp name=LNS1 output.filter-chain=RM_PRINCIPAL  .redistribute=connected,static remote.address=100.101.0.1

#BACKUP
/routing bgp connection
add as=64560 local.role=ebgp name=LNS1 output.filter-chain=RM_BACKUP .redistribute=connected,static remote.address=100.101.0.1

Les peerings montent bien dans la VRF sur les LNS :

#LNS1
[admin@PE-LNS1] /routing/bgp/session> pr
Flags: E - established
 0 E name="Customer1-1" instance=BGP_BACKBONE
     remote.address=100.64.0.1@Customer1 .as=64560 .id=192.168.4.1 .capabilities=mp,rr,gr,as4 .afi=ip .messages=30 .bytes=603 .eor=""
     local.address=100.101.0.1@Customer1 .as=64555 .id=100.124.0.1 .cluster-id=100.124.0.1 .capabilities=mp,rr,gr,as4 .afi=ip .messages=23 .bytes=549
     .eor=""
     output.procid=20 .default-originate=always
     input.procid=20 ebgp
     routing-table=Customer1 hold-time=1m keepalive-time=30s uptime=9m31s820ms last-started=2025-06-24 19:19:29 prefix-count=2

 1 E name="Customer1-2" instance=BGP_BACKBONE
     remote.address=100.64.0.2@Customer1 .as=64560 .id=192.168.2.1 .capabilities=mp,rr,gr,as4 .afi=ip .messages=8 .bytes=201 .eor=""
     local.address=100.101.0.1@Customer1 .as=64555 .id=100.124.0.1 .cluster-id=100.124.0.1 .capabilities=mp,rr,gr,as4 .afi=ip .messages=8 .bytes=264
     .eor=""
     output.procid=21 .default-originate=always
     input.procid=21 ebgp
     routing-table=Customer1 hold-time=1m keepalive-time=30s uptime=2m18s160ms last-started=2025-06-24 19:26:43 prefix-count=2

Admettons que le subnet 192.168.1.0/24 doit être backup :

#LNS1
[admin@PE-LNS1] /ip/route> print where routing-table=Customer1 dst-address=192.168.1.0/24
Flags: D - DYNAMIC; A - ACTIVE; b - BGP
Columns: DST-ADDRESS, GATEWAY, ROUTING-TABLE, DISTANCE
    DST-ADDRESS     GATEWAY               ROUTING-TABLE  DISTANCE
DAb 192.168.1.0/24  100.64.0.1@Customer1  Customer1            20
D b 192.168.1.0/24  100.64.0.2@Customer1  Customer1            20

La route est bien annoncée par les deux CPE mais seul la route du principal monte dans la table de routage.

#LNS1
[admin@PE-LNS1] > /routing/route/print detail where routing-table=Customer1 dst-address=192.168.1.0/24
Flags: X - disabled, F - filtered, U - unreachable, A - active;
c - connect, s - static, r - rip, b - bgp, o - ospf, i - isis, d - dhcp, v - vpn, m - modem, a - ldp-address, l - ldp-mapping, g - slaac, y - bgp-mpls>
H - hw-offloaded; + - ecmp, B - blackhole
 Ab   afi=ip contribution=active dst-address=192.168.1.0/24 routing-table=Customer1 gateway=100.64.0.1@Customer1
       immediate-gw=100.64.0.1% distance=20 scope=40 target-scope=10 belongs-to="bgp-IP-100.64.0.1@*1"
       bgp.session=Customer1-1 .as-path="64560" .origin=igp
       debug.fwp-ptr=0x20302A80

  b   afi=ip contribution=candidate dst-address=192.168.1.0/24 routing-table=Customer1 gateway=100.64.0.2@Customer1
       immediate-gw=100.64.0.2% distance=20 scope=40 target-scope=10 belongs-to="bgp-IP-100.64.0.2@*1"
       bgp.session=Customer1-2 .as-path="64560,64560,64560,64560,64560" .origin=igp
       debug.fwp-ptr=0x20302C00

Comme vous pouvez le voir, la route est bien apprise deux fois mais avec un AS PATH différent. Ainsi, le PE va privilégier la route avec .as-path="64560" plutôt que .as-path="64560,64560,64560,64560,64560".

Et du côté de LNS2 ? Ca dit quoi ?

#LNS2
[admin@PE-LNS2] > /ip route/print where routing-table=Customer1
Flags: D - DYNAMIC; A - ACTIVE; c - CONNECT, y - BGP-MPLS-VPN
Columns: DST-ADDRESS, GATEWAY, ROUTING-TABLE, DISTANCE
    DST-ADDRESS     GATEWAY                 ROUTING-TABLE  DISTANCE
DAy 192.168.1.0/24  100.124.0.1             Customer1           200
DAy 100.64.0.1/32   100.124.0.1             Customer1           200
DAy 100.64.0.2/32   100.124.0.1             Customer1           200
D y 100.101.0.1/32  100.124.0.1             Customer1           200
DAc 100.101.0.1/32  Lo_Customer1@Customer1  Customer1             0

Les routes sont bien inscrites dans la table de routage de la VRF 😄!

Allez un peu d'automatisation

Pour récapituler, il nous comme informations :

    • Le nom de la VRF
    • Le RD
    • L'AS des CPE
    • IP de la Loopback de la VRF

J'utilise Netbox pour mon IPAM. C'est ma source de vérité. Toutes les informations de mon réseau sont dedans. Que ce soit le subnet d'interco de deux routeurs backbone comme la position précise d'un device dans une baie dans un datacenter.

Il existe une API qui permet d'interroger Netbox et faire du first available. C'est ce qu'il nous faut ! On va prendre le premier RD dispo, l'AS et une IP pour la looback. Le nom de la VRF sera donné par l'utilisateur qui souhaite réserver la configuration. Une fois la réservation faite, je souhaite que le même utilisateur puisse pousser la configuration de façon automatique sur les LNS.

Pour répondre à ces besoin, je vais utiliser du go et de l'ansible. Le go me servira à interroger l'API netbox et lancer le playbook ansible (qui servira à pousser la configuration sur les LNS). Le tout avec une API en gin.

Avec une simple requête HTTP, on va pouvoir réserver et pousser la configuration 😄

Le code est ici :

J'utilise les custom fields de netbox. Pour une machine virtuelle (une VRF donc un client), je peux utiliser un json :

(Ne faites pas attention au custom field Fortigate, c'est pour automatiser la production de VDOM sur le même principe de go et ansible 😄)

Le code a donc réservé l'AS 4210000000 pour les CPE et 100.101.0.1 comme IP pour la loopback. Le RD sera toujours AS_BGP:AS_CPE. Donc 64555:4210000000.

Pour réserver les ressources (AS et IPLooback), j'utilise cette commande :

curl -X POST http://192.168.1.59:8081/conflns -H "Content-Type: application/json" -d '{"Vdom":"VRF_CLIENTE"}'

Pour pousser la configuration, j'utilise cette commande :

curl -X POST http://192.168.1.59:8081/postlns -H "Content-Type: application/json" -d '{"Vdom":"VRF_CLIENTE"}'

Ensuite, j'ai un playbook ansible qui vient s'exécuter sur les LNS.

Et le pire la dedans, c'est que ça fonctionne ;)

Conclusion

La configuration d'un L3VPN MPLS diffère un peu entre Mikrotik et Cisco surtout que j'ai dû changer la manière d'annoncer les subnets dans les MPLS clients avec la non prise en charge des framed-routes côté Mikrotik mais je suis plutôt content du résultat.

Un tech/admin réseau ne sera plus obligé de créer x framed route sur les logins. Un gain de temps non négligeable ! De plus, cette méthode permet d'avoir TOUT LE TEMPS la même template de configuration sur les CPE. Et pour les PE, c'est automatiser via le script go + ansible.

Prochain épisode : Soit je regarde comment recollecter les 4G/Starlink sur les LNS soit je commence à monter mon infra de virtualisation (cluster proxmox en EVPN/VXLAN sur mes Spines et du ZFS pour mon stockage).

Je termine sur un mot de fin : Si la production est industrialisée et automatisée, TOUT coulera de source après. Que ce soit le support, l'exploitation ou un ajout de sites pour un client.
Une production propre est nécessaire pour tout opérateur !