Topology

Introduction

Hello all and thank you for checking out another one of my blog posts. It really means a lot. I recently completed the Cisco ENARSI exam after a few months of studying. It was honestly pretty difficult for me but the pass was worth it! I may have to write a blog post on studying for and passing that exam. In this post I will break down how I automated some parts of an MPLS L3VPN deployment. Configuring the provider edge devices to allow connectivity between customer edge routers.

In serious news, that was the intro you see in those recipe websites where you just want to see the ingredients and the cooking directions but there’s a whole life story on the dish or how it came about…. well this blog post is similar! Usually when studying for an exam I tend to get tunnel vision and focus on the task at hand. That also had me dropping the love for automation a bit. One of the main points in ENARSI is learning about MPLS and more specifically MPLS L3VPNs. I had such a joy learning more about this topic and running through multiple labs to get used to the technology. I wont go too deep into MPLS in this post since I’m assuming the reader is trying to automate that exact task or is familiar with it already.

Topology Brief

As you can see by the featured image in this post, we have four provider edge (PE) routers and four customer edge (CE) routes. Each PE device connects to one CE device, and all PE routers connect to the provider (P) router, also acting as our BGP route reflector. You might be thinking, why the route reflector? You could run iBGP sessions between all PE devices but down the road that can lead to a lot of overhead. In this case all PE devices only have one internal BGP relationship between the P router and one external BGP peering with the CE routers. This also makes the iBGP configuration on the PE routers very cookie cutter. In this case we are pretending not to have control of the CE devices, so those will already be preconfigured. I will include the configurations on the nodes so you can see what is all preconfigured.

Ce Routers Configuration Example

interface Loopback0
 ip address 172.16.1.1 255.255.255.0
!
interface Ethernet0/0
 ip address 10.0.11.2 255.255.255.0
!

router bgp 789
 bgp log-neighbor-changes
 network 172.16.1.0 mask 255.255.255.0
 neighbor 10.0.11.1 remote-as 12345
 neighbor 10.0.11.1 allowas-in

Every CE router looks very similar, one lookback interface to be advertised to the PE router and a small bit of BGP configuration. You could use different autonomous system (AS) numbers between customer routers, in this case I used the same AS for the same customer. In BGP this would introduce a loop since the router will see its own AS in the path. This design option adds the command of allowas-in to our BGP configuration. This feature allows a route to be received even if the routers own AS is in path.

PE Routers Configuration Example

interface Ethernet0/1
 ip address 10.0.15.1 255.255.255.0
 ip ospf network point-to-point
 mpls ip
!

router ospf 1
 router-id 1.1.1.1
 network 1.1.1.1 0.0.0.0 area 0
 network 10.0.15.1 0.0.0.0 area 0
!
router bgp 12345
 bgp log-neighbor-changes
 no bgp default ipv4-unicast
 neighbor 5.5.5.5 remote-as 12345
 neighbor 5.5.5.5 update-source Loopback0
 !
 address-family ipv4
 exit-address-family
 !
 address-family vpnv4
  neighbor 5.5.5.5 activate
  neighbor 5.5.5.5 send-community extended
 exit-address-family
 !
!

Internally we are running OSPF to give MPLS all the paths to work with. Every PE device has essentially the same iBGP configuration towards the P router. If you look closely we are not even bringing up the peering relationship with the IPv4 address family. Only the VPNv4 address family will be used. This is due to the BGP peering only being used to distribute the VPNv4 routes across the PE routers.

P/RR Router Configuration Example

interface Loopback0
 ip address 5.5.5.5 255.255.255.255
!
interface Ethernet0/0
 ip address 10.0.15.5 255.255.255.0
 ip ospf network point-to-point
 mpls ip
!
interface Ethernet0/1
 ip address 10.0.25.5 255.255.255.0
 ip ospf network point-to-point
 mpls ip
