Automate Cisco DMVPN Deployment with Ansible

By | June 19, 2018

In this post we will automate the deployment of a Cisco DMVPN network. I use this as a lab for testing, but with a few tweaks you could use this to deploy a production DMVPN network. I’ve tested this in GNS3 on IOSv 15.6 routers and the Network Automation appliance from the GNS3 marketplace, with Ansible v2.5 installed. The DMVPN network is dual-hub, with 3 spokes, using EIGRP as the WAN routing protocol, and based on this Cisco validated design. You can find the all code in my Git repo.

As per best practice we are using Ansible Roles to break the deployment up into smaller chunks. The roles are as follows:

  • acl
  • crypto
  • interfaces
  • hubs
  • spokes
  • test

The ‘ACL’ role applies an access control list to the WAN interface. ‘Crypto’ takes care of the Tunnels, ‘Interfaces’ looks after the interfaces, and the ‘hub’ and ‘spoke’ roles handle configuration that is specific to the device role. The ‘test’ role performs basic ping tests from each router to all of the loopback addresses in the topology. All these roles are called by a single playbook that provisions the whole network, and then tests connectivity. Here’s our playbook:

root@netdev1:~/ansible-dmvpn# cat deploy_dmvpn.yml
---

- name: PLAY 1 - deploy common configuration
  hosts: all
  connection: local
  gather_facts: 'no'

  roles:
    - role: acl
      tags: acl
    - role: crypto
      tags: crypto
    - role: interfaces
      tags: interfaces


- name: PLAY 2 - deploy hub configuration
  hosts: hubs
  connection: local
  gather_facts: 'no'

  roles:
    - role: hubs
      tags: hubs


- name: PLAY 3 - deploy spoke configuration
  hosts: spokes
  connection: local
  gather_facts: 'no'

  roles:
    - role: spokes
      tags: spokes


- name: PLAY 4 - test connectivity
  hosts: all
  connection: local
  gather_facts: 'no'

  roles:
    - role: test
      tags: test

The playbook is run against the inventory file, which looks like this:

root@netdev1:~/ansible-dmvpn# cat inventory
[all:vars]

#SSH connection
ansible_network_os=ios
ansible_user=ansible
ansible_ssh_pass=ansible

#Tunnel PSK
pre_shared_key=cisco123

[hubs]
hub1 ansible_host=192.168.122.100
hub2 ansible_host=192.168.122.101

[spokes]
spoke1 ansible_host=192.168.122.102
spoke2 ansible_host=192.168.122.103
spoke3 ansible_host=192.168.122.104

Before we can use Ansible to connect to the routers, we have to apply a minimal config to bring up the WAN interface in a ‘front door’ VRF and create the Ansible user account. Now we can run our playbook:

root@netdev1:~/ansible-dmvpn# ansible-playbook -i inventory deploy_dmvpn.yml

PLAY [PLAY 1 - deploy common configuration] ************************************

TASK [acl : deploy acl configuration] ******************************************
changed: [spoke3]
changed: [hub2]
changed: [spoke2]
changed: [spoke1]
changed: [hub1]

TASK [crypto : deploy crypto configuration] ************************************
changed: [hub2]
changed: [spoke2]
changed: [spoke1]
changed: [hub1]
changed: [spoke3]

TASK [interfaces : deploy interface configuration] *****************************
changed: [spoke2]
changed: [hub1]
changed: [spoke3]
changed: [spoke1]
changed: [hub2]

PLAY [PLAY 2 - deploy hub configuration] ***************************************

TASK [hubs : deploy hub specific configuration] ********************************
changed: [hub2]
changed: [hub1]

PLAY [PLAY 3 - deploy spoke configuration] *************************************

TASK [spokes : deploy spoke specific configuration] ****************************
changed: [spoke1]
changed: [spoke2]
changed: [spoke3]

PLAY [PLAY 4 - test connectivity] **********************************************

