Skip to content

Ansible KVM Router Lab Part 5

date: 2021-10-17

Introduction

This is Part 5 of a multi-part series of blog posts for building a router lab automatically using a series of bash scripts and ansible.

Ansible KVM Router Lab Part 1 is an overview.

In Ansible KVM Router Lab Part 2, I break down the script build_vms.bash.

In Ansible KVM Router Lab Part 3, I explain define_bridge_networks.bash and shutdown_vms.bash scripts which are used to construct the lab.

In Ansible KVM Router Lab Part 4, I explain connect_vms_to_bridges.bash, start_vms.bash, and rebuild_known_hosts.bash scripts which are used to construct the lab.

In this post I explain how I use Ansible to finish constructing the lab.

In Ansible KVM Router Lab Part 6, I explain disconnect_vms_from_bridges.bash, undefine_and_remove_vms.bash, and remove_bridge_networks which are used to destroy the lab.

Setup Ansible

  • Configure ansible host file
    # ~/.ansible.cfg
    [defaults]
    inventory = ~/router-lab/ansible/hosts.yml
    
  • Setup bashrc
    # ~/.bashrc
    export LIBVIRT_DEFAULT_URI="qemu+ssh://<user>@<server>/system"
    
    alias ansible-pb=anspb
    anspb() {
      ANS_DIR=~/router-lab/ansible/playbooks;
      echo Changing to "${ANS_DIR}" and executing: ansible-playbook "${@}"
      (cd $ANS_DIR || exit ; ansible-playbook "${@}")
    }
    
  • install apps
    apt install ansible ansible-lint
    

Run Ansible

ansible-pb build_out_routers.yml -K
or if you want to first update all the clients
ansible-pb update_and_build.yml -K

Ansible Tasks

This is an explaination of the tasks in the Ansible Playbook. Playbooks are executed from top to bottom.

Install dnsmasq, iptables-persistent

This task is only run against the first and second lab clients as they are the routers.

Install traceroute

Traceroute is parsed in a later task to confirm that traffic is following the correct route. (Also incidentally installs needrestart and screen.)

Backup /etc/network/interfaces

This is a simple bash command that tests if /etc/network/interfaces.bak exists, and if not creates it.

Update Network Config

This task updates /etc/network/interfaces in all the lab clients to describe the network interfaces needed to connect to each other.

For instance, here is the new /etc/network/interfaces file for dnettwo.

# /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
allow-hotplug enp1s0
iface enp1s0 inet dhcp

# The primary network interface
allow-hotplug enp7s0
iface enp7s0 inet dhcp

auto enp8s0
iface enp8s0 inet static
        address 10.4.4.1
        network 10.4.4.0
        netmask 255.255.255.0
        broadcast 10.4.4.255

Backup /etc/dnsmasq.conf

This is a simple bash command that tests if /etc/dnsmasq.conf.bak exists, and if not creates it. (only applies to the two router clients)

Configure dnsmasq

This task copies the templates for /etc/dnsmasq.conf to each of the two router clients.

dnsmasq is used to provide DHCP (and name resolution). For instance, here is the new /etc/dnsmasq.conf for dnetone.

# /etc/dnsmasq.conf
dhcp-range=10.5.5.50,10.5.5.150
listen-address=127.0.0.1, 10.5.5.1

Configure Network ifup

This applies to all the lab clients except for the first one, changes the default route. A bash script is copied from template to /etc/network/if-up.d/ifup-script.

For instance here is ifup-script for dnetthree.

#!/bin/bash
# /etc/network/if-up.d/ifup-script

default_dev="$(ip route | head -1 | awk '{print $5}')"
echo "${default_dev}"

if [ "${default_dev}" == "enp1s0" ]
then
  ip route del default via 10.55.44.1 dev enp1s0
fi

if [ "${default_dev}" != "enp7s0" ]
then
  ip route add default via 10.4.4.1 dev enp7s0
fi

Restart Network and dnsmasq