!
interface Ethernet0/2
 ip address 10.0.35.5 255.255.255.0
 ip ospf network point-to-point
 mpls ip
!
interface Ethernet0/3
 ip address 10.0.45.5 255.255.255.0
 ip ospf network point-to-point
 mpls ip
!
router ospf 1
 router-id 5.5.5.5
 network 0.0.0.0 255.255.255.255 area 0
!
router bgp 12345
 bgp log-neighbor-changes
 no bgp default ipv4-unicast
 neighbor iBGP peer-group
 neighbor iBGP remote-as 12345
 neighbor iBGP update-source Loopback0
 neighbor 1.1.1.1 peer-group iBGP
 neighbor 2.2.2.2 peer-group iBGP
 neighbor 3.3.3.3 peer-group iBGP
 neighbor 4.4.4.4 peer-group iBGP
 !
 address-family ipv4
 exit-address-family
 !
 address-family vpnv4
  neighbor iBGP send-community extended
  neighbor iBGP route-reflector-client
  neighbor 1.1.1.1 activate
  neighbor 2.2.2.2 activate
  neighbor 3.3.3.3 activate
  neighbor 4.4.4.4 activate
 exit-address-family
!

The P router configuration does look a bit more involved but in reality its not too bad. In the case of the P router, I just advertised all the things in OSPF for simplicity. As I mentioned earlier with the PE routers, only the VPNv4 peering will be used. I created a peer group to bring down the number of commands required to make this all work.

Introduction to Nornir

I will break down the structure of the repository I made but I wont go too deep into details. Mainly because there’s a lot of great resources out there to check out (will link at the bottom of the post), and I want to try and keep this post some what short. Nornir is a network automation framework that is written in Python. This allows developers and engineers to build automation and tools with a very powerful programming language. This also allows individuals to easily extend the functionality of the framework to meet their needs.

config.yaml

---
inventory:
  plugin: SimpleInventory
  options:
    host_file: "hosts.yaml"
    group_file: "groups.yaml"
runner:
  plugin: threaded
  options:
    num_workers: 10

The config.yaml file is pretty important in Nornir. In the sample above we are using the SimpleInventory plugin and specifying where the host and group files are located. I am using 10 workers but my lab is fairly small so I could have used less here.

hosts.yaml

---
PE1:
  hostname: "192.168.10.176"
  groups:
    - pe
  data:
    interfaces:
      - name: Ethernet0/0
        vrf: CUSTOMER_789
        ip: 10.0.11.1/24
    bgp:
      neighbors:
        - vrf_name: CUSTOMER_789
          remote_ip: 10.0.11.2
          remote_as: 789

I included the setup for our PE1 router, the rest follow a similar structure. Anything host specific I set at the host.yaml file and anything group specific is set at the groups.yaml file you will see shortly. I assign all PE routers to the pe group and set certain interface parameters as well as BGP information that will be used in the future.

groups.yaml

---
pe:
  platform: ios
  username: cisco
  password: cisco
  data:
    vrfs:
      - name: CUSTOMER_789
        rd: "789:1"
        rt_import: "789:1"
        rt_export: "789:1"

      - name: CUSTOMER_777
        rd: "777:1"
        rt_import: "777:1"
        rt_export: "777:1"

The groups file is pretty neat, since all of my routers are part of the pe group and have a similar platform as well as authentication parameters, I decided to include all of that information under the group. In this automation example I wanted all VRFs to be included on all PE routers. Therefore I also added the VRF information under the group file vs under the host data.

Breaking Down Script

l3vpn_deploy.py snippet

from nornir import InitNornir
from nornir_scrapli.tasks import send_configs
from nornir_jinja2.plugins.tasks import template_file
from nornir_napalm.plugins.tasks import napalm_get
from nornir_utils.plugins.tasks.files import write_file
from nornir_utils.plugins.functions import print_result
from net_utils import address, mask