TASK [test : quick test to ping all loopback IP's] *****************************
ok: [hub1] => (item=192.168.123.100)
ok: [hub2] => (item=192.168.123.100)
ok: [spoke2] => (item=192.168.123.100)
ok: [spoke1] => (item=192.168.123.100)
ok: [spoke3] => (item=192.168.123.100)
ok: [hub1] => (item=192.168.123.101)
ok: [hub2] => (item=192.168.123.101)
ok: [spoke1] => (item=192.168.123.101)
ok: [spoke2] => (item=192.168.123.101)
ok: [spoke3] => (item=192.168.123.101)
ok: [hub1] => (item=192.168.123.102)
ok: [hub2] => (item=192.168.123.102)
ok: [spoke1] => (item=192.168.123.102)
ok: [spoke2] => (item=192.168.123.102)
ok: [spoke3] => (item=192.168.123.102)
ok: [hub1] => (item=192.168.123.103)
ok: [spoke1] => (item=192.168.123.103)
ok: [hub2] => (item=192.168.123.103)
ok: [spoke2] => (item=192.168.123.103)
ok: [spoke3] => (item=192.168.123.103)
ok: [hub1] => (item=192.168.123.104)
ok: [spoke1] => (item=192.168.123.104)
ok: [hub2] => (item=192.168.123.104)
ok: [spoke2] => (item=192.168.123.104)
ok: [spoke3] => (item=192.168.123.104)

PLAY RECAP *********************************************************************
hub1                       : ok=5    changed=4    unreachable=0    failed=0
hub2                       : ok=5    changed=4    unreachable=0    failed=0
spoke1                     : ok=5    changed=4    unreachable=0    failed=0
spoke2                     : ok=5    changed=4    unreachable=0    failed=0
spoke3                     : ok=5    changed=4    unreachable=0    failed=0

So, our basic ping test in PLAY 4 proves that the DMVPN is up and running, but if we want to check in more detail we can run another playbook for this and limit the run to only a specific router or subset of routers:

root@netdev1:~/ansible-dmvpn# ansible-playbook -i inventory verify_dmvpn.yml --limit spoke3

PLAY [VERIFY DMVPN] ************************************************************

TASK [CHECK TUNNEL INT | EIGRP | NHRP | DMVPN | IPSEC] *************************
ok: [spoke3]

TASK [debug] *******************************************************************
ok: [spoke3] => {
    "print_output.stdout_lines": [
        [
            "Tunnel10                   10.0.0.104      YES manual up                    up"
        ],
        [
            "EIGRP-IPv4 VR(WAN-DMVPN-1) Address-Family Neighbors for AS(10)",
            "H   Address                 Interface              Hold Uptime   SRTT   RTO  Q  Seq",
            "                                                   (sec)         (ms)       Cnt Num",
            "0   10.0.0.101              Tu10                     54 00:06:56 1378  5000  0  23",
            "1   10.0.0.100              Tu10                     54 00:07:17   66   396  0  12"
        ],
        [
            "IP routing table name is default (0x0)",
            "IP routing table maximum-paths is 32",
            "Route Source    Networks    Subnets     Replicates  Overhead    Memory (bytes)",
            "connected       0           3           0           204         540",
            "static          0           0           0           0           0",
            "application     0           0           0           0           0",
            "nhrp            0           2           0           136         360",
            "eigrp 10        0           4           0           688         720",
            "internal        2                                               880",
            "Total           2           9           0           1028        2500"
        ],
        [
            "10.0.0.100/32 via 10.0.0.100",
            "   Tunnel10 created 00:07:24, never expire ",
            "   Type: static, Flags: used ",
            "   NBMA address: 192.168.122.100 ",
            "10.0.0.101/32 via 10.0.0.101",
            "   Tunnel10 created 00:07:23, never expire ",
            "   Type: static, Flags: used ",
            "   NBMA address: 192.168.122.101 ",
            "10.0.0.102/32 via 10.0.0.102",
            "   Tunnel10 created 00:07:07, expire 00:02:53",
            "   Type: dynamic, Flags: router used nhop rib ",
            "   NBMA address: 192.168.122.102 ",
            "10.0.0.103/32 via 10.0.0.103",
            "   Tunnel10 created 00:07:04, expire 00:02:56",
            "   Type: dynamic, Flags: router used nhop rib ",
            "   NBMA address: 192.168.122.103 ",
            "10.0.0.104/32 via 10.0.0.104",
            "   Tunnel10 created 00:07:06, expire 00:02:56",
            "   Type: dynamic, Flags: router unique local ",
            "   NBMA address: 192.168.122.104 ",
            "    (no-socket) ",
            "192.168.123.101/32 via 10.0.0.101",
            "   Tunnel10 created 00:07:09, expire 00:02:49",
            "   Type: dynamic, Flags: router used rib nho ",
            "   NBMA address: 192.168.122.101 ",
            "192.168.123.102/32 via 10.0.0.102",
            "   Tunnel10 created 00:07:07, expire 00:02:53",
            "   Type: dynamic, Flags: router rib nho ",
            "   NBMA address: 192.168.122.102 ",
            "192.168.123.103/32 via 10.0.0.103",
            "   Tunnel10 created 00:07:04, expire 00:02:56",
            "   Type: dynamic, Flags: router rib nho ",
            "   NBMA address: 192.168.122.103 ",
            "192.168.123.104/32 via 10.0.0.104",
            "   Tunnel10 created 00:07:02, expire 00:02:58",
            "   Type: dynamic, Flags: router unique local ",
            "   NBMA address: 192.168.122.104 ",
            "    (no-socket)"
        ],
        [
            "Interface: Tunnel10, IPv4 NHRP Details ",
            "Type:Spoke, NHRP Peers:4, ",
            "",
            " # Ent  Peer NBMA Addr Peer Tunnel Add State  UpDn Tm Attrb",
            " ----- --------------- --------------- ----- -------- -----",
            "     1 192.168.122.100      10.0.0.100    UP 00:07:22     S",
            "     2 192.168.122.101      10.0.0.101    UP 00:07:21     S",
            "                            10.0.0.101    UP 00:07:23   DT2",
            "     2 192.168.122.102      10.0.0.102    UP 00:07:06   DT1",
            "                            10.0.0.102    UP 00:07:06   DT2",
            "     2 192.168.122.103      10.0.0.103    UP 00:07:03   DT1",
            "                            10.0.0.103    UP 00:07:03   DT2"
        ],
        [
            "Active ISAKMP SA's: 6",
            "Standby ISAKMP SA's: 0",
            "Currently being negotiated ISAKMP SA's: 0",
            "Dead ISAKMP SA's: 0"
        ],
        [
            "IPsec SA total: 16, active: 8, rekeying: 0, unused: 8, invalid: 0"
        ]
    ]
}

PLAY RECAP *********************************************************************
spoke3                     : ok=2    changed=0    unreachable=0    failed=0

I was inspired to write this blog post after reading some of the awesome content on https://techbloc.net/ and https://networkautomationblog.com/

Leave a Reply

Your email address will not be published. Required fields are marked *