58 minutes
Ansible for Networking - Part 5: Arista EOS
The fifth part of my ongoing series of posts on Ansible for Networking will cover Arista’s EOS. You can view the other posts in the series below: -
- Part 1 - Start of the series
- Part 2 - The Lab Environment
- Part 3 - Cisco IOS
- Part 4 - Juniper JunOS
- Part 6 - MikroTik RouterOS
- Part 7 - VyOS
All the playbooks, roles and variables used in this article are available in my Network Automation with Ansible repository
Why EOS?
Arista was formed by former Cisco employees (more information here), all of whom had major involvement in Cisco’s switching portfolio at different points in time. Since their founding in 2004, they have become a very popular option in the data centre switching market, and elsewhere.
Rather than building their own operating system, Arista is based upon Linux (similar to how JunOS is based upon FreeBSD). You can even access the Linux operating system, and run everything from standard Linux applications to Docker containers.
Arista’s CLI is very similar to Cisco. Many Cisco commands are valid on an Arista switch. For this reason, migrating from a Cisco-centric network platform (and associated Ansible playbooks!) to an Arista-centric platform is quite a smooth transition.
Arista offer top-of-rack switching, as well as BGP-speaking edge/transit routers. They often come with high capacity ports (i.e. multiple 10/40/100G ports), as well as a high density of ports.
In my career I have managed a few Arista switches. Other than making sure I was familiar with the Multi-Chassis Link Aggregation syntax (i.e. allowing LACP across multiple switches, rather than one), most of my Cisco knowledge translated instantly.
To make transitioning from another vendor even easier Arista make a virtualised Arista EOS switch available, known as vEOS. It does have come with a few limitations, but nothing which makes it unsuitable for testing or training.
IOS and EOS similarities
Due of their shared heritage, not only are the CLI commands almost identical between EOS and a Cisco IOS, but also the available Ansible modules. Rather than refactoring large sections of playbooks (like we did for JunOS), in many cases the only major differences is using the eos
in place of ios
.
Due to the similarities, instead of explaining each individual task and what it does, most of sections will detail how they differ from the IOS playbooks. If I went into detail explaining each section, this post would look remarkably similar to part 3!
To see the rationale behind the configuration and decisions made, I would recommend reading the Cisco IOS post, and using this post to draw comparisons.
Objectives
For each vendor, I will be using Ansible to configure two routers/switches/firewalls/appliances.
One will serve as the Edge switch, connecting to the Internet and also via BGP to the Net Server. The Net Server is a CentOS 8 Virtual Machine acting as a route server, syslog collector and TACACS+ server (detailed in The Lab Environment)
The other will be an internal switch, performing core functions (i.e. internal routing rather than external).
Edge switch
The edge switch will run the following: -
- External BGP (eBGP) to the Net Server
- Advertising internal networks
- Internal BGP (iBGP) to the Internal switch
- Advertising any routes received from the Net Server
- Advertising a default route (for internet access)
- OSPF
- Advertising loopbacks and internal networks between both switches
- IPv4 and IPv6 routing
- Using OSPFv3 (for IPv6 support)
- Using the IPv6 Address Family for BGP
- SNMPv3 for monitoring
- Logging via Syslog to the Net Server
- Authentication, Authorization and Accounting (AAA) via TACACS+ to the Net Server
- Access Lists on the port facing the Net Server
Usually you would also place some form of access-list
or other filtering on your ports facing the internet, but this is in a lab environment and already behind a firewall.
Internal switch
The internal switch runs a subset of the functions that the edge switch does: -
- Internal BGP (iBGP) to the Edge switch
- Receiving any routes received from the Net Server
- Receiving a default route (for internet access)
- OSPF
- Advertising loopbacks and internal networks between both switches
- IPv4 and IPv6 routing
- Using OSPFv3 (for IPv6 support)
- Using the IPv6 Address Family for BGP
- SNMPv3 for monitoring
- Logging via Syslog to the Net Server
- Authentication, Authorization and Accounting (AAA) via TACACS+ to the Net Server
Prerequisites
To manage an Arista EOS device with Ansible, the following steps are required to allow access to the switches. We also make some changes to the default Ansible connection configuration.
Ansible Configuration
The following defaults are required to use Ansible with Arista EOS: -
ansible_user: ansible
ansible_connection: network_cli
ansible_network_os: eos
ansible_ssh_pass: ###REDACTED###
ansible_become: yes
ansible_become_method: enable
ansible_become_password: ###REDACTED###
This is exactly the same as the Cisco IOS configuration, except the Network OS has changed to eos
.
Arista EOS Configuration
The prerequisites for allowing Ansible access to manage an EOS device are: -
- Create a user with the correct privilege level
- Create an
enable
password - Give the device a
hostname
Password encryption is enabled by default (using SHA512 rather than a proprietary method like Cisco), and SSH is enabled by default too.
Below shows how to do all of the above: -
arista> enable
arista# conf t
Enter configuration commands, one per line. End with CNTL/Z.
! Add the ansible user - Privilege level 15 is equivalent to full admin rights
arista(config)# username ansible privilege 15 secret 0 $PASSWORD-HERE$
! Create the enable password
arista(config)# enable password 0 $ENABLE-PASSWORD$
! Give the device a hostname
arista(config)# hostname arista-01
arista-01(config)# end
! Write your configuration - Important, otherwise you will lose it if the devices powers off or reboots
arista-01# copy running-config startup-config
Once the above is done, add the device into your Ansible inventory. My inventory file looks like the below: -
[arista]
arista-01 ansible_host=10.15.30.43
arista-02 ansible_host=10.15.30.44
Verification
Can we contact both devices?
$ ansible arista -m eos_facts | grep -i hostname
"ansible_net_hostname": "arista-01",
"ansible_net_hostname": "arista-02",
That will do it!
Setup
The setup is similar to the IOS lab. However, I couldn’t use the VLAN Bridge. The Arista vEOS images in my lab were not able to receive frames with tagged VLANs.
I verified (using packet captures) that each vEOS virtual machine sent frames with tagged VLANs, but they would not receive them (presumably being dropped somewhere at the kernel level).
I tried this lab with two different KVM machines, and also attempted it with a Hyper-V hypervisor instance too, with no success. The same setup has worked with every other vendor I have tried so far (including some I haven’t written blogs about yet), so it does appear to be an issue with the vEOS image itself.
Instead of using VLANs, we are using dedicated interfaces (and bridges) between netsvr-01 and arista-01, and between the two arista instances.
VLANs, IP addressing and Autonomous System numbers
The ID chosen for Arista EOS is 03
.
Interfaces
VLANs are not being used, due to limitations with the vEOS image. Instead, physical interfaces are used instead
enp10s0
on netsvr-01 is connected toether1
on the edge switch - netsvr bridgeether2
on arista-01 is connected toether1
on the internal switch - arista bridge
IP Addressing
- IPv4 Subnet on the netsvr bridge:
10.100.103.0/24
- edge switch -
10.100.103.253/24
- netsvr-01 -
10.100.103.254/24
- edge switch -
- IPv4 Subnet on the arista bridge: 10.100.203.0/24
- edge switch -
10.100.203.254/24
- internal switch -
10.100.203.253/24
- edge switch -
- IPv6 Subnet on the netsvr bridge:
2001:db8:103::/64
- edge switch -
2001:db8:103::f/64
- netsvr-01 -
2001:db8:103:ffff/64
- edge switch -
- IPv6 Subnet on the arista bridge: 2001:db8:203::/64
- edge switch -
2001:db8:203::a/64
- internal switch -
2001:db8:203:f/64
- edge switch -
- IPv4 Loopback Addressing
- edge switch -
192.0.2.103/32
- internal switch -
192.0.2.203/32
- edge switch -
- IPv6 Loopback Address
- edge switch -
2001:db8:903:beef::1/128
- internal switch -
2001:db8:903:beef::2/128
- edge switch -
BGP Autonomous System
The BGP Autonomous System number will be AS65103
.
Configuration
In this section we will go through the configuration of the switches. Due to the similarities between Arista and Cisco, much of the configuration is close to identical. Rather than explaining each task individually, I shall go through the major differences.
Also, we have remove the NAT role. This is because the Arista switches (like most switches) do not support Network Address Translation. While I have never managed an Arista router, looking at Arista documentation shows that the configuration is similar to IOS. The only major difference is that NAT is tied to an interface directly, rather than being configured at the global level and then added to an interface with ip nat inside
or ip nat outside
at the interface hierarchical level.
Additionally, while we will configure access-lists, they do not take effect on Arista vEOS. This is because it has no real switch hardware to apply the access-lists to. The configuration would work on a real Arista switch, but in our virtual lab, while we can configure it but it will not actually block/allow traffic.
System Tasks
As noted in previous parts, the system tasks setup basic logging, banners and the hostname.
Playbook
The contents of the playbook are below: -
---
## tasks file for system
- name: Set system hostname
eos_system:
hostname: "{{ inventory_hostname }}"
- name: Remove unneeded banners
eos_banner:
banner: "{{ item }}"
state: absent
loop:
- motd
- name: Update login banner
eos_banner:
banner: login
text: |
----------------------------------------
|
| This banner was generated by Ansible
|
----------------------------------------
|
| You are logged into {{ inventory_hostname }}
|
----------------------------------------
state: present
- name: Configure syslog
eos_logging:
dest: host
name: "{{ log_host }}"
state: present
- name: Set log source
eos_config:
lines:
- logging source-interface Loopback0
- name: Configure log buffer
eos_logging:
dest: buffered
level: informational
size: 5000
state: present
The main differences between this playbook and the Cisco IOS playbook are: -
- The word
ios
is replaced witheos
(e.g.eos_banner
instead ofios_banner
) - We only remove the
motd
banner, as theexec
andincoming
banners are not available on EOS - We set the hostname
- We could have done this in the Cisco playbook as well, using
ios_system
- We could have done this in the Cisco playbook as well, using
- We set the source of logs to be the
Loopback0
interface- This allows logs to come from the same interface, no matter the egress point of traffic
- Setting the log size in
eos_logging
- This is a mandatory setting in the
eos_logging
module
- This is a mandatory setting in the
- As noted previously, password encryption is enabled by default in Arista
While this might seem like a few changes, in practice the differences in the playbooks are quite small: -
$ diff arista/roles/system/tasks/main.yml cisco/roles/system/tasks/main.yml
3,6d2
< - name: Set system hostname
< eos_system:
< hostname: "{{ inventory_hostname }}"
<
8c4
< eos_banner:
---
> ios_banner:
12a9,10
> - exec
> - incoming
15c13
< eos_banner:
---
> ios_banner:
30c28
< eos_logging:
---
> ios_logging:
35,39d32
< - name: Set log source
< eos_config:
< lines:
< - logging source-interface Loopback0
<
41c34
< eos_logging:
---
> ios_logging:
44d36
< size: 5000
45a38,42
>
> - name: Enable service password-encryption
> ios_config:
> lines:
> - service password-encryption
Compared this to the differences you’ll find in the JunOS playbooks, or the upcoming playbooks for MikroTik or Cumulus, this is minimal.
All Ansible modules are referred to and used almost identically to the IOS modules, including the use of parents
for eos_config
(for commands only available in certain hierarchies).
Output
The generated configuration from this playbook is below: -
logging buffered 5000 informational
logging host 10.100.103.254 514
logging source-interface Loopback0
hostname arista-01
banner login
----------------------------------------
|
| This banner was generated by Ansible
|
----------------------------------------
|
| You are logged into arista-01
|
----------------------------------------
EOF
Interfaces
This role configures the interfaces, descriptions and IP addressing (IPv4 and IPv6). We are not able to make use of VLANs due to the lack of VLAN tagging support on the vEOS images (or at least in the setups I have tried), so all references to subinterfaces in our host_vars
have been removed.
Playbook
The contents of the playbook are below: -
- name: Configure subinterfaces first
eos_config:
src: subints.j2
- name: Configure routed ports
eos_config:
lines:
- "no switchport"
parents:
- interface {{ item.eos_if }}
when:
- item.routed is defined
loop: "{{ interfaces }}"
- name: Configure interfaces - Status and Descriptions
eos_interfaces:
config:
- name: "{{ item.eos_if }}"
description: "{{ item.desc }}"
enabled: "{{ item.enabled }}"
loop: "{{ interfaces }}"
- name: Configure interfaces - L3 IPv4
eos_l3_interfaces:
config:
- name: "{{ item.eos_if }}"
ipv4:
- address: "{{ item.ipv4 }}"
when: item.ipv4 is defined
loop: "{{ interfaces }}"
- name: Configure interfaces - L3 IPv6
eos_l3_interfaces:
config:
- name: "{{ item.eos_if }}"
ipv6:
- address: "{{ item.ipv6 }}"
when: item.ipv6 is defined
loop: "{{ interfaces }}"
The differences between this and the IOS interfaces playbook are: -
- Setting ports to routed mode, rather than switched mode
- Switched ports cannot have IPs assigned to them
- The same configuration would be required on a Cisco IOS switch
- The word
ios
is replaced witheos
(e.g.eos_interfaces
instead ofios_interfaces
The actual differences are minimal: -
$ diff arista/roles/system/main.yml cisco/roles/interfaces/tasks/main.yml
1a2
> # tasks file for interfaces
3c4
< eos_config:
---
> ios_config:
6,15d6
< - name: Configure routed ports
< eos_config:
< lines:
< - "no switchport"
< parents:
< - interface {{ item.eos_if }}
< when:
< - item.routed is defined
< loop: "{{ interfaces }}"
<
17c8
< eos_interfaces:
---
> ios_interfaces:
19c10
< - name: "{{ item.eos_if }}"
---
> - name: "{{ item.ios_if }}"
25c16
< eos_l3_interfaces:
---
> ios_l3_interfaces:
27c18
< - name: "{{ item.eos_if }}"
---
> - name: "{{ item.ios_if }}"
34c25
< eos_l3_interfaces:
---
> ios_l3_interfaces:
36c27
< - name: "{{ item.eos_if }}"
---
> - name: "{{ item.ios_if }}"
The routed
variable comes from our host_vars
, which can be seen below: -
interfaces:
- eos_if: "Management1"
desc: "Management"
enabled: true
ipv4: "10.15.30.43/24"
- eos_if: "Ethernet1"
desc: "To netsvr"
enabled: true
routed: true
ipv4: "10.100.103.253/24"
ipv6: "2001:db8:103::f/64"
- eos_if: "Ethernet2"
desc: "To arista-02"
routed: true
enabled: true
ipv4: "10.100.203.254/24"
ipv6: "2001:db8:203::a/64"
- eos_if: "Ethernet3"
desc: "To the Internet"
enabled: true
routed: true
ipv4: "dhcp"
- eos_if: "Loopback0"
desc: "Loopback"
enabled: true
ipv4: "192.0.2.103/32"
ipv6: "2001:db8:903:beef::1/128"
The Management port and Loopback interface do not require the routed
option, as they are routed by default.
If you run this playbook on real hardware, and have access to sub-interfaces, then the below will be relevant: -
$ diff arista/roles/interfaces/templates/subints.j2 cisco/roles/interfaces/templates/subints.j2
4,5c4,5
< interface {{ interface['eos_if'] }}.{{ vlan }}
< encapsulation dot1q vlan {{ vlan }}
---
> interface {{ interface['ios_if'] }}.{{ vlan }}
> encapsulation dot1q {{ vlan }}
Output
The generated configuration looks like the below: -
interface Ethernet1
description To netsvr
no switchport
ip address 10.100.103.253/24
ipv6 address 2001:db8:103::f/64
!
interface Ethernet2
description To arista-02
no switchport
ip address 10.100.203.254/24
ipv6 address 2001:db8:203::a/64
!
interface Ethernet3
description To the Internet
no switchport
ip address dhcp
!
interface Loopback0
description Loopback
ip address 192.0.2.103/32
ipv6 address 2001:db8:903:beef::1/128
!
interface Management1
description Management
ip address 10.15.30.43/24
Again, if you are used to Cisco IOS, this should look very familiar. The only major difference is the use of CIDR notation (i.e. 10.100.103.253/24
) rather than network masks (i.e. 10.100.103.253 255.255.255.0
) for IPv4 addresses.
Verification
arista-01
! Show IPs
rista-01#show ip int brief
Address
Interface IP Address Status Protocol MTU Owner
----------------- ----------------------- ------------ -------------- ----------- -------
Ethernet1 10.100.103.253/24 up up 1500
Ethernet2 10.100.203.254/24 up up 1500
Ethernet3 192.168.122.72/24 up up 1500
Loopback0 192.0.2.103/32 up up 65535
Management1 10.15.30.43/24 up up 1500
arista-01#show ipv6 interface brief
Interface Status MTU IPv6 Address Addr State Addr Source
--------------- ------------ ----------- -------------------------------- ---------------- -----------
Et1 up 1500 fe80::5054:ff:fec5:df3c/64 up link local
2001:db8:103::f/64 up config
Et2 up 1500 fe80::5054:ff:fec5:df3c/64 up link local
2001:db8:203::a/64 up config
Lo0 up 65535 fe80::ff:fe00:0/64 up link local
2001:db8:903:beef::1/128 up config
! Show interface statuses and descriptions
arista-01#show interfaces description
Interface Status Protocol Description
Et1 up up To netsvr
Et2 up up To arista-02
Et3 up up To the Internet
Lo0 up up Loopback
Ma1 up up Management
! Ping to netsvr-01 on IPv4 and IPv6
arista-01#ping 10.100.103.254
PING 10.100.103.254 (10.100.103.254) 72(100) bytes of data.
80 bytes from 10.100.103.254: icmp_seq=1 ttl=64 time=1.62 ms
80 bytes from 10.100.103.254: icmp_seq=2 ttl=64 time=1.08 ms
80 bytes from 10.100.103.254: icmp_seq=3 ttl=64 time=1.19 ms
80 bytes from 10.100.103.254: icmp_seq=4 ttl=64 time=1.01 ms
80 bytes from 10.100.103.254: icmp_seq=5 ttl=64 time=1.53 ms
--- 10.100.103.254 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 8ms
rtt min/avg/max/mdev = 1.010/1.289/1.622/0.244 ms, ipg/ewma 2.017/1.458 ms
arista-01#ping 2001:db8:103::ffff
PING 2001:db8:103::ffff(2001:db8:103::ffff) 72 data bytes
80 bytes from 2001:db8:103::ffff: icmp_seq=1 ttl=64 time=1.53 ms
80 bytes from 2001:db8:103::ffff: icmp_seq=2 ttl=64 time=1.02 ms
80 bytes from 2001:db8:103::ffff: icmp_seq=3 ttl=64 time=1.04 ms
80 bytes from 2001:db8:103::ffff: icmp_seq=4 ttl=64 time=1.06 ms
80 bytes from 2001:db8:103::ffff: icmp_seq=5 ttl=64 time=1.37 ms
--- 2001:db8:103::ffff ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 1.021/1.209/1.539/0.210 ms, ipg/ewma 1.879/1.376 ms
! Ping to cisco-02 on IPv4 and IPv6
arista-01#ping 10.100.203.253
PING 10.100.203.253 (10.100.203.253) 72(100) bytes of data.
80 bytes from 10.100.203.253: icmp_seq=1 ttl=64 time=3.43 ms
80 bytes from 10.100.203.253: icmp_seq=2 ttl=64 time=2.52 ms
80 bytes from 10.100.203.253: icmp_seq=3 ttl=64 time=2.66 ms
80 bytes from 10.100.203.253: icmp_seq=4 ttl=64 time=3.39 ms
80 bytes from 10.100.203.253: icmp_seq=5 ttl=64 time=3.63 ms
--- 10.100.203.253 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 14ms
rtt min/avg/max/mdev = 2.523/3.128/3.630/0.451 ms, ipg/ewma 3.622/3.306 ms
arista-01#ping 2001:db8:203::f
PING 2001:db8:203::f(2001:db8:203::f) 72 data bytes
80 bytes from 2001:db8:203::f: icmp_seq=1 ttl=64 time=8.17 ms
80 bytes from 2001:db8:203::f: icmp_seq=2 ttl=64 time=3.34 ms
80 bytes from 2001:db8:203::f: icmp_seq=3 ttl=64 time=2.88 ms
80 bytes from 2001:db8:203::f: icmp_seq=4 ttl=64 time=2.80 ms
80 bytes from 2001:db8:203::f: icmp_seq=5 ttl=64 time=2.67 ms
--- 2001:db8:203::f ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 30ms
rtt min/avg/max/mdev = 2.676/3.977/8.172/2.110 ms, ipg/ewma 7.608/5.988 ms
For those who use Linux, you’ll probably recognise the layout of the ping
output, as it is exactly the same as on a Linux machine. The Linux underpinnings of Arista do creep through every now and then!
arista-02
! Show IPs
arista-02#show ip interface brief
Address
Interface IP Address Status Protocol MTU Owner
----------------- ----------------------- ------------ -------------- ----------- -------
Ethernet1 10.100.203.253/24 up up 1500
Loopback0 192.0.2.203/32 up up 65535
Management1 10.15.30.44/24 up up 1500
arista-02#show ipv6 interface brief
Interface Status MTU IPv6 Address Addr State Addr Source
--------------- ------------ ----------- -------------------------------- ---------------- -----------
Et1 up 1500 fe80::5054:ff:fed8:9755/64 up link local
2001:db8:203::f/64 up config
Lo0 up 65535 fe80::ff:fe00:0/64 up link local
2001:db8:903:beef::2/128 up config
! Show interface statuses and descriptions
arista-02#show interfaces description
Interface Status Protocol Description
Et1 up up To arista-01
Lo0 up up Loopback
Ma1 up up Management
! Ping to cisco-01 on IPv4 and IPv6
arista-02#ping 10.100.203.254
PING 10.100.203.254 (10.100.203.254) 72(100) bytes of data.
80 bytes from 10.100.203.254: icmp_seq=1 ttl=64 time=3.13 ms
80 bytes from 10.100.203.254: icmp_seq=2 ttl=64 time=2.57 ms
80 bytes from 10.100.203.254: icmp_seq=3 ttl=64 time=2.51 ms
80 bytes from 10.100.203.254: icmp_seq=4 ttl=64 time=2.47 ms
80 bytes from 10.100.203.254: icmp_seq=5 ttl=64 time=3.26 ms
--- 10.100.203.254 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 13ms
rtt min/avg/max/mdev = 2.477/2.793/3.262/0.337 ms, ipg/ewma 3.272/2.972 ms
arista-02#ping 2001:db8:203::a
PING 2001:db8:203::a(2001:db8:203::a) 72 data bytes
80 bytes from 2001:db8:203::a: icmp_seq=1 ttl=64 time=3.18 ms
80 bytes from 2001:db8:203::a: icmp_seq=2 ttl=64 time=2.66 ms
80 bytes from 2001:db8:203::a: icmp_seq=3 ttl=64 time=2.53 ms
80 bytes from 2001:db8:203::a: icmp_seq=4 ttl=64 time=2.67 ms
80 bytes from 2001:db8:203::a: icmp_seq=5 ttl=64 time=3.19 ms
--- 2001:db8:203::a ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 13ms
rtt min/avg/max/mdev = 2.534/2.849/3.195/0.284 ms, ipg/ewma 3.388/3.024 ms
All looking good so far!
Access Lists
As noted earlier, the access-lists applied in this task will be part of the configuration, but will not take effect and therefore will not be able to verify they are correct. If you run these tasks on real Arista hardware though, they should function correctly.
Playbook
The contents of the playbook are below: -
---
## tasks file for acl
- name: Edge Access List - Outbound IPv4
eos_config:
lines:
- 10 permit icmp any any log
- 20 permit udp any host {{ log_host }} eq syslog log
- 30 permit tcp any host {{ tacacs['ipv4'] }} eq tacacs log
- 1000 deny ip any any log
parents:
- ip access-list extended EDGE-OUT
when:
- rtr_role is search("edge")
tags:
- acl
- acl_ipv4
- name: Edge Access List - Inbound IPv4
eos_config:
lines:
- 10 permit icmp any any log
- 20 permit tcp host {{ tacacs['ipv4'] }} eq tacacs any log
- 1000 deny ip any any log
parents:
- ip access-list extended EDGE-IN
when:
- rtr_role is search("edge")
tags:
- acl
- acl_ipv4
- name: Edge Access List - BGP Outbound IPv4
eos_config:
lines:
- "no {{ item.acl.acl_index }}"
- "{{ item.acl.acl_index }} permit tcp any host {{ item.peer }} eq bgp log"
parents:
- ip access-list extended EDGE-OUT
when:
- rtr_role is search("edge")
- bgp is defined
- bgp.neighbors is defined
- bgp.neighbors.ipv4 is defined
- item.acl is defined
loop: "{{ bgp.neighbors.ipv4 }}"
tags:
- acl
- acl_ipv4
- name: Edge Access List - BGP Inbound IPv4
eos_config:
lines:
- "no {{ item.acl.acl_index }}"
- "{{ item.acl.acl_index }} permit tcp host {{ item.peer }} eq bgp any log"
parents:
- ip access-list extended EDGE-IN
when:
- rtr_role is search("edge")
- bgp is defined
- bgp.neighbors is defined
- bgp.neighbors.ipv4 is defined
- item.acl is defined
loop: "{{ bgp.neighbors.ipv4 }}"
tags:
- acl
- acl_ipv4
- name: Apply Inbound IPv4 ACL
eos_config:
lines:
- ip access-group {{ item.acl.ipv4.in }} in
parents:
- interface {{ item.eos_if }}
when:
- item.acl is defined
- item.acl.ipv4 is defined
- item.acl.ipv4.in is defined
loop: "{{ interfaces }}"
tags:
- acl
- acl_ipv4
- name: Apply Outbound IPv4 ACL
eos_config:
lines:
- ip access-group {{ item.acl.ipv4.out }} out
parents:
- interface {{ item.eos_if }}
when:
- item.acl is defined
- item.acl.ipv4 is defined
- item.acl.ipv4.out is defined
loop: "{{ interfaces }}"
tags:
- acl
- acl_ipv4
- name: Edge Access List - Outbound IPv6
eos_config:
lines:
- 10 permit icmp any any log
- 1000 deny ipv6 any any log
parents:
- ipv6 access-list EDGEv6-OUT
when:
- rtr_role is search("edge")
tags:
- acl
- acl_ipv6
- name: Edge Access List - Inbound IPv6
eos_config:
lines:
- 10 permit icmp any any log
- 1000 deny ipv6 any any log
parents:
- ipv6 access-list EDGEv6-IN
when:
- rtr_role is search("edge")
tags:
- acl
- acl_ipv6
- name: Edge Access List - BGP Outbound IPv6
eos_config:
lines:
- "no {{ item.acl.acl_index }}"
- "{{ item.acl.acl_index }} permit tcp any host {{ item.peer }} eq bgp log"
parents:
- ipv6 access-list EDGEv6-OUT
when:
- rtr_role is search("edge")
- bgp is defined
- bgp.neighbors is defined
- bgp.neighbors.ipv6 is defined
- item.acl is defined
loop: "{{ bgp.neighbors.ipv6 }}"
tags:
- acl
- acl_ipv6
- name: Edge Access List - BGP Inbound IPv6
eos_config:
lines:
- "no {{ item.acl.acl_index }}"
- "{{ item.acl.acl_index }} permit tcp host {{ item.peer }} eq bgp any log"
parents:
- ipv6 access-list EDGEv6-IN
when:
- rtr_role is search("edge")
- bgp is defined
- bgp.neighbors is defined
- bgp.neighbors.ipv6 is defined
- item.acl is defined
loop: "{{ bgp.neighbors.ipv6 }}"
tags:
- acl
- acl_ipv6
- name: Apply Inbound IPv6 ACL
eos_config:
lines:
- ipv6 access-group {{ item.acl.ipv6.in }} in
parents:
- interface {{ item.eos_if }}
when:
- item.acl is defined
- item.acl.ipv6 is defined
- item.acl.ipv6.in is defined
loop: "{{ interfaces }}"
tags:
- acl
- acl_ipv6
- name: Apply Outbound IPv6 ACL
eos_config:
lines:
- ipv6 access-group {{ item.acl.ipv6.out }} out
parents:
- interface {{ item.eos_if }}
when:
- item.acl is defined
- item.acl.ipv6 is defined
- item.acl.ipv6.out is defined
loop: "{{ interfaces }}"
tags:
- acl
- acl_ipv6
The differences between this and the IOS playbook are: -
- We are remove existing entries in an access-list, so that we do not have out-of-date configuration
- We do not need to prefix numbered rules in an IPv6 access-list with
sequence
- We use
ipv6 access-group
to apply IPv6 access-lists to an interface, rather thanipv6 traffic-filter
- This is consistent with IPv4 (applied using
ip access-group
)
- This is consistent with IPv4 (applied using
- The word
ios
is replaced witheos
(e.g.eos_config
instead ofios_config
)
The full differences are shown below: -
$ diff arista/roles/acl/tasks/main.yml cisco/roles/acl/tasks/main.yml
< eos_config:
---
> ios_config:
19c19
< eos_config:
---
> ios_config:
33c33
< eos_config:
---
> ios_config:
35d34
< - "no {{ item.acl.acl_index }}"
51c50
< eos_config:
---
> ios_config:
53d51
< - "no {{ item.acl.acl_index }}"
69c67
< eos_config:
---
> ios_config:
73c71
< - interface {{ item.eos_if }}
---
> - interface {{ item.ios_if }}
84c82
< eos_config:
---
> ios_config:
88c86
< - interface {{ item.eos_if }}
---
> - interface {{ item.ios_if }}
99c97
< eos_config:
---
> ios_config:
101,102c99,100
< - 10 permit icmp any any log
< - 1000 deny ipv6 any any log
---
> - sequence 10 permit icmp any any log
> - sequence 1000 deny ipv6 any any log
112c110
< eos_config:
---
> ios_config:
114,115c112,113
< - 10 permit icmp any any log
< - 1000 deny ipv6 any any log
---
> - sequence 10 permit icmp any any log
> - sequence 1000 deny ipv6 any any log
125c123
< eos_config:
---
> ios_config:
127,128c125
< - "no {{ item.acl.acl_index }}"
< - "{{ item.acl.acl_index }} permit tcp any host {{ item.peer }} eq bgp log"
---
> - "sequence {{ item.acl.acl_index }} permit tcp any host {{ item.peer }} eq bgp log"
143c140
< eos_config:
---
> ios_config:
145,146c142
< - "no {{ item.acl.acl_index }}"
< - "{{ item.acl.acl_index }} permit tcp host {{ item.peer }} eq bgp any log"
---
> - "sequence {{ item.acl.acl_index }} permit tcp host {{ item.peer }} eq bgp any log"
161c157
< eos_config:
---
> ios_config:
163c159
< - ipv6 access-group {{ item.acl.ipv6.in }} in
---
> - ipv6 traffic-filter {{ item.acl.ipv6.in }} in
165c161
< - interface {{ item.eos_if }}
---
> - interface {{ item.ios_if }}
176c172
< eos_config:
---
> ios_config:
178c174
< - ipv6 access-group {{ item.acl.ipv6.out }} out
---
> - ipv6 traffic-filter {{ item.acl.ipv6.out }} out
180c176
< - interface {{ item.eos_if }}
---
> - interface {{ item.ios_if }}
The below is the relevant section in our host_vars
: -
bgp:
neighbors:
ipv4:
- peer: 10.100.103.254
acl:
acl_index: 110
ipv6:
- peer: "2001:db8:103::ffff"
acl:
acl_index: 110
interfaces:
- eos_if: "Ethernet1"
acl:
ipv4:
in: EDGE-IN
out: EDGE-OUT
ipv6:
in: EDGEv6-IN
out: EDGEv6-OUT
The access-lists create rules based upon BGP peers, syslog and tacacs.
Generated configuration
The configuration the above generates is below: -
interface Ethernet1
ip access-group EDGE-IN in
ip access-group EDGE-OUT out
ipv6 access-group EDGEv6-IN in
ipv6 access-group EDGEv6-OUT out
!
ipv6 access-list EDGEv6-IN
10 permit icmpv6 any any log
110 permit tcp host 2001:db8:103::ffff eq bgp any log
1000 deny ipv6 any any log
!
ipv6 access-list EDGEv6-OUT
10 permit icmpv6 any any log
110 permit tcp any host 2001:db8:103::ffff eq bgp log
1000 deny ipv6 any any log
!
ip access-list EDGE-IN
10 permit icmp any any log
20 permit tcp host 192.0.2.1 eq tacacs any log
110 permit tcp host 10.100.103.254 eq bgp any log
1000 deny ip any any log
!
ip access-list EDGE-OUT
10 permit icmp any any log
20 permit udp any host 10.100.103.254 eq syslog log
30 permit tcp any host 192.0.2.1 eq tacacs log
110 permit tcp any host 10.100.103.254 eq bgp log
1000 deny ip any any log
!
If you apply this on a vEOS image, the access-lists will be created. However you will see these error messages when applying the access-group
configuration: -
arista-01(config)#interface Ethernet1
arista-01(config-if-Et1)# ip access-group EDGE-IN in
% Unavailable command (not supported on this hardware platform)
arista-01(config-if-Et1)# ip access-group EDGE-OUT out
% Unavailable command (not supported on this hardware platform)
arista-01(config-if-Et1)# ipv6 access-group EDGEv6-IN in
% Unavailable command (not supported on this hardware platform)
arista-01(config-if-Et1)# ipv6 access-group EDGEv6-OUT out
% Unavailable command (not supported on this hardware platform)
Routing
This section is where we will configure BGP, OSPF and OSPFv3. As per the previous parts, we are using OSPF (for IPv4 routing) and OSPFv3 (for IPv6 routing) because not all vendors support IPv4 in OSPFv3. If you are targeting an Arista-only network, or deemed IPv4 support in OSPFv3 an acceptance criteria on any network hardware choices, then you could remove the need for OSPF.
Main Playbook
The main playbook looks like the below: -
---
## tasks file for routing
##
- name: Include OSPF routing
include: ospf.yml
- name: Include OSPFv3 routing
include: ospfv3.yml
- name: Include BGP routing
include: bgp.yml
This is identical to the IOS playbook, as we are not doing anything except including other playbooks (for ease of reading/debugging).
OSPF Playbook
The contents of the OSPF playbook are below: -
---
## tasks file for routing
##
- name: Enable IPv4 routing
eos_config:
lines:
- "ip routing"
tags:
- ospf
- name: OSPF Process
eos_config:
lines:
- "router ospf 1"
tags:
- ospf
- name: OSPF Process - Router ID
eos_config:
lines:
- "router-id {{ router_id }}"
parents: router ospf 1
tags:
- ospf
- name: OSPF Interfaces
eos_config:
lines:
- ip ospf area {{ item.ospf.area }}
parents: interface {{ item.eos_if }}
when: item.ospf is defined
loop: "{{ interfaces }}"
tags:
- ospf
- name: OSPF Interfaces - Passive
eos_config:
lines:
- passive-interface {{ item.eos_if }}
parents: router ospf 1
when:
- item.ospf is defined
- item.ospf.passive is defined
loop: "{{ interfaces }}"
tags:
- ospf
There are two major differences with this playbook and the IOS playbook: -
- We enable
ip routing
- We are emulating an Arista switch, and many switches tend to have routing disabled by default
- We do not set a process ID in the
ip ospf area
command
The reason for the latter is because Cisco allows multiple OSPF processes in the default routing table. By contrast, Arista do not. Arista allow multiple OSPF processes, but they must be configured as part of different VRFs (Virtual Routing and Forwarding). VRFs are used to create separate and unique routing tables, with different interfaces (or subinterfaces) bound to them. This makes it so each customer can have their own private routing table, with different egress and ingress points to their network, while still running over the same physical core network as each other.
Besides that, all other details are the same.
The below host_vars
are relevant to the above playbook: -
- eos_if: "Ethernet1"
ospf:
area: "0.0.0.0"
passive: true
ospfv3:
area: "0.0.0.0"
passive: true
- eos_if: "Ethernet2"
ospf:
area: "0.0.0.0"
ospfv3:
area: "0.0.0.0"
- eos_if: "Loopback0"
ospf:
area: "0.0.0.0"
passive: true
ospfv3:
area: "0.0.0.0"
passive: true
Generated configuration
The configuration this generates is below: -
interface Ethernet1
ip ospf area 0.0.0.0
!
interface Ethernet2
ip ospf area 0.0.0.0
!
interface Loopback0
ip ospf area 0.0.0.0
!
router ospf 1
router-id 192.0.2.103
passive-interface Ethernet1
passive-interface Loopback0
!
Verification
arista-01
! Show OSPF interfaces
arista-01#show ip ospf interface brief
Interface Instance VRF Area IP Address Cost State Nbrs
Et2 1 default 0.0.0.0 10.100.203.254/24 10 Backup DR 1
Lo0 1 default 0.0.0.0 192.0.2.103/32 10 DR 0
Et1 1 default 0.0.0.0 10.100.103.253/24 10 DR 0
! Show OSPF neighbours
arista-01#show ip ospf neighbor
Neighbor ID Instance VRF Pri State Dead Time Address Interface
192.0.2.203 1 default 1 FULL/DR 00:00:29 10.100.203.253 Ethernet2
! Show routing table
arista-01#show ip route ospf
VRF: default
Codes: C - connected, S - static, K - kernel,
O - OSPF, IA - OSPF inter area, E1 - OSPF external type 1,
E2 - OSPF external type 2, N1 - OSPF NSSA external type 1,
N2 - OSPF NSSA external type2, B - BGP, B I - iBGP, B E - eBGP,
R - RIP, I L1 - IS-IS level 1, I L2 - IS-IS level 2,
O3 - OSPFv3, A B - BGP Aggregate, A O - OSPF Summary,
NG - Nexthop Group Static Route, V - VXLAN Control Service,
DH - DHCP client installed default route, M - Martian,
DP - Dynamic Policy Route, L - VRF Leaked
O 192.0.2.203/32 [110/20] via 10.100.203.253, Ethernet2
! Ping!
arista-01#ping 192.0.2.203
PING 192.0.2.203 (192.0.2.203) 72(100) bytes of data.
80 bytes from 192.0.2.203: icmp_seq=1 ttl=64 time=3.54 ms
80 bytes from 192.0.2.203: icmp_seq=2 ttl=64 time=2.57 ms
80 bytes from 192.0.2.203: icmp_seq=3 ttl=64 time=2.57 ms
80 bytes from 192.0.2.203: icmp_seq=4 ttl=64 time=2.87 ms
80 bytes from 192.0.2.203: icmp_seq=5 ttl=64 time=2.92 ms
--- 192.0.2.203 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 14ms
rtt min/avg/max/mdev = 2.574/2.898/3.549/0.362 ms, ipg/ewma 3.525/3.222 ms
arista-01#ping 192.0.2.203 source 192.0.2.103
PING 192.0.2.203 (192.0.2.203) from 192.0.2.103 : 72(100) bytes of data.
80 bytes from 192.0.2.203: icmp_seq=1 ttl=64 time=3.04 ms
80 bytes from 192.0.2.203: icmp_seq=2 ttl=64 time=2.60 ms
80 bytes from 192.0.2.203: icmp_seq=3 ttl=64 time=2.58 ms
80 bytes from 192.0.2.203: icmp_seq=4 ttl=64 time=2.88 ms
80 bytes from 192.0.2.203: icmp_seq=5 ttl=64 time=3.23 ms
--- 192.0.2.203 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 14ms
rtt min/avg/max/mdev = 2.583/2.871/3.235/0.252 ms, ipg/ewma 3.524/2.971 ms
arista-02
! Show OSPF interfaces
arista-02#show ip ospf interface brief
Interface Instance VRF Area IP Address Cost State Nbrs
Lo0 1 default 0.0.0.0 192.0.2.203/32 10 DR 0
Et1 1 default 0.0.0.0 10.100.203.253/24 10 DR 1
! Show OSPF neighbours
arista-02#show ip ospf neighbor
Neighbor ID Instance VRF Pri State Dead Time Address Interface
192.0.2.103 1 default 1 FULL/BDR 00:00:32 10.100.203.254 Ethernet1
! Show routing table
arista-02#show ip route ospf
VRF: default
Codes: C - connected, S - static, K - kernel,
O - OSPF, IA - OSPF inter area, E1 - OSPF external type 1,
E2 - OSPF external type 2, N1 - OSPF NSSA external type 1,
N2 - OSPF NSSA external type2, B - BGP, B I - iBGP, B E - eBGP,
R - RIP, I L1 - IS-IS level 1, I L2 - IS-IS level 2,
O3 - OSPFv3, A B - BGP Aggregate, A O - OSPF Summary,
NG - Nexthop Group Static Route, V - VXLAN Control Service,
DH - DHCP client installed default route, M - Martian,
DP - Dynamic Policy Route, L - VRF Leaked
O 10.100.103.0/24 [110/20] via 10.100.203.254, Ethernet1
O 192.0.2.103/32 [110/20] via 10.100.203.254, Ethernet1
! Ping!
arista-02#ping 192.0.2.103
PING 192.0.2.103 (192.0.2.103) 72(100) bytes of data.
80 bytes from 192.0.2.103: icmp_seq=1 ttl=64 time=3.23 ms
80 bytes from 192.0.2.103: icmp_seq=2 ttl=64 time=2.55 ms
80 bytes from 192.0.2.103: icmp_seq=3 ttl=64 time=2.61 ms
80 bytes from 192.0.2.103: icmp_seq=4 ttl=64 time=2.75 ms
80 bytes from 192.0.2.103: icmp_seq=5 ttl=64 time=3.12 ms
--- 192.0.2.103 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 13ms
rtt min/avg/max/mdev = 2.550/2.854/3.233/0.279 ms, ipg/ewma 3.352/3.050 ms
arista-02#ping 192.0.2.103 source lo0
PING 192.0.2.103 (192.0.2.103) from 192.0.2.203 : 72(100) bytes of data.
80 bytes from 192.0.2.103: icmp_seq=1 ttl=64 time=3.13 ms
80 bytes from 192.0.2.103: icmp_seq=2 ttl=64 time=2.50 ms
80 bytes from 192.0.2.103: icmp_seq=3 ttl=64 time=2.59 ms
80 bytes from 192.0.2.103: icmp_seq=4 ttl=64 time=2.78 ms
80 bytes from 192.0.2.103: icmp_seq=5 ttl=64 time=3.18 ms
--- 192.0.2.103 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 13ms
rtt min/avg/max/mdev = 2.505/2.842/3.188/0.277 ms, ipg/ewma 3.291/3.001 ms
arista-02#ping 10.100.103.253 source lo0
PING 10.100.103.253 (10.100.103.253) from 192.0.2.203 : 72(100) bytes of data.
80 bytes from 10.100.103.253: icmp_seq=1 ttl=64 time=3.26 ms
80 bytes from 10.100.103.253: icmp_seq=2 ttl=64 time=2.75 ms
80 bytes from 10.100.103.253: icmp_seq=3 ttl=64 time=2.76 ms
80 bytes from 10.100.103.253: icmp_seq=4 ttl=64 time=4.98 ms
80 bytes from 10.100.103.253: icmp_seq=5 ttl=64 time=3.42 ms
--- 10.100.103.253 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 15ms
rtt min/avg/max/mdev = 2.759/3.440/4.983/0.815 ms, ipg/ewma 3.841/3.384 ms
All looking good!
OSPFv3 Playbook
As noted, we run OSPFv3 for IPv6 routing. It is technically capable of IPv4 routing to, but not all vendors support this.
The contents of the playbook are below: -
---
## tasks file for routing
##
- name: Enable IPv6 routing
eos_config:
lines:
- "ipv6 unicast-routing"
tags:
- ospfv3
- name: OSPFv3 Process
eos_config:
lines:
- "router ospfv3"
tags:
- ospfv3
- name: OSPFv3 Process - Router ID
eos_config:
lines:
- "router-id {{ router_id }}"
parents: router ospfv3
tags:
- ospfv3
- name: OSPFv3 Interfaces
eos_config:
lines:
- ospf ipv6 area {{ item.ospfv3.area }}
parents: interface {{ item.eos_if }}
when: item.ospfv3 is defined
loop: "{{ interfaces }}"
tags:
- ospfv3
- name: OSPFv3 Interfaces - Passive
eos_config:
lines:
- ospf ipv6 passive-interface
parents: interface {{ item.eos_if }}
when:
- item.ospfv3 is defined
- item.ospfv3.passive is defined
loop: "{{ interfaces }}"
tags:
- ospfv3
- name: Removed IPv4 address family
eos_config:
lines:
- "no address-family ipv4"
parents: router ospfv3
tags:
- ospfv3
There are some small differences in this playbook from the IOS playbook: -
- No OSPF process ID is specified anywhere in Arista (for the same reasons as IPv4)
ospf ipv6 area
is used under an interface rather thanipv6 ospf 1 area
- Passive interface configuration is at the interface level, rather than the process level (
parents: interface
rather thanparents: router ospfv3 1
) no address-family ipv4
disables the IPv4 address family, theunicast
option is not required
The differences between the playbooks themselves are below: -
diff arista/roles/routing/tasks/ospfv3.yml cisco/roles/routing/tasks/ospfv3.yml
6c6
< eos_config:
---
> ios_config:
9,10d8
< tags:
< - ospfv3
13c11
< eos_config:
---
> ios_config:
15,17c13
< - "router ospfv3"
< tags:
< - ospfv3
---
> - "router ospfv3 1"
20c16
< eos_config:
---
> ios_config:
23,25c19
< parents: router ospfv3
< tags:
< - ospfv3
---
> parents: router ospfv3 1
28c22
< eos_config:
---
> ios_config:
30,31c24,25
< - ospf ipv6 area {{ item.ospfv3.area }}
< parents: interface {{ item.eos_if }}
---
> - ipv6 ospf 1 area {{ item.ospfv3.area }}
> parents: interface {{ item.ios_if }}
34,35d27
< tags:
< - ospfv3
38c30
< eos_config:
---
> ios_config:
40,41c32,33
< - ospf ipv6 passive-interface
< parents: interface {{ item.eos_if }}
---
> - passive-interface {{ item.ios_if }}
> parents: router ospfv3 1
46,47d37
< tags:
< - ospfv3
50c40
< eos_config:
---
> ios_config:
52,55c42,43
< - "no address-family ipv4"
< parents: router ospfv3
< tags:
< - ospfv3
---
> - "no address-family ipv4 unicast"
> parents: router ospfv3 1
In some ways I prefer this configuration. All of the OSPF configuration for an interface is grouped together, rather than partly under the process, partly under the interface.
The relevant host_vars
are below: -
- eos_if: "Ethernet1"
ospf:
area: "0.0.0.0"
passive: true
ospfv3:
area: "0.0.0.0"
passive: true
- eos_if: "Ethernet2"
ospf:
area: "0.0.0.0"
ospfv3:
area: "0.0.0.0"
- eos_if: "Loopback0"
ospf:
area: "0.0.0.0"
passive: true
ospfv3:
area: "0.0.0.0"
passive: true
Generated configuration
The generated configuration is below: -
interface Ethernet1
ospfv3 ipv6 passive-interface
ospfv3 ipv6 area 0.0.0.0
!
interface Ethernet2
ospfv3 ipv6 area 0.0.0.0
!
interface Loopback0
ospfv3 ipv6 passive-interface
ospfv3 ipv6 area 0.0.0.0
!
router ospfv3
router-id 192.0.2.103
!
address-family ipv6
Arista appears to translate the ospf ipv6
command to ospfv3 ipv6
, so you could change the playbook to this style instead.
Verification
arista-01
! Show OSPF interfaces (no brief option available)
arista-01# show ospfv3 interface
OSPFv3 address-family ipv6
Ethernet2 is up
Interface Address fe80::5054:ff:fec5:df3c, VRF default, Area 0.0.0.0
Network Type Broadcast, Cost 10
Transmit Delay is 1 sec, State Backup DR, Priority 1
Designated Router is 192.0.2.203
Backup Designated Router is 192.0.2.103
Timer intervals configured, Hello 10, Dead 40, Retransmit 5
Neighbor Count is 1
Options are R E V6
Ethernet1 is up
Interface Address fe80::5054:ff:fec5:df3c, VRF default, Area 0.0.0.0
Network Type Broadcast, Cost 10
Transmit Delay is 1 sec, State DR, Priority 1
Designated Router is 192.0.2.103
Backup Designated Router is 0.0.0.0
Timer intervals configured, Hello 10, Dead 40, Retransmit 5
Neighbor Count is 0 (Passive Interface)
Options are R E V6
Loopback0 is up
Interface Address unassigned, VRF default, Area 0.0.0.0
Network Type Broadcast, Cost 10
Transmit Delay is 1 sec, State DR, Priority 1
Designated Router is 192.0.2.103
Backup Designated Router is 0.0.0.0
Timer intervals configured, Hello 10, Dead 40, Retransmit 5
Neighbor Count is 0 (Passive Interface)
Options are R E V6
! Show OSPF neighbours
arista-01#show ipv6 ospf neighbor
Routing Process "ospf 0":
Neighbor 192.0.2.203 VRF default priority is 1, state is Full
In area 0.0.0.0 interface Ethernet2
DR is 192.0.2.203 BDR is 192.0.2.103
Options is R E V6
Dead timer is due in 29 seconds
Graceful-restart-helper mode is Inactive
Graceful-restart attempts: 0
! Show routing table
arista-01#show ipv6 route ospf
VRF: default
Displaying 1 of 11 IPv6 routing table entries
Codes: C - connected, S - static, K - kernel, O3 - OSPFv3, B - BGP, R - RIP, A B - BGP Aggregate, I L1 - IS-IS level 1, I L2 - IS-IS level 2, DH - DHCP, NG - Nexthop Group Static Route, M - Martian, DP - Dynamic Policy Route, L - VRF Leaked
O3 2001:db8:903:beef::2/128 [110/20]
via fe80::5054:ff:fed8:9755, Ethernet2
! Ping!
arista-01#ping 2001:db8:903:beef::2
PING 2001:db8:903:beef::2(2001:db8:903:beef::2) 72 data bytes
80 bytes from 2001:db8:903:beef::2: icmp_seq=1 ttl=64 time=7.31 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=2 ttl=64 time=2.62 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=3 ttl=64 time=2.55 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=4 ttl=64 time=2.54 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=5 ttl=64 time=2.51 ms
--- 2001:db8:903:beef::2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 27ms
rtt min/avg/max/mdev = 2.513/3.509/7.310/1.901 ms, ipg/ewma 6.827/5.341 ms
arista-01#ping 2001:db8:903:beef::2 source lo0
PING 2001:db8:903:beef::2(2001:db8:903:beef::2) from 2001:db8:903:beef::1 : 72 data bytes
80 bytes from 2001:db8:903:beef::2: icmp_seq=1 ttl=64 time=3.02 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=2 ttl=64 time=2.39 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=3 ttl=64 time=2.44 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=4 ttl=64 time=2.67 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=5 ttl=64 time=3.12 ms
--- 2001:db8:903:beef::2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 13ms
rtt min/avg/max/mdev = 2.393/2.732/3.121/0.297 ms, ipg/ewma 3.388/2.890 ms
arista-02
! Show OSPF interfaces
arista-02#show ipv6 ospf interface
Ethernet1 is up
Interface Address fe80::5054:ff:fed8:9755, VRF default, Area 0.0.0.0
Network Type Broadcast, Cost 10
Transmit Delay is 1 sec, State DR, Priority 1
Designated Router is 192.0.2.203
Backup Designated Router is 192.0.2.103
Timer intervals configured, Hello 10, Dead 40, Retransmit 5
Neighbor Count is 1
Options are R E V6
Loopback0 is up
Interface Address unassigned, VRF default, Area 0.0.0.0
Network Type Broadcast, Cost 10
Transmit Delay is 1 sec, State DR, Priority 1
Designated Router is 192.0.2.203
Backup Designated Router is 0.0.0.0
Timer intervals configured, Hello 10, Dead 40, Retransmit 5
Neighbor Count is 0 (Passive Interface)
Options are R E V6
! Show OSPF neighbours
arista-02#show ipv6 ospf neighbor
Routing Process "ospf 0":
Neighbor 192.0.2.103 VRF default priority is 1, state is Full
In area 0.0.0.0 interface Ethernet1
DR is 192.0.2.203 BDR is 192.0.2.103
Options is R E V6
Dead timer is due in 31 seconds
Graceful-restart-helper mode is Inactive
Graceful-restart attempts: 0
! Show routing table
arista-02#show ipv6 route ospf
VRF: default
Displaying 2 of 10 IPv6 routing table entries
Codes: C - connected, S - static, K - kernel, O3 - OSPFv3, B - BGP, R - RIP, A B - BGP Aggregate, I L1 - IS-IS level 1, I L2 - IS-IS level 2, DH - DHCP, NG - Nexthop Group Static Route, M - Martian, DP - Dynamic Policy Route, L - VRF Leaked
O3 2001:db8:103::/64 [110/20]
via fe80::5054:ff:fec5:df3c, Ethernet1
O3 2001:db8:903:beef::1/128 [110/20]
via fe80::5054:ff:fec5:df3c, Ethernet1
! Ping!
arista-02#ping 2001:db8:903:beef::1
PING 2001:db8:903:beef::1(2001:db8:903:beef::1) 72 data bytes
80 bytes from 2001:db8:903:beef::1: icmp_seq=1 ttl=64 time=6.94 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=2 ttl=64 time=2.67 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=3 ttl=64 time=2.64 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=4 ttl=64 time=2.64 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=5 ttl=64 time=2.47 ms
--- 2001:db8:903:beef::1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 25ms
rtt min/avg/max/mdev = 2.474/3.477/6.946/1.736 ms, ipg/ewma 6.387/5.147 ms
arista-02#ping 2001:db8:903:beef::1 source lo0
PING 2001:db8:903:beef::1(2001:db8:903:beef::1) from 2001:db8:903:beef::2 : 72 data bytes
80 bytes from 2001:db8:903:beef::1: icmp_seq=1 ttl=64 time=3.17 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=2 ttl=64 time=2.61 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=3 ttl=64 time=2.48 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=4 ttl=64 time=2.47 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=5 ttl=64 time=3.59 ms
--- 2001:db8:903:beef::1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 13ms
rtt min/avg/max/mdev = 2.476/2.868/3.592/0.447 ms, ipg/ewma 3.267/3.039 ms
arista-02#ping 2001:db8:103::f
PING 2001:db8:103::f(2001:db8:103::f) 72 data bytes
80 bytes from 2001:db8:103::f: icmp_seq=1 ttl=64 time=3.10 ms
80 bytes from 2001:db8:103::f: icmp_seq=2 ttl=64 time=2.51 ms
80 bytes from 2001:db8:103::f: icmp_seq=3 ttl=64 time=2.47 ms
80 bytes from 2001:db8:103::f: icmp_seq=4 ttl=64 time=2.99 ms
80 bytes from 2001:db8:103::f: icmp_seq=5 ttl=64 time=3.26 ms
--- 2001:db8:103::f ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 13ms
rtt min/avg/max/mdev = 2.476/2.872/3.269/0.322 ms, ipg/ewma 3.308/3.002 ms
Unfortunately the IPv6 verification commands for OSPF are a little on the verbose side, with no brief
options available. It is nice to see the extra information, but it makes it harder to quickly glance at the output to see a status for all neighbours and interfaces.
Other than though, everything is looking good!
BGP Playbook
We are using BGP to communicate with netsvr-01, as well as advertising routes received from netsvr-01 from the edge switch to the internal switch.
The contents of the playbook are: -
---
## tasks file for routing
##
- name: Configure BGP - eBGP v4 peers
eos_bgp:
config:
bgp_as: "{{ bgp['local_as'] }}"
log_neighbor_changes: True
router_id: "{{ router_id }}"
neighbors:
- neighbor: "{{ item.peer }}"
remote_as: "{{ item.remote_as }}"
when:
- bgp is defined
- bgp.neighbors is defined
- bgp.neighbors.ipv4 is defined
- item.ebgp is defined
loop: "{{ bgp.neighbors.ipv4 }}"
tags:
- bgp
- bgp_v4
- name: Configure BGP - eBGP v6 peers
eos_bgp:
config:
bgp_as: "{{ bgp['local_as'] }}"
log_neighbor_changes: True
router_id: "{{ router_id }}"
neighbors:
- neighbor: "{{ item.peer }}"
remote_as: "{{ item.remote_as }}"
address_family:
- afi: ipv6
neighbors:
- neighbor: "{{ item.peer }}"
activate: true
when:
- bgp is defined
- bgp.neighbors is defined
- bgp.neighbors.ipv6 is defined
- item.ebgp is defined
loop: "{{ bgp.neighbors.ipv6 }}"
tags:
- bgp
- bgp_v6
- name: Configure BGP - iBGP v4 peers
eos_bgp:
config:
bgp_as: "{{ bgp['local_as'] }}"
log_neighbor_changes: True
router_id: "{{ router_id }}"
neighbors:
- neighbor: "{{ item.peer }}"
remote_as: "{{ item.remote_as }}"
update_source: "{{ item.update_source }}"
when:
- bgp is defined
- bgp.neighbors is defined
- bgp.neighbors.ipv4 is defined
- item.ibgp is defined
loop: "{{ bgp.neighbors.ipv4 }}"
tags:
- bgp
- bgp_v4
- name: Configure BGP - iBGP v6 peers
eos_bgp:
config:
bgp_as: "{{ bgp['local_as'] }}"
log_neighbor_changes: True
router_id: "{{ router_id }}"
neighbors:
- neighbor: "{{ item.peer }}"
remote_as: "{{ item.remote_as }}"
update_source: "{{ item.update_source }}"
address_family:
- afi: ipv6
neighbors:
- neighbor: "{{ item.peer }}"
activate: true
when:
- bgp is defined
- bgp.neighbors is defined
- bgp.neighbors.ipv6 is defined
- item.ibgp is defined
loop: "{{ bgp.neighbors.ipv6 }}"
tags:
- bgp
- bgp_v6
- name: Configure BGP - Redistribute OSPF
eos_bgp:
config:
bgp_as: "{{ bgp['local_as'] }}"
redistribute:
- protocol: ospf
- protocol: connected
when:
- bgp is defined
- bgp.redist is defined
- bgp.redist.ospf is defined
tags:
- bgp
- bgp_v4
- name: Configure BGP - Redistribute OSPFv3
eos_bgp:
config:
bgp_as: "{{ bgp['local_as'] }}"
address_family:
- afi: ipv6
redistribute:
- protocol: ospf
when:
- bgp is defined
- bgp.redist is defined
- bgp.redist.ospfv3 is defined
tags:
- bgp
- bgp_v6
- name: Configure BGP - iBGP v4 Default Originate
eos_config:
lines:
- neighbor {{ item.peer }} default-originate
parents:
- router bgp {{ bgp['local_as'] }}
when:
- bgp is defined
- bgp.neighbors is defined
- bgp.neighbors.ipv4 is defined
- item.ibgp is defined
- item.default_originate is defined
loop: "{{ bgp.neighbors.ipv4 }}"
tags:
- bgp
- bgp_v4
The main differences here are: -
- Arista does not support the
include-connected
statement when redistributing OSPF - We redistribute connected routes, due to the above
- Most of the IPv4-specific statements do not go under
address-family ipv4
(e.g. redistribution)
Other than that, there are no major differences: -
< eos_bgp:
---
> ios_bgp:
24c24
< eos_bgp:
---
> ios_bgp:
48c48
< eos_bgp:
---
> ios_bgp:
68c68
< eos_bgp:
---
> ios_bgp:
92,93c92,93
< - name: Configure BGP - Redistribute OSPF and connected routes
< eos_bgp:
---
> - name: Configure BGP - Redistribute OSPF
> ios_bgp:
96,98c96,100
< redistribute:
< - protocol: ospf
< - protocol: connected
---
> address_family:
> - afi: ipv4
> redistribute:
> - protocol: ospf
> id: 1
107,108c109,110
< - name: Configure BGP - Redistribute OSPFv3 and connected routes
< eos_bgp:
---
> - name: Configure BGP - Redistribute OSPFv3
> ios_bgp:
115c117
< - protocol: connected
---
> id: 1
123a126,140
> - name: Configure BGP - Redistribute OSPFv3 and include connected networks
> ios_config:
> lines:
> - redistribute ospf 1 include-connected
> parents:
> - router bgp {{ bgp['local_as'] }}
> - address-family ipv6 unicast
> when:
> - bgp is defined
> - bgp.redist is defined
> - bgp.redist.ospfv3 is defined
> tags:
> - bgp
> - bgp_v6
>
125c142
< eos_config:
---
> ios_config:
129a147
> - address-family ipv4 unicast
The relevant host_vars
for this are: -
bgp:
local_as: 65103
redist:
ospf: true
ospfv3: true
neighbors:
ipv4:
- peer: 10.100.103.254
remote_as: 65430
ebgp: true
acl:
acl_index: 110
- peer: 192.0.2.203
remote_as: 65103
ibgp: true
update_source: Loopback0
default_originate: true
ipv6:
- peer: "2001:db8:103::ffff"
remote_as: 65430
ebgp: true
acl:
acl_index: 110
- peer: "2001:db8:903:beef::2"
remote_as: 65103
ibgp: true
update_source: Loopback0
Generated configuration
The generated configuration from the above is: -
router bgp 65103
router-id 192.0.2.103
neighbor 10.100.103.254 remote-as 65430
neighbor 192.0.2.203 remote-as 65103
neighbor 192.0.2.203 update-source Loopback0
neighbor 192.0.2.203 default-originate
neighbor 192.0.2.203 maximum-routes 12000
neighbor 2001:db8:103::ffff remote-as 65430
neighbor 2001:db8:903:beef::2 remote-as 65103
neighbor 2001:db8:903:beef::2 update-source Loopback0
redistribute connected
redistribute ospf
!
address-family ipv6
neighbor 2001:db8:103::ffff activate
neighbor 2001:db8:903:beef::2 activate
redistribute ospfv3
Other than the slightly shorter address-family
commands, this would qualify as valid Cisco IOS configuration too!
Verification
arista-01
! Show BGP neighbours on IPv4 and IPv6
arista-01#show ip bgp summary
BGP summary information for VRF default
Router identifier 192.0.2.103, local AS number 65103
Neighbor Status Codes: m - Under maintenance
Neighbor V AS MsgRcvd MsgSent InQ OutQ Up/Down State PfxRcd PfxAcc
10.100.103.254 4 65430 403 403 0 0 06:36:38 Estab 1 1
192.0.2.203 4 65103 399 404 0 0 06:35:11 Estab 0 0
arista-01#show ipv6 bgp summary
BGP summary information for VRF default
Router identifier 192.0.2.103, local AS number 65103
Neighbor Status Codes: m - Under maintenance
Neighbor V AS MsgRcvd MsgSent InQ OutQ Up/Down State PfxRcd PfxAcc
2001:db8:103::ffff 4 65430 402 402 0 0 06:36:56 Estab 1 1
2001:db8:903:beef::2 4 65103 399 402 0 0 06:35:31 Estab 0 0
! Show BGP routes
arista-01#show ip bgp
BGP routing table information for VRF default
Router identifier 192.0.2.103, local AS number 65103
Route status codes: s - suppressed, * - valid, > - active, # - not installed, E - ECMP head, e - ECMP
S - Stale, c - Contributing to ECMP, b - backup, L - labeled-unicast
Origin codes: i - IGP, e - EGP, ? - incomplete
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop
Network Next Hop Metric LocPref Weight Path
* > 10.15.30.0/24 - 1 0 - i
* > 10.100.103.0/24 - 1 0 - i
* > 10.100.203.0/24 - 1 0 - i
* > 192.0.2.1/32 10.100.103.254 0 100 0 65430 i
* > 192.0.2.103/32 - 0 0 - i
* > 192.0.2.203/32 - 20 0 - i
* > 192.168.122.0/24 - 1 0 - i
arista-01#show ipv6 bgp
BGP routing table information for VRF default
Router identifier 192.0.2.103, local AS number 65103
Route status codes: s - suppressed, * - valid, > - active, # - not installed, E - ECMP head, e - ECMP
S - Stale, c - Contributing to ECMP, b - backup, L - labeled-unicast
Origin codes: i - IGP, e - EGP, ? - incomplete
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop
Network Next Hop Metric LocPref Weight Path
* > 2001:db8:103::/64 - 1 0 - i
* > 2001:db8:203::/64 - 1 0 - i
* > 2001:db8:903:beef::1/128 - 0 0 - i
* > 2001:db8:903:beef::2/128 - 20 0 - i
* > 2001:db8:999:beef::1/128 2001:db8:103::ffff 0 100 0 65430 i
! Ping the netsvr Loopback (192.0.2.1 and 2001:DB8:999:BEEF::1)
arista-01#ping 192.0.2.1
PING 192.0.2.1 (192.0.2.1) 72(100) bytes of data.
80 bytes from 192.0.2.1: icmp_seq=1 ttl=64 time=1.52 ms
80 bytes from 192.0.2.1: icmp_seq=2 ttl=64 time=1.08 ms
80 bytes from 192.0.2.1: icmp_seq=3 ttl=64 time=1.01 ms
80 bytes from 192.0.2.1: icmp_seq=4 ttl=64 time=1.12 ms
80 bytes from 192.0.2.1: icmp_seq=5 ttl=64 time=1.56 ms
--- 192.0.2.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 1.017/1.263/1.564/0.232 ms, ipg/ewma 1.899/1.398 ms
arista-01#ping 192.0.2.1 source lo0
PING 192.0.2.1 (192.0.2.1) from 192.0.2.103 : 72(100) bytes of data.
80 bytes from 192.0.2.1: icmp_seq=1 ttl=64 time=1.48 ms
80 bytes from 192.0.2.1: icmp_seq=2 ttl=64 time=1.04 ms
80 bytes from 192.0.2.1: icmp_seq=3 ttl=64 time=1.05 ms
80 bytes from 192.0.2.1: icmp_seq=4 ttl=64 time=1.11 ms
80 bytes from 192.0.2.1: icmp_seq=5 ttl=64 time=1.47 ms
--- 192.0.2.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 1.040/1.233/1.486/0.204 ms, ipg/ewma 1.916/1.365 ms
arista-01#ping 192.0.2.1 source 10.100.203.254
PING 192.0.2.1 (192.0.2.1) from 10.100.203.254 : 72(100) bytes of data.
80 bytes from 192.0.2.1: icmp_seq=1 ttl=64 time=1.47 ms
80 bytes from 192.0.2.1: icmp_seq=2 ttl=64 time=1.09 ms
80 bytes from 192.0.2.1: icmp_seq=3 ttl=64 time=1.03 ms
80 bytes from 192.0.2.1: icmp_seq=4 ttl=64 time=1.23 ms
80 bytes from 192.0.2.1: icmp_seq=5 ttl=64 time=1.60 ms
--- 192.0.2.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 1.038/1.290/1.607/0.219 ms, ipg/ewma 1.825/1.392 ms
arista-01#ping 2001:db8:999:beef::1
PING 2001:db8:999:beef::1(2001:db8:999:beef::1) 72 data bytes
80 bytes from 2001:db8:999:beef::1: icmp_seq=1 ttl=64 time=1.42 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=2 ttl=64 time=1.09 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=3 ttl=64 time=1.19 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=4 ttl=64 time=1.59 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=5 ttl=64 time=1.76 ms
--- 2001:db8:999:beef::1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 1.092/1.413/1.764/0.249 ms, ipg/ewma 1.903/1.435 ms
arista-01#ping 2001:db8:999:beef::1 source lo0
PING 2001:db8:999:beef::1(2001:db8:999:beef::1) from 2001:db8:903:beef::1 : 72 data bytes
80 bytes from 2001:db8:999:beef::1: icmp_seq=1 ttl=64 time=1.52 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=2 ttl=64 time=1.08 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=3 ttl=64 time=1.05 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=4 ttl=64 time=1.11 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=5 ttl=64 time=1.71 ms
--- 2001:db8:999:beef::1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 1.056/1.297/1.713/0.271 ms, ipg/ewma 1.851/1.420 ms
arista-02
! Show BGP neighbours on IPv4 and IPv6
arista-02#show ip bgp summary
BGP summary information for VRF default
Router identifier 192.0.2.203, local AS number 65103
Neighbor Status Codes: m - Under maintenance
Neighbor V AS MsgRcvd MsgSent InQ OutQ Up/Down State PfxRcd PfxAcc
192.0.2.103 4 65103 412 407 0 0 06:43:44 Estab 7 7
arista-02#show ipv6 bgp summary
BGP summary information for VRF default
Router identifier 192.0.2.203, local AS number 65103
Neighbor Status Codes: m - Under maintenance
Neighbor V AS MsgRcvd MsgSent InQ OutQ Up/Down State PfxRcd PfxAcc
2001:db8:903:beef::1 4 65103 410 407 0 0 06:43:45 Estab 5 5
! Show BGP routes
BGP routing table information for VRF default
Router identifier 192.0.2.203, local AS number 65103
Route status codes: s - suppressed, * - valid, > - active, # - not installed, E - ECMP head, e - ECMP
S - Stale, c - Contributing to ECMP, b - backup, L - labeled-unicast
Origin codes: i - IGP, e - EGP, ? - incomplete
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop
Network Next Hop Metric LocPref Weight Path
* > 0.0.0.0/0 192.0.2.103 0 100 0 ?
* # 10.15.30.0/24 192.0.2.103 0 100 0 i
* # 10.100.103.0/24 192.0.2.103 0 100 0 i
* # 10.100.203.0/24 192.0.2.103 0 100 0 i
* > 192.0.2.1/32 10.100.103.254 0 100 0 65430 i
* # 192.0.2.103/32 192.0.2.103 0 100 0 i
* > 192.168.122.0/24 192.0.2.103 0 100 0 i
arista-02#show ipv6 bgp
BGP routing table information for VRF default
Router identifier 192.0.2.203, local AS number 65103
Route status codes: s - suppressed, * - valid, > - active, # - not installed, E - ECMP head, e - ECMP
S - Stale, c - Contributing to ECMP, b - backup, L - labeled-unicast
Origin codes: i - IGP, e - EGP, ? - incomplete
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop
Network Next Hop Metric LocPref Weight Path
* # 2001:db8:103::/64 2001:db8:903:beef::1 0 100 0 i
* # 2001:db8:203::/64 2001:db8:903:beef::1 0 100 0 i
* # 2001:db8:903:beef::1/128 2001:db8:903:beef::1 0 100 0 i
* # 2001:db8:903:beef::2/128 2001:db8:903:beef::1 0 100 0 i
* > 2001:db8:999:beef::1/128 2001:db8:103::ffff 0 100 0 65430 i
! Ping the netsvr Loopback (192.0.2.1 and 2001:db8:999:beef::1)
arista-02#ping 192.0.2.1
PING 192.0.2.1 (192.0.2.1) 72(100) bytes of data.
80 bytes from 192.0.2.1: icmp_seq=1 ttl=63 time=4.88 ms
80 bytes from 192.0.2.1: icmp_seq=2 ttl=63 time=4.38 ms
80 bytes from 192.0.2.1: icmp_seq=3 ttl=63 time=4.62 ms
80 bytes from 192.0.2.1: icmp_seq=4 ttl=63 time=4.52 ms
80 bytes from 192.0.2.1: icmp_seq=5 ttl=63 time=5.12 ms
--- 192.0.2.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 20ms
rtt min/avg/max/mdev = 4.383/4.707/5.120/0.262 ms, ipg/ewma 5.142/4.806 ms
arista-02#ping 192.0.2.1 source lo0
PING 192.0.2.1 (192.0.2.1) from 192.0.2.203 : 72(100) bytes of data.
80 bytes from 192.0.2.1: icmp_seq=1 ttl=63 time=4.51 ms
80 bytes from 192.0.2.1: icmp_seq=2 ttl=63 time=3.86 ms
80 bytes from 192.0.2.1: icmp_seq=3 ttl=63 time=4.29 ms
80 bytes from 192.0.2.1: icmp_seq=4 ttl=63 time=4.60 ms
80 bytes from 192.0.2.1: icmp_seq=5 ttl=63 time=4.61 ms
--- 192.0.2.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 18ms
rtt min/avg/max/mdev = 3.866/4.378/4.613/0.287 ms, ipg/ewma 4.706/4.460 ms
arista-02#ping 2001:db8:999:beef::1 source lo0
PING 2001:db8:999:beef::1(2001:db8:999:beef::1) from 2001:db8:903:beef::2 : 72 data bytes
80 bytes from 2001:db8:999:beef::1: icmp_seq=1 ttl=63 time=4.47 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=2 ttl=63 time=3.99 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=3 ttl=63 time=4.11 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=4 ttl=63 time=4.53 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=5 ttl=63 time=5.06 ms
--- 2001:db8:999:beef::1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 18ms
rtt min/avg/max/mdev = 3.999/4.436/5.063/0.382 ms, ipg/ewma 4.644/4.479 ms
All looking good! Because of including connected routes, we do not have the same reachability issues we encountered in the Cisco post.
Ideally we should be filtering what connected routes are redistributed too (like in Part 4 - Juniper JunOS), but we are just redistributing all the routes for now.
NAT
In the Cisco post, I covered doing NAT. However as mentioned the vEOS images do not support NAT (due to them emulating switches rather than routers). WIth this being the case, I have omitted this role, as I have no way of verifying that the commands will even apply. I hope to one day have access to a routed-based Arista image (or an Arista router) to test the role out.
SNMP
SNMP is enabled so that we would be able to add the vEOS images into a monitoring system.
Playbook
The contents of the playbook are: -
---
## tasks file for snmp
- name: Enable SNMPv3
eos_config:
src: snmpv3.j2
tags:
- snmp
This is identical to the IOS playbook, except we use eos_config
rather than ios_config
. The template itself is below: -
snmp-server location {{ snmp['location'] }}
snmp-server contact {{ snmp['contact'] }}
snmp-server group {{ snmp['group'] }} v3 priv
snmp-server user {{ snmp['user'] }} {{ snmp['group'] }} v3 auth sha {{ snmp['auth_key'] }} priv aes {{ snmp['priv_key'] }}
The only difference between this template and the Cisco template is that in the Arista template, we specify priv aes
for AES128-based encryption, whereas in Cisco you specify priv aes 128
. Everything else is identical.
Our group_vars
relevant to this are below: -
snmp:
location: Yeti Home
contact: The Hairy One
user: yetiops
group: yetiops_group
auth_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
383###REDACTED###############################################################566
363###REDACTED###############################################################366
343###REDACTED###############################################################565
326###REDACTED###############################################################362
3431
priv_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
386###REDACTED###############################################################764
613###REDACTED###############################################################630
646###REDACTED###############################################################331
376###REDACTED###############################################################137
3563
These are the same as for our Cisco playbooks, and again we use Ansible Vault to encrypt the authentication and privacy keys.
Generated configuration
The generated configuration looks like the below: -
snmp-server contact The Hairy One
snmp-server location Yeti Home
snmp-server group yetiops_group v3 priv
snmp-server user yetiops yetiops_group v3 localized f5717f525400c5df3c00 auth sha ###AUTH-KEY### priv aes ###PRIV-KEY###
The localized
command is based upon the engineID that is generated by the image itself. For this image, this is snmp-server engineID local f5717f525400c5df3c00
.
Verification
To check this, we’ll check using snmpwalk
: -
$ snmpwalk -v3 -u yetiops -a SHA -A yeti_hash123 -x AES -X yeti_key123 -l authPriv 10.15.30.43
iso.3.6.1.2.1.1.1.0 = STRING: "Linux arista-01 4.9.122.Ar-14427155.4231F #1 SMP PREEMPT Wed Nov 27 21:50:11 PST 2019 x86_64"
iso.3.6.1.2.1.1.2.0 = OID: iso.3.6.1.4.1.30065.1.2759
iso.3.6.1.2.1.1.3.0 = Timeticks: (2573396) 7:08:53.96
iso.3.6.1.2.1.1.4.0 = STRING: "The Hairy One"
iso.3.6.1.2.1.1.5.0 = STRING: "arista-01"
iso.3.6.1.2.1.1.6.0 = STRING: "Yeti Home"
iso.3.6.1.2.1.1.7.0 = INTEGER: 14
[...]
$ snmpwalk -v3 -u yetiops -a SHA -A yeti_hash123 -x AES -X yeti_key123 -l authPriv 10.15.30.44 | head -n 7
iso.3.6.1.2.1.1.1.0 = STRING: "Linux arista-02 4.9.122.Ar-14427155.4231F #1 SMP PREEMPT Wed Nov 27 21:50:11 PST 2019 x86_64"
iso.3.6.1.2.1.1.2.0 = OID: iso.3.6.1.4.1.30065.1.2759
iso.3.6.1.2.1.1.3.0 = Timeticks: (2579360) 7:09:53.60
iso.3.6.1.2.1.1.4.0 = STRING: "The Hairy One"
iso.3.6.1.2.1.1.5.0 = STRING: "arista-02"
iso.3.6.1.2.1.1.6.0 = STRING: "Yeti Home"
iso.3.6.1.2.1.1.7.0 = INTEGER: 14
[...]
All looking good!
AAA
We use AAA (via the tac_plus
instance on our netsvr-01) to allow central management of user login, command authorization, and accounting of all commands ran on the devices.
Playbook
The contents of the playbook are as follows: -
---
## tasks file for aaa
- name: Enable TACACS+
eos_config:
src: tacacs.j2
tags:
- aaa
The only difference here to the IOS task is that we use eos_config
rather than ios_config
. The template itself looks like the below: -
aaa authentication enable default group tacacs+ local
aaa authorization exec default group tacacs+ none
aaa authorization commands 0 default group tacacs+ none
aaa authorization commands 1 default group tacacs+ none
aaa authorization commands 15 default group tacacs+ none
aaa accounting exec default start-stop group tacacs+
aaa accounting commands 0 default start-stop group tacacs+
aaa accounting commands 1 default start-stop group tacacs+
aaa accounting commands 15 default start-stop group tacacs+
tacacs-server host {{ tacacs['ipv4'] }} key 0 {{ tacacs['secret'] }} timeout 1
ip tacacs source-interface Loopback0
There are a few differences here from the Cisco template, which are: -
- We don’t specify
aaa new-model
, as the default model for AAA on Arista is equivalent to Cisco’s “new” model enable default group tacacs+ local
, as IOS uses the wordenable
instead oflocal
as the last part of the command- We source TACACS+ from our Loopback interface, to ensure that it is not tied to physical interface
- We do not need to specify
line vty
andline console
commands to enable TACACS+ for remote and console users - The
tacacs server
syntax in IOS does not work on EOS, so we use the “older” style syntax
The reason I say “older” syntax for specifying tacacs-server host
's is that if you apply this command on any recent Cisco router, it would give a warning that the syntax style will be deprecated soon, to be replaced by: -
tacacs server NETSVR
address ipv4 $IP-ADDRESS
key 0 $SECRET
timeout 1
Despite all of this, the configuration being applied is still very similar to Cisco. This is another case where if you are used to Cisco IOS, you are going to be immediately familiar with most of an Arista EOS configuration.
The group_vars
that are relevant to this are: -
tacacs:
ipv4: 192.0.2.1
secret: supersecret
Generated configuration
The configuration that is generated looks like the below: -
aaa authentication enable default group tacacs+ local
aaa authorization exec default group tacacs+ none
aaa authorization commands 0 default group tacacs+ none
aaa authorization commands 1 default group tacacs+ none
aaa authorization commands 15 default group tacacs+ none
aaa accounting exec default start-stop group tacacs+
aaa accounting commands 0 default start-stop group tacacs+
aaa accounting commands 1 default start-stop group tacacs+
aaa accounting commands 15 default start-stop group tacacs+
tacacs-server host 192.0.2.1 key 0 supersecret timeout 1
ip tacacs source-interface Loopback0
After it has been applied, the running configuration will look like: -
aaa authentication login default group tacacs+ local
aaa authentication enable default group tacacs+ local
aaa authorization exec default group tacacs+ none
aaa authorization commands 0-1,15 default group tacacs+ none
aaa accounting exec default start-stop group tacacs+
aaa accounting commands 0-1,15 default start-stop group tacacs+
!
tacacs-server host 192.0.2.1 key 7 095F5B191C170417081E013E6B30213E302D06135652
!
ip tacacs source-interface Loopback0
In the above, the separate commands for 0
, 1
and 15
(the privilege levels) are grouped together as one command. Also the TACACS+ secret key is encrypted.
Verification
! Can we login with our yetiops user, and be placed into enable mode instantly?
$ ssh [email protected]
----------------------------------------
|
| This banner was generated by Ansible
|
----------------------------------------
|
| You are logged into arista-01
|
----------------------------------------
Password:
arista-01#
! What about a user that doesn't exist?
$ ssh [email protected]
----------------------------------------
|
| This banner was generated by Ansible
|
----------------------------------------
|
| You are logged into arista-01
|
----------------------------------------
Password:
Password:
Password:
[email protected]: Permission denied (publickey,keyboard-interactive).
! What do we see in our accounting log?
Apr 18 20:22:12 192.0.2.103 ansible vty3 10.15.30.1 stop task_id=1 service=shell start_time=1587237505 timezone=UTC elapsed_time=227
Apr 18 20:22:18 192.0.2.103 yetiops ssh 10.15.30.1 start task_id=5 service=shell start_time=1587237737 timezone=UTC
Apr 18 20:22:37 192.0.2.103 yetiops vty3 10.15.30.1 stop task_id=6 service=shell priv-lvl=15 start_time=1587237756 timezone=UTC cmd=exit <cr>
Apr 18 20:22:37 192.0.2.103 yetiops vty3 10.15.30.1 stop task_id=5 service=shell start_time=1587237737 timezone=UTC elapsed_time=20
! What about if the TACACS+ server goes away?
$ sudo systemctl stop tac_plus
$ sudo systemctl status tac_plus
● tac_plus.service - LSB: TACACS+ server based on Cisco source release
Loaded: loaded (/etc/rc.d/init.d/tac_plus; generated)
Active: inactive (dead) since Sat 2020-04-18 20:24:08 BST; 11s ago
Docs: man:systemd-sysv-generator(8)
Process: 7549 ExecStop=/etc/rc.d/init.d/tac_plus stop (code=exited, status=0/SUCCESS)
Process: 7388 ExecStart=/etc/rc.d/init.d/tac_plus start (code=exited, status=0/SUCCESS)
Apr 18 20:22:50 netsvr-01 tac_plus[7522]: login failure: bob 192.0.2.103 (192.0.2.103) ssh
Apr 18 20:23:33 netsvr-01 tac_plus[7524]: connect from 192.0.2.103 [192.0.2.103]
Apr 18 20:23:39 netsvr-01 tac_plus[7528]: connect from 192.0.2.103 [192.0.2.103]
Apr 18 20:23:39 netsvr-01 tac_plus[7529]: connect from 192.0.2.103 [192.0.2.103]
Apr 18 20:23:40 netsvr-01 tac_plus[7530]: connect from 192.0.2.103 [192.0.2.103]
Apr 18 20:23:44 netsvr-01 tac_plus[7531]: connect from 192.0.2.103 [192.0.2.103]
Apr 18 20:23:48 netsvr-01 tac_plus[7533]: connect from 192.0.2.103 [192.0.2.103]
Apr 18 20:24:08 netsvr-01 systemd[1]: Stopping LSB: TACACS+ server based on Cisco source release...
Apr 18 20:24:08 netsvr-01 tac_plus[7549]: Shutting down tacacs+: [ OK ]
Apr 18 20:24:08 netsvr-01 systemd[1]: Stopped LSB: TACACS+ server based on Cisco source release.
$ ssh [email protected]
----------------------------------------
|
| This banner was generated by Ansible
|
----------------------------------------
|
| You are logged into arista-01
|
----------------------------------------
Password:
arista-01>en
Password:
arista-01#
All looking good!
Parent playbook
The parent playbook (i.e. the playbook that brings all the roles together) is below: -
---
- hosts: arista
gather_facts: no
tasks:
- import_role:
name: system
- import_role:
name: interfaces
- import_role:
name: acl
- import_role:
name: routing
- import_role:
name: snmp
# - import_role:
# name: nat
- import_role:
name: aaa
- name: save running to startup when modified
eos_config:
save_when: modified
Again, this is the same as the IOS playbook. The only exception is that we have commented out the NAT role until it can be tested.
Role Order
The role order is similar to the IOS playbook, except the SNMP task is applied later in the playbook. As SNMP has no dependencies, we can place it anywhere.
To reiterate the reasons for the order of the roles: -
system
- Basic setup, and enabling logginginterfaces
- A device cannot route traffic without IPs!acl
- Ensures that we do not allow nefarious traffic when external routing is enabledrouting
- Required for NAT and AAAsnmp
- As mentioned, this can go anywhere in the playbooknat
- If we could enable NAT, traffic from the internal router would not reach the NAT interfaces until routing is establishedaaa
- Ensures that if TACACS+ is not functioning correctly, the device is not in a half configured (and potentially broken) state
Artifacts
The final directory structure looks like the below: -
$ tree -L 2
.
├── ansible.cfg
├── ansible.log
├── arista.yaml
├── group_vars
│ └── arista
├── host_vars
│ ├── arista-01.yml
│ └── arista-02.yml
├── inventory
└── roles
├── aaa
├── acl
├── interfaces
├── lldp
├── nat
├── routing
├── snmp
└── system
11 directories, 7 files
The final contents of our group_vars
are: -
ansible_user: ansible
ansible_connection: network_cli
ansible_network_os: eos
ansible_ssh_pass: !vault |
$ANSIBLE_VAULT;1.1;AES256
383###REDACTED###############################################################566
363###REDACTED###############################################################366
343###REDACTED###############################################################565
326###REDACTED###############################################################362
3431Ta
ansible_become: yes
ansible_become_method: enable
ansible_become_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
383###REDACTED###############################################################566
363###REDACTED###############################################################366
343###REDACTED###############################################################565
326###REDACTED###############################################################362
3431Ta
log_host: 10.100.101.254
tacacs:
ipv4: 192.0.2.1
secret: supersecret
snmp:
location: Yeti Home
contact: The Hairy One
user: yetiops
group: yetiops_group
auth_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
383###REDACTED###############################################################566
363###REDACTED###############################################################366
343###REDACTED###############################################################565
326###REDACTED###############################################################362
3431
priv_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
386###REDACTED###############################################################764
613###REDACTED###############################################################630
646###REDACTED###############################################################331
376###REDACTED###############################################################137
3563
The final contents of our host_vars
are: -
arista-01.yaml
outer_id: 192.0.2.103
rtr_role: edge
bgp:
local_as: 65103
redist:
ospf: true
ospfv3: true
neighbors:
ipv4:
- peer: 10.100.103.254
remote_as: 65430
ebgp: true
acl:
acl_index: 110
- peer: 192.0.2.203
remote_as: 65103
ibgp: true
update_source: Loopback0
default_originate: true
ipv6:
- peer: "2001:db8:103::ffff"
remote_as: 65430
ebgp: true
acl:
acl_index: 110
- peer: "2001:db8:903:beef::2"
remote_as: 65103
ibgp: true
update_source: Loopback0
interfaces:
- eos_if: "Management1"
desc: "Management"
enabled: true
ipv4: "10.15.30.43/24"
- eos_if: "Ethernet1"
desc: "To netsvr"
enabled: true
routed: true
ipv4: "10.100.103.253/24"
ipv6: "2001:db8:103::f/64"
acl:
ipv4:
in: EDGE-IN
out: EDGE-OUT
ipv6:
in: EDGEv6-IN
out: EDGEv6-OUT
ospf:
area: "0.0.0.0"
passive: true
ospfv3:
area: "0.0.0.0"
passive: true
- eos_if: "Ethernet2"
desc: "To arista-02"
routed: true
enabled: true
ipv4: "10.100.203.254/24"
ipv6: "2001:db8:203::a/64"
ospf:
area: "0.0.0.0"
ospfv3:
area: "0.0.0.0"
- eos_if: "Ethernet3"
desc: "To the Internet"
enabled: true
routed: true
ipv4: "dhcp"
- eos_if: "Loopback0"
desc: "Loopback"
enabled: true
ipv4: "192.0.2.103/32"
ipv6: "2001:db8:903:beef::1/128"
ospf:
area: "0.0.0.0"
passive: true
ospfv3:
area: "0.0.0.0"
passive: true
arista-02-yaml
router_id: 192.0.2.203
rtr_role: internal
bgp:
local_as: 65103
neighbors:
ipv4:
- peer: 192.0.2.103
remote_as: 65103
ibgp: true
update_source: Loopback0
ipv6:
- peer: "2001:db8:903:beef::1"
remote_as: 65103
ibgp: true
update_source: Loopback0
interfaces:
- eos_if: "Management1"
desc: "Management"
enabled: true
- eos_if: "Ethernet1"
desc: "To arista-01"
enabled: true
routed: true
ipv4: "10.100.203.253/24"
ipv6: "2001:db8:203::f/64"
ospf:
area: "0.0.0.0"
ospfv3:
area: "0.0.0.0"
- eos_if: "Loopback0"
desc: "Loopback"
enabled: true
ipv4: "192.0.2.203/32"
ipv6: "2001:db8:903:beef::2/128"
ospf:
area: "0.0.0.0"
passive: true
ospfv3:
area: "0.0.0.0"
passive: true
Running the playbooks
Below is an Asciinema output of my terminal when running the playbooks, so you can see them being applied: -
Compared to the IOS playbooks, only a couple of tasks were marked as changed, despite applying the same configuration.
When looking through the TACACS+ accounting logs, you can see the command show session-config diffs
, which is a method similar to JunOS’s show | compare
. Arista allows you to enter a “session” for configuration and compare candidate configuration to the running configuration before committing. This isn’t the case with the Cisco IOS tasks. Tasks are marked as changed based upon the difference between the commands being applied, and the resulting output in the running configuration.
Native modules versus eos_config
Below is a summary of how many different modules are used, and also how many in total were native modules (compared to using eos_config
).
Module | Used |
---|---|
eos_config | 31 |
eos_bgp | 6 |
eos_logging | 2 |
eos_l3_interfaces | 2 |
eos_banner | 2 |
eos_system | 1 |
eos_interfaces | 1 |
As with the IOS playbooks, we are using the eos_config
module significantly more than the others. The reason we use the eos_config
module more than in the Cisco module is because we are setting a log source for syslog
, and because we need to enable IPv4 routing (due to emulating a switch rather than a router).
Thoughts compared to IOS and JunOS
If we compare the Arista playbooks to the JunOS playbooks, there are a number of differences. This is to be expected, as the configuration style of JunOS is very different from Arista EOS (or Cisco IOS). Until either common APIs or data modelling languages like YANG become commonplace, this will probably always be the case.
However if we compare them to IOS, there are so many similarities, we could almost get away with making no changes except module names.
To build the playbooks for this module, rather than rewriting the tasks, I did a mass find and replace for the word ios
and replaced it with eos
. The majority of the tasks applied cleanly, with only a few failing. Within a couple of hours of comparison, reading Arista documentation and checking against the Arista command line, I had a finished set of tasks and playbooks.
In the first post in this series, I said the following: -
I also believe that showing it across multiple vendors (and how similar some aspects are) will make it easier for people to move from one vendor to another, without worrying about needing to relearn syntax or tools for managing their networking estate.
The differences between IOS and EOS (in CLI syntax and in Ansible module usage) are a perfect demonstration of this. If you were to decide to move from an IOS platform to an EOS platform, migrating your Ansible playbooks would take very little effort.
In contrast, the differences between the IOS and JunOS playbooks are significant. A few days or weeks familiarising yourself with JunOS syntax would probably be enough to move from one vendor to another, but it is not quite as straightforward as the move from IOS to EOS. This is not to say that JunOS (or other vendors) are impossible to migrate between. It just means that if you do rely on Ansible, you’ll need to account for the extra time in refactoring your roles and playbooks.
Summary
While the previous two posts in the series were more about using Ansible with either Cisco or Juniper (and how each task/playbook works), this post has mainly covered how to the differences between IOS playbooks and EOS playbooks.
However this is not a criticism of Arista. It is quite easy to see how Arista has made the gains they have in the networking industry. With Cisco being such a massive part of the networking industry, using similar (and in some cases identical) syntax allows comparisons and purchasing decisions to be made entirely on price and features. I have seen first hand companies unwilling to change their networking vendor purely out of familiarity.
The final configs from the switches are in my Network Automation with Ansible repository. The next part of this series will be configuring MikroTik devices, running RouterOS.
devops sysadmin ansible config management networking
technical sysadmin config management networking
12299 Words
2020-04-20 17:30 +0000