The portion above is essentially importing any tasks or functions we will need to run Nornir. Whether its importing InitNornir or plugins used to connect to devices and send commands. To learn more about plugins that can be used with Nornir please check out the nornir.tech site.

l3vpn_deploy.py snippet

def main():

    """
    Main that calls l3vpn function
    """

    nornir = InitNornir(config_file="config.yaml")
    print("Nornir initialized with the following hosts:\n")
    for host in nornir.inventory.hosts.keys():
        print(f"{host}\n")

    result = nornir.run(task=l3vpn)

    print_result(result)

This portion of the script is our main function and essentially kicks off all the other portions of our script to execute. Nornir is initialized with our config.yaml file that includes information about our hosts, groups, and connection parameters. There is a simple print statement at the bottom to let the operator know what inventory was just initialized for this job run. We will call on the l3vpn function that is defined towards the top of the script that I will walk through in a bit.

Creating the L3VPNs

We will need the following items on our PE routers to complete our L3VPN build:

  • Customer VRFs created with RD and RTs
  • Assign VRF to interface facing customer router
  • Configure BGP relationship between PE and CE device under new VRF

Now that we have the main portions required for our build, it was just a matter of converting the manual configurations into simple Jinja templates. I will spare you from reading about every task since they all follow the same pattern; create commands from Jinja template, split commands, send commands to PE routers. Here is a snippet below of what I mean.

l3vpn_deploy.py snippet

def l3vpn(task):

    """
    Main function built for L3VPN deployment tasks
    """
    task1_result = task.run(
        name=f"{task.host.name}: Creating VRFs Configuration",
        task=template_file,
        template="vrf.j2",
        path="templates/",
        data=task.host["vrfs"],
    )
    vrf_config = task1_result[0].result

    task2_result = task.run(
        name=f"{task.host.name}: Configuring VRFs on PE Nodes",
        task=send_configs,
        configs=vrf_config.split("\n"),
    )

I name the tasks for clarity when seeing the job output. Initially we run a template_file task that will create commands from a template. Then its a matter of pointing it to our specific template and feeding it data. Once that is done I set the output of that task to a variable called vrf_config, this can then be fed into the next task to push the commands to our PE routers.

{% for vrf in data %}
vrf definition {{ vrf.name }}
 rd {{ vrf.rd }}
 route-target export {{ vrf.rt_export }}
 route-target import {{ vrf.rt_import }}
 !
 address-family ipv4
 exit-address-family
{% endfor %}

Example of the VRF Jinja template above. The rest of the items for L3VPNs use the same structure. Feel free to check out the git repository linked below to see the rest. Ill show the output from a few PE and CE routers before and after the changes.

PE1#show vrf brief
  Name                             Default RD            Protocols   Interfaces
  MGMT                             <not set>             ipv4        Et0/3
PE1#
PE2#show vrf brief
  Name                             Default RD            Protocols   Interfaces
  MGMT                             <not set>             ipv4
PE2#
PE3#show vrf brief
  Name                             Default RD            Protocols   Interfaces
  MGMT                             <not set>             ipv4
PE3#
PE4#show vrf brief
  Name                             Default RD            Protocols   Interfaces
  MGMT                             <not set>             ipv4        Et0/3
PE4#
##### CE Devices #####
CE1#show ip bgp  summary | b Neigh
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
10.0.11.1       4        12345       0       0        1    0    0 00:10:51 Active
CE1#show ip bgp | b Network
     Network          Next Hop            Metric LocPrf Weight Path
 *>  172.16.1.0/24    0.0.0.0                  0         32768 i
CE1#
CE2#show ip bgp summary | b Neigh
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
10.0.22.1       4        12345       0       0        1    0    0 00:13:22 Idle
CE2#show ip bgp | b Network
     Network          Next Hop            Metric LocPrf Weight Path
 *>  172.16.2.0/24    0.0.0.0                  0         32768 i