This is sequential:

  1. enp7s0 is restarted on dnetone
  2. dnsmasq is restarted on dnetone, offering service on enp7s0
  3. enp7s0 and enp8s0 are restarted on dnettwo, thus soliciting dhcp service on enp7s0, and triggering /etc/network/if-up.d/ifup-script
  4. dnsmasq is restarted on dnettwo, offering service on enp8s0
  5. enp7s0 is restarted on dnetthree, dnetfour, and dnetfive, thus soliciting dhcp service on enp7s0, and triggering /etc/network/if-up.d/ifup-script

Backup /etc/sysctl.conf

This is a simple bash command that tests if /etc/sysctl.conf.bak exists, and if not creates it. (only applies to the two router clients)

Enable ipv4 forwarding

This is a simple bash command that uncomments the option for ipv4 forwarding in /etc/sysctl.conf, applies only to the two routers.

# /etc/sysctl.conf
...
# this
#net.ipv4.ip_forward=1
...
# becomes this
net.ipv4.ip_forward=1
...

Start ipv4 forwarding

This simple bash command starts ipv4 forwarding, applies only to the two routers.

bash -c "sysctl -w net.ipv4.ip_forward=1"

Configure iptables workaround

This applies only to the two router clients. From iptables's point of view, the ansible connection isn't a RELATED INPUT connection, thus it is necessary to bring up a firewall in a two-step process that involves first ACCEPTING RELATED OUTPUT connections in a workaround.

From ansible template, the following is copied to /dev/shm/iptables_workaround

# /dev/shm/iptables_workaround
*filter
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]

-A INPUT -j ACCEPT -m conntrack --ctstate ESTABLISHED,RELATED
-A OUTPUT -j ACCEPT -m conntrack --ctstate ESTABLISHED,RELATED

COMMIT

Apply iptables workaround

This applies only to the two router clients. The following command is dispatched to apply the above iptables_workaround:

bash -c "iptables-restore < /dev/shm/iptables_workaround"

Configure iptables

This applies only to the two router clients.

From ansible template the following is copied to /etc/iptables/rules.v4 on dnetone.

*nat
-A POSTROUTING -o enp1s0 -j MASQUERADE
COMMIT

*filter
-A INPUT -i lo -j ACCEPT
# allow ssh, so that we do not lock ourselves
-A INPUT -i enp1s0 -p tcp -m tcp --dport 22 -j ACCEPT
# allow incoming traffic to the outgoing connections,
# et al for clients from the private network
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
# prohibit everything else incoming
-A INPUT -i enp1s0 -j DROP
COMMIT

From ansible template the following is copied to /etc/iptables/rules.v4 on dnettwo.

*nat
-A POSTROUTING -o enp7s0 -j MASQUERADE
COMMIT

*filter
-A INPUT -i lo -j ACCEPT
# allow ssh, so that we do not lock ourselves
-A INPUT -i enp7s0 -p tcp -m tcp --dport 22 -j ACCEPT
# allow incoming traffic to the outgoing connections,
# et al for clients from the private network
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
# prohibit everything else incoming
-A INPUT -i enp7s0 -j DROP
COMMIT

Apply iptables firewall

This applies only to the two router clients. The following command is dispatched to apply the above from /etc/iptables/rules.v4:

bash -c "iptables-restore < /etc/iptables/rules.v4"

traceroute test

The following script is dispatched to dnettwo:

#!/bin/bash

RESULT="$(traceroute 8.8.8.8)"

FIRST_HOP="$(echo "${RESULT}" | head -2 | tail -1 | awk '{print $2}')"

if [ "${FIRST_HOP}" == "10.5.5.1" ]
then
  exit 0
else
  exit 1
fi
The following script is dispatched to dnetthree, dnetfour, and dnetfive:
#!/bin/bash

RESULT="$(traceroute 8.8.8.8)"

FIRST_HOP="$(echo "${RESULT}" | head -2 | tail -1 | awk '{print $2}')"

if [ "${FIRST_HOP}" != "10.4.4.1" ]
then
  exit 1
fi

SECOND_HOP="$(echo "${RESULT}" | head -3 | tail -1 | awk '{print $2}')"

if [ "${SECOND_HOP}" == "10.5.5.1" ]
then
  exit 0
else
  exit 1
fi

To Be Continued

In Ansible KVM Router Lab Part 6, I explain disconnect_vms_from_bridges.bash, undefine_and_remove_vms.bash, and remove_bridge_networks which are used to destroy the lab.