CE2#
CE3#show ip bgp summary | b Neigh
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
10.0.33.1       4        12345       0       0        1    0    0 never    Active
CE3#show ip bgp | b Network
     Network          Next Hop            Metric LocPrf Weight Path
 *>  172.16.1.0/24    0.0.0.0                  0         32768 i
CE3#
CE4#show ip bgp summary | b Neigh
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
10.0.44.1       4        12345       0       0        1    0    0 never    Active
CE4#show ip bgp | b Network
     Network          Next Hop            Metric LocPrf Weight Path
 *>  172.16.2.0/24    0.0.0.0                  0         32768 i
CE4#

Script Output

(venv) [juliopdx@pbpro auto_mpls_l3vpn]$ time python l3vpn_deploy.py
Nornir initialized with the following hosts:

PE1

PE2

PE3

PE4

l3vpn***************************************************************************
* PE1 ** changed : True ********************************************************
vvvv l3vpn ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- PE1: Creating VRFs Configuration ** changed : False ----------------------- INFO
vrf definition CUSTOMER_789
 rd 789:1
 route-target export 789:1
 route-target import 789:1
 !
 address-family ipv4
 exit-address-family
vrf definition CUSTOMER_777
 rd 777:1
 route-target export 777:1
 route-target import 777:1
 !
 address-family ipv4
 exit-address-family

---- PE1: Configuring VRFs on PE Nodes ** changed : True ----------------------- INFO
vrf definition CUSTOMER_789
 rd 789:1
 route-target export 789:1
 route-target import 789:1
 !
 address-family ipv4
 exit-address-family
vrf definition CUSTOMER_777
 rd 777:1
 route-target export 777:1
 route-target import 777:1
 !
 address-family ipv4
 exit-address-family


---- PE1: Create VRF to Interfaces Configuration ** changed : False ------------ INFO
interface Ethernet0/0
 vrf forwarding CUSTOMER_789
 ip address 10.0.11.1 255.255.255.0
!

---- PE1: Configuring VRFs on Interfaces ** changed : True --------------------- INFO
interface Ethernet0/0
 vrf forwarding CUSTOMER_789
 ip address 10.0.11.1 255.255.255.0
!


---- PE1: Create BGP Neighbor Configuration ** changed : False ----------------- INFO
router bgp 12345
  address-family ipv4 vrf CUSTOMER_789
  neighbor 10.0.11.2 remote-as 789
  neighbor 10.0.11.2 activate
 exit-address-family

---- PE1: Configuring BGP Neighbors under VRFs ** changed : True --------------- INFO
router bgp 12345
  address-family ipv4 vrf CUSTOMER_789
  neighbor 10.0.11.2 remote-as 789
  neighbor 10.0.11.2 activate
 exit-address-family


^^^^ END l3vpn ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* PE2 ** changed : True ********************************************************
vvvv l3vpn ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- PE2: Creating VRFs Configuration ** changed : False ----------------------- INFO
vrf definition CUSTOMER_789
 rd 789:1
 route-target export 789:1
 route-target import 789:1
 !
 address-family ipv4
 exit-address-family
vrf definition CUSTOMER_777
 rd 777:1
 route-target export 777:1
 route-target import 777:1
 !
 address-family ipv4
 exit-address-family

---- PE2: Configuring VRFs on PE Nodes ** changed : True ----------------------- INFO
vrf definition CUSTOMER_789
 rd 789:1
 route-target export 789:1
 route-target import 789:1
 !
 address-family ipv4
 exit-address-family
vrf definition CUSTOMER_777
 rd 777:1
 route-target export 777:1
 route-target import 777:1
 !
 address-family ipv4
 exit-address-family


---- PE2: Create VRF to Interfaces Configuration ** changed : False ------------ INFO
interface Ethernet0/0
 vrf forwarding CUSTOMER_777
 ip address 10.0.22.1 255.255.255.0
!

---- PE2: Configuring VRFs on Interfaces ** changed : True --------------------- INFO
interface Ethernet0/0
 vrf forwarding CUSTOMER_777
 ip address 10.0.22.1 255.255.255.0
!


---- PE2: Create BGP Neighbor Configuration ** changed : False ----------------- INFO
router bgp 12345
  address-family ipv4 vrf CUSTOMER_777
  neighbor 10.0.22.2 remote-as 777
  neighbor 10.0.22.2 activate
 exit-address-family

---- PE2: Configuring BGP Neighbors under VRFs ** changed : True --------------- INFO
router bgp 12345
  address-family ipv4 vrf CUSTOMER_777
  neighbor 10.0.22.2 remote-as 777
  neighbor 10.0.22.2 activate
 exit-address-family


^^^^ END l3vpn ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* PE3 ** changed : True ********************************************************
vvvv l3vpn ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- PE3: Creating VRFs Configuration ** changed : False ----------------------- INFO
vrf definition CUSTOMER_789
 rd 789:1
 route-target export 789:1
 route-target import 789:1
 !
 address-family ipv4
 exit-address-family
vrf definition CUSTOMER_777
 rd 777:1
 route-target export 777:1
 route-target import 777:1
 !
 address-family ipv4
 exit-address-family

---- PE3: Configuring VRFs on PE Nodes ** changed : True ----------------------- INFO
vrf definition CUSTOMER_789
 rd 789:1
 route-target export 789:1
 route-target import 789:1
 !
 address-family ipv4
 exit-address-family
vrf definition CUSTOMER_777
 rd 777:1
 route-target export 777:1
 route-target import 777:1
 !
 address-family ipv4
 exit-address-family


---- PE3: Create VRF to Interfaces Configuration ** changed : False ------------ INFO
interface Ethernet0/0
 vrf forwarding CUSTOMER_777
 ip address 10.0.33.1 255.255.255.0
!

---- PE3: Configuring VRFs on Interfaces ** changed : True --------------------- INFO
interface Ethernet0/0
 vrf forwarding CUSTOMER_777
 ip address 10.0.33.1 255.255.255.0
!


---- PE3: Create BGP Neighbor Configuration ** changed : False ----------------- INFO
router bgp 12345
  address-family ipv4 vrf CUSTOMER_777
  neighbor 10.0.33.2 remote-as 777
  neighbor 10.0.33.2 activate
 exit-address-family

---- PE3: Configuring BGP Neighbors under VRFs ** changed : True --------------- INFO
router bgp 12345
  address-family ipv4 vrf CUSTOMER_777
  neighbor 10.0.33.2 remote-as 777
  neighbor 10.0.33.2 activate
 exit-address-family


^^^^ END l3vpn ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* PE4 ** changed : True ********************************************************
vvvv l3vpn ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- PE4: Creating VRFs Configuration ** changed : False ----------------------- INFO
vrf definition CUSTOMER_789
 rd 789:1
 route-target export 789:1
 route-target import 789:1
 !
 address-family ipv4
 exit-address-family
vrf definition CUSTOMER_777
 rd 777:1
 route-target export 777:1
 route-target import 777:1
 !
 address-family ipv4
 exit-address-family

---- PE4: Configuring VRFs on PE Nodes ** changed : True ----------------------- INFO
vrf definition CUSTOMER_789
 rd 789:1
 route-target export 789:1
 route-target import 789:1
 !
 address-family ipv4
 exit-address-family
vrf definition CUSTOMER_777
 rd 777:1
 route-target export 777:1
 route-target import 777:1
 !
 address-family ipv4
 exit-address-family


---- PE4: Create VRF to Interfaces Configuration ** changed : False ------------ INFO
interface Ethernet0/0
 vrf forwarding CUSTOMER_789
 ip address 10.0.44.1 255.255.255.0
!

---- PE4: Configuring VRFs on Interfaces ** changed : True --------------------- INFO
interface Ethernet0/0
 vrf forwarding CUSTOMER_789
 ip address 10.0.44.1 255.255.255.0
!


---- PE4: Create BGP Neighbor Configuration ** changed : False ----------------- INFO
router bgp 12345
  address-family ipv4 vrf CUSTOMER_789
  neighbor 10.0.44.2 remote-as 789
  neighbor 10.0.44.2 activate
 exit-address-family

---- PE4: Configuring BGP Neighbors under VRFs ** changed : True --------------- INFO
router bgp 12345
  address-family ipv4 vrf CUSTOMER_789
  neighbor 10.0.44.2 remote-as 789
  neighbor 10.0.44.2 activate
 exit-address-family


^^^^ END l3vpn ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

real    0m9.216s
user    0m6.683s
sys     0m1.079s
(venv) [juliopdx@pbpro auto_mpls_l3vpn]$

VRF Creation Validation

PE1#show ip vrf
  Name                             Default RD            Interfaces
  CUSTOMER_777                     777:1
  CUSTOMER_789                     789:1                 Et0/0
  MGMT                             <not set>             Et0/3
PE1#
PE2#show ip vrf
  Name                             Default RD            Interfaces
  CUSTOMER_777                     777:1                 Et0/0
  CUSTOMER_789                     789:1
  MGMT                             <not set>
PE2#
PE3#show ip vrf
  Name                             Default RD            Interfaces
  CUSTOMER_777                     777:1                 Et0/0
  CUSTOMER_789                     789:1
  MGMT                             <not set>
PE3#
PE4#show ip vrf
  Name                             Default RD            Interfaces
  CUSTOMER_777                     777:1
  CUSTOMER_789                     789:1                 Et0/0
  MGMT                             <not set>             Et0/3
PE4#

BGP Peers and Routes on CE Routers

CE1#show ip bgp  summary | b Neigh
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
10.0.11.1       4        12345      19      20        3    0    0 00:13:17        1
CE1#show ip bgp | b Network
     Network          Next Hop            Metric LocPrf Weight Path
 *>  172.16.1.0/24    0.0.0.0                  0         32768 i
 *>  172.16.2.0/24    10.0.11.1                              0 12345 789 i
CE1#traceroute 172.16.2.1 source 172.16.1.1 probe 1 numeric
Type escape sequence to abort.
Tracing the route to 172.16.2.1
VRF info: (vrf in name/id, vrf out name/id)
  1 10.0.11.1 0 msec
  2 10.0.15.5 [MPLS: Labels 300/24 Exp 0] 2 msec
  3 10.0.44.1 [MPLS: Label 24 Exp 0] 2 msec
  4 10.0.44.2 1 msec
CE1#
CE3#show ip bgp summary | b Neigh
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
10.0.33.1       4        12345      25      23        3    0    0 00:17:34        1
CE3#show ip bgp | b Network
     Network          Next Hop            Metric LocPrf Weight Path
 *>  172.16.1.0/24    0.0.0.0                  0         32768 i
 *>  172.16.2.0/24    10.0.33.1                              0 12345 777 i
CE3#traceroute 172.16.2.1 source 172.16.1.1 probe 1 numeric
Type escape sequence to abort.
Tracing the route to 172.16.2.1
VRF info: (vrf in name/id, vrf out name/id)
  1 10.0.33.1 1 msec
  2 10.0.35.5 [MPLS: Labels 302/407 Exp 0] 2 msec
  3 10.0.22.1 [MPLS: Label 407 Exp 0] 2 msec
  4 10.0.22.2 2 msec
CE3#

I think there’s always room for improvement. Even off the top of my head a few additions could be the following:

  • Automated testing
  • Validation with something like pyATS to validate VRF creation on PE routers

I hope you found this a bit useful and maybe inspires you to build something you can use in everyday workload. Feel free to check out the links below to a few resources I found very helpful. Thank you again for reading this far, really means a lot.