IPsec on Linux – Strongswan Configuration w/Cisco IOSv (IKEv2, Route-Based VTI, PSK)

IPsec is a cool tool for encrypting connections between network nodes, usually over the Internet (but not always). There are many different ways to configure an IPsec tunnel. Many tunnels use a policy-based approach which means the traffic that is sent through the tunnel is pre-defined using a “policy” that is part of the configuration. That doesn’t always work, you may need to dynamically change what traffic goes through using a routing protocol like OSPF or EIGRP without having to bring the tunnel down and reconfigure it . Thus there is another option, a “route-based” tunnel.

There are two different methods for creating a route-based IPsec tunnel, the first using GRE, which inserts a second GRE IP header into packets going into the tunnel. The second is VTI, which operates in a similar manner to GRE but under the hood it’s quite a different implementation.

VTI was originally way to save IP space on point-to-point links in the early networking days before subnetting. It was adapted as a way to assign routes to an IPsec tunnel. Part of its legacy is a “numbered” or “unnumbered” mode. Originally, VTI could inherit an IP from another interface and save IP address space. This unnumbered mode is pretty strange, so today we’ll take a look at numbered mode to keep things familiar.

Topology

Pretty simple, we’re trying to get the Window10 box at the bottom left to ping the Ubuntu Server 18.04 at the bottom right to ping each other. We’ll configure a tunnel between the Ubuntu box at the top left and the Cisco IOSv router at the top right. The underlying 1.1.1.0/30 network serves to look like a WAN network, while the network inside the VTI tunnel will be 10.0.0.0/30.

Installation

Ubuntu18.04-FRR-1


Strongswan on Ubuntu 18.04 is pretty easy with apt-get:

apt-get install strongswan

Configuration

Ubuntu18.04-FRR-1

First you need to add a config to /etc/ipsec.conf, something that looks like this:

conn tunnel 
        leftupdown=/usr/local/sbin/ipsec-notify.sh #run this script on start
        left=166.0.0.5
        leftsubnet=0.0.0.0/0 #all traffic
        right=166.0.0.1
        rightsubnet=0.0.0.0/0 #all traffic
        ike=aes256-sha2_256-modp1024!
        esp=aes-sha2_256!
        authby=secret
        auto=start
        keyexchange=ikev2
        mark=32 # only accepts packets with this mark
        type=transport

Then configure the PSK in /etc/ipsec.secrets:

1.1.1.2 1.1.1.1 : PSK '12345'

Then create the tunnel script that is referenced in the config. For me the file will be located at /home/james/ipsec-notify.sh:

ip link add vti0 type vti local 1.1.1.2 remote 1.1.1.1 key 32
ip link set vti0 up
ip addr add 10.0.0.2/30 dev vti0
ip lin set dev vti0 mtu 1400

Note the “key 32” in the first line above. That identifies what traffic strongswan should encrypt and corresponds to the “mark” in the strongswan config. It’s important.

Next you need to add a line for your VTI interface in /etc/sysctl.conf that looks like this to disable kernel policy lookups, this is a routed interface:

net.ipv4.conf.vti0.disable_policy=1

Finally, you need to tell Charon (Strongswan’s IKE daemon) to not handle routing. We’ll handle routing on our own. In /etc/strongswan.d/charon.conf, find this line:

install_routes = no

Make sure it’s set to no. Tell strongswan to restart and the tunnel should attempt a connection:

ipsec restart

CiscoIOSv15.6(2)T-1

My running-config is abbreviated, but it looks like this:

crypto ikev2 proposal james-proposal 
 encryption aes-cbc-256
 integrity sha256
 group 2
!
crypto ikev2 policy james-policy 
 proposal james-proposal
!
crypto ikev2 keyring james-ring
 peer remote-router-james
  address 1.1.1.2
  pre-shared-key 12345
!   
crypto ikev2 profile james-profile
 match identity remote address 1.1.1.2 255.255.255.255 
 authentication local pre-share
 authentication remote pre-share
 keyring local james-ring
!
crypto ipsec transform-set james-trans esp-aes esp-sha256-hmac 
 mode transport
!
crypto ipsec profile james-protect-vti
 set transform-set james-trans 
 set ikev2-profile james-profile
!
interface Tunnel0
 ip address 10.0.0.1 255.255.255.252
 ip mtu 1400
 tunnel source 1.1.1.1
 tunnel mode ipsec ipv4
 tunnel destination 1.1.1.2
 tunnel protection ipsec profile james-protect-vti

Static routing

Ubuntu18.04-FRR-1

Pretty simple, just add a route for 172.16.0.0/24 pointing to 10.0.0.1

ip route add 172.16.0.0/24 via 10.0.0.1

CiscoIOSv15.6(2)T-1

Simple here too:

ip route 192.168.0.0 255.255.255.0 10.0.0.2

Optional – EIGRP Routing

Remove those static routes and we’ll try a protocol to achieve the aforementioned dynamic routing.

Ubuntu18.04-FRR-1

Checkout my post on setting up EIGRP with Free Range Routing on Linux for the installation. Assuming you have that done, just log into the FRR shell on Ubuntu, it’s in /snap/bin, or just use the full path:

/snap/bin/frr.vtysh
Hello, this is FRRouting (version 7.2.1).
Copyright 1996-2005 Kunihiro Ishiguro, et al.
james# 
james# conf t
james(config)# router eigrp 10
james(config-router)# network 192.168.0.0/24
james(config-router)# network 10.0.0.0/30
james(config-router)# end
james# 

CiscoIOSv15.6(2)T-1

On a Cisco IOSv router, it’s pretty simple:

Router(config)#router eigrp 10
Router(config-router)#network 172.16.0.0 0.0.0.255
Router(config-router)#network 10.0.0.0 0.0.0.3
Router(config-router)#end

Verification

Ubuntu18.04-FRR-1

Verifying the tunnel on Ubuntu is done with “ipsec statusall”, although there are more specific commands. This will do since we only have one tunnel. I’ve abbreviated the output, but these lines say it all:

Security Associations (1 up, 0 connecting):
      tunnel[1]: ESTABLISHED 58 minutes ago, 1.1.1.2[1.1.1.2]...1.1.1.1[1.1.1.1]
      tunnel[1]: IKEv2 SPIs: 9a137e96ee332b6b_i* 1599c6291be65825_r, pre-shared key reauthentication in 110 minutes
      tunnel[1]: IKE proposal: AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1024
      tunnel{11}:  INSTALLED, TRANSPORT, reqid 1, ESP SPIs: c72f4e27_i de8fcbc0_o
      tunnel{11}:  AES_CBC_128/HMAC_SHA2_256_128, 0 bytes_i, 0 bytes_o, rekeying in 30 minutes
      tunnel{11}:   1.1.1.2/32 === 1.1.1.1/32

For routing, just issue “ip route” and you’ll see your static or EIGRP routes:

root@james:/snap/bin# ip route
1.1.1.0/30 dev eth1 proto kernel scope link src 1.1.1.2  
10.0.0.0/30 dev vti0 proto kernel scope link src 10.0.0.2  
172.16.0.0/24 via 10.0.0.1 dev vti0 proto 192 metric 20  
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.1

CiscoIOSv15.6(2)T-1

Lots of ways to show what’s going on with an IPsec tunnel on an IOS device, and “show crypto ipsec sa peer” is one of them. I usually use “i” to just include the status:

Router#show crypto ipsec sa peer 1.1.1.2 | i Status
        Status: ACTIVE(ACTIVE)
        Status: ACTIVE(ACTIVE)
        Status: ACTIVE(ACTIVE)
        Status: ACTIVE(ACTIVE)

Also “show ip route” will show your static or EIGRP routes (D for EIGRP):

      1.0.0.0/8 is variably subnetted, 2 subnets, 2 masks
C        1.1.1.0/30 is directly connected, GigabitEthernet0/1
L        1.1.1.1/32 is directly connected, GigabitEthernet0/1
      10.0.0.0/8 is variably subnetted, 2 subnets, 2 masks
C        10.0.0.0/30 is directly connected, Tunnel0
L        10.0.0.1/32 is directly connected, Tunnel0
      172.16.0.0/16 is variably subnetted, 2 subnets, 2 masks
C        172.16.0.0/24 is directly connected, GigabitEthernet0/0
L        172.16.0.1/32 is directly connected, GigabitEthernet0/0
D     192.168.0.0/24 [90/26882560] via 10.0.0.2, 00:07:56, Tunnel0

Finally, don’t forget to ping from Windows:

Troubleshooting

There are lots of tools here, including the strongswan “ipsec statusall”, Cisco debug commands, and others. But the one that always let’s me know what’s wrong the fastest is a packet capture. Look for IKE negotiation packets (ISAKMP filter in Wireshark) if you’re tunnel isn’t coming up, and make sure traffic goes through the tunnel (ESP filter in Wireshark) when it’s up:

BIND9 DNS Server on Ubuntu 18.04 Linux (w/ Cisco Routing)

Background

BIND9 is an open-source DNS (Domain Name System – the text-to-ip-address resolution system we all rely upon) server application that many of the world’s DNS servers use to change IP addresses (e.g. 1.2.3.4) into strings of characters such as “questioncomputer.com”. DNS is an area of expertise in which some people spend their entire careers and as such, there is much to know about it. I’m no DNS expert, but since anyone can fire up their own DNS server with BIND9, I figured I’d give it a go. BIND9 of couse runs on Linux, today we’ll be using Ubuntu 18.04.

BIND9 is one of several applications that a non-profit company called “Internet Systems Consortium” produces, you can find their page on BIND9 here. They rely on companies that use BIND9 to purchase professional support, so if you work for an organization that does, please consider reaching out to them.

Topology

This topology includes 3 different routers and 3 different servers. The routers, aptly-named Router1, Router2, and Router3 each have 1 server connected. Two of the routers are Cisco IOSv routers, and Router3 is an Ubuntu box with Free Range Routing installed just for kicks. Routing is set up using OSPF, so everything can already ping everything else. If you’re interested in how set up an Ubuntu 18.04 server as a router with OSPF, please check out my post on installing FRR. In that post I ran EIGRP but configuring OSPF is pretty simple once you get FRR installed.

The DNS server at the top is running Ubuntu 18.04, as well as the generic Ubuntu 18.04 server at the bottom right. There is a Windows 10 desktop at the bottom left to provide some diversity.

Installation

Installation of BIND9 is pretty easy using Ubuntu’s package manager:

apt-get install bind9

Configuration

There are three text files needed to get a basic BIND9 configuration up and running, these are:
/etc/bind/named.conf.options –> Configures BIND9 options
/etc/bind/named.conf.local –> Sets zone file name and gives its location
/etc/bind/zones/db.jamesmcclay.com –> The actual zone file with DNS records.

First order of business is edit /etc/bind/named.conf.options to have a very, very basic configuration:

options {
        directory "/var/cache/bind";
        listen-on { any; };
};

Second, add a zone configuration to named.conf.local that indicates where the zone file will be kept:

zone "jamesmcclay.com" {
    type master;
    file "/etc/bind/zones/db.jamesmcclay.com";
};

Lastly, we’ll create “db.jamesmcclay.com” under the “zones” directory, because that’s what I put in named.conf.local. Obviously you’ll want to probably substitute something else for “jamesmcclay.com”:

@               IN      SOA     dns-server.jamesmcclay.com    dns-server.localhost (
                                2               ; Serial
                                604800  ; Refresh
                                86400           ; Retry
                                2419200 ; Expire
                                604800 )        ; Negative Cache TTL
;
@               IN      NS      dns-server
@               IN      A       172.16.0.2
Router1         IN      A       10.0.0.1
Router2         IN      A       10.0.0.2
Router3         IN      A       11.0.0.1
Win10           IN      A       192.168.0.2
U1804           IN      A       192.168.1.2
dns-server      IN      A       172.16.0.2

The values towards the top are mostly just setting default values for DNS, while the A records at the bottom are more important. Those are the IP addresses that will be returned for hostnames within “jamesmcclay.com”. Gotta make sure those are correct. They all correspond with the IP’s listed in the topology.

Now restart BIND9:

systemctl restart bind9

It should show green if your config is good:

$systemctl status bind9

Configuring the servers and desktops is as simple as setting the DNS server IP address to 172.16.0.2. On Ubuntu linux, you can do it with Netplan. The default config file for Netplan is at /etc/netplan/50-cloud-init.yaml, and I configured UbuntuServer18.04-2 at the bottom right of the topology like this:

network:
    ethernets:
        eth0:
            addresses: [192.168.1.2/24]
            dhcp4: false
            optional: true
            gateway4: 192.168.1.1
            nameservers:
                search: [jamesmcclay.com]
                addresses: [172.16.0.2]
    version: 2

For Windows 10 (and most others, I think) you can sift through the control panel to get to network adapter settings, but I just hit “window key + R” and type “ncpa.cpl” and press enter which takes me to network adapter configuration. My configuration on the Windows 10 desktop at the bottom left of the topology looks like this:

In “Advanced” I also set the search domain to “jamesmcclay.com” which appends that domain to any non-fully qualified hosts entered. You’ll see how that’s helpful in the verification section:

Verification

Simple pings will verify that you’re able to reach various hosts via their DNS names instead of IP addresses. On the UbuntuServer18.04-2 at the bottom right, I pinged the whole group using their fully qualified domain names:

$ping router1.jamesmcclay.com -c 1
64 bytes from 10.0.0.1 (10.0.0.1): icmp_seq=1 ttl=254 time=1.45 ms
$ping router2.jamesmcclay.com -c 1
64 bytes from 10.0.0.2 (10.0.0.2): icmp_seq=1 ttl=254 time=1.03 ms
$ping router3.jamesmcclay.com -c 1
64 bytes from 11.0.0.1: icmp_seq=1 ttl=64 time=0.635 ms
$ping win10.jamesmcclay.com -c 1
64 bytes from 192.168.0.2 (192.168.0.2): icmp_seq=1 ttl=126 time=2.11 ms
$ping dns-server.jamesmcclay.com -c 1
64 bytes from 172.16.0.2 (172.16.0.2): icmp_seq=1 ttl=62 time=0.913 ms

And since I entered search domains in my DNS server configurations, I can ping hosts without fully qualified domain names. From my Windows 10 desktop:

And as usual, you can do a packet capture to verify that the DNS packet flow is working correctly. If I look at packets leaving my Windows 10 desktop, I’ll see a DNS query from the desktop, and a response from BIND9:

Troubleshooting

If you’re having trouble, make sure all of your config files are formatted correctly. They get angry if you don’t format them right. Be sure and do a packet capture if you get stuck, that will pinpoint the problem pretty quickly. Also you can check the logs in the BIND9 server for messages that might have a clue. The command I use is “cat /var/log/syslog | grep bind”.

Apr 19 03:42:16 uvm1804 named[15145]: running as: named -f -u bind<br>Apr 19 03:42:16 uvm1804 named[15145]: loading configuration from '/etc/bind/named.conf'<br>Apr 19 03:42:16 uvm1804 named[15145]: reading built-in trust anchors from file '/etc/bind/bind.keys'<br>Apr 19 03:42:16 uvm1804 named[15145]: set up managed keys zone for view _default, file 'managed-keys.bind'<br>Apr 19 03:42:16 uvm1804 named[15145]: configuring command channel from '/etc/bind/rndc.key'<br>Apr 19 03:42:16 uvm1804 named[15145]: configuring command channel from '/etc/bind/rndc.key'<br>root@dns-server:/etc/netplan#

Loopback Adapter on Ubuntu 18.04 (Like on Cisco)

Background

For many years, Linux users used the old familiar network configuration text file located (usually) at ‘/etc/network/interfaces’ to configure IP addresses, DNS settings, gateway settings, and the like. It was even possible to create a virtual (read: loopback) interface with no associated physical interface using this file. While desktop versions of Ubuntu have used Network Manager for a long time to make things easier from a GUI, the CLI-only nature of an Ubuntu Server doesn’t need all the overhead of Network Manager.

Recent versions of Ubuntu Server (starting with 18.04) have moved to a new network configuration method, using a software package called “Netplan” developed by Ubuntu’s maintainer, Canonical. I’ve read the Ubuntu developers’ reasoning for making this switch, and to make a long story short, their decision was…. justified. The old method had many shortcomings. And while Netplan has a cool new and modern feel including configuration from a YAML file, the one thing that’s missing from it (best I can tell) is a way to create a loopback adapter. I scoured the Intertubes looking for a way to create one via Netplan but finally came to the conclusion that it doesn’t really have one. Others in the Ubuntu community seemed to agree but I could find no official documentation… if you know of some please leave a comment or send me an email, I’d love to know. I’ll update this article if loopback functionality is added to Netplan.

In any case, I’ve been using a method to create loopback adapters on Ubuntu Server and Desktop well before Netplan came along, simply because it’s the way I learned and it seems to work no matter what distribution or version I’m using. Loopback adapters are useful for all sorts of stuff in networking. You can create one, give it an IP address and advertise it in a routing protocol like OSPF, for example. Unlike physical interfaces, loopback adapters are much, much less likely to “go down”.

Topology

Nothing mind-blowing, we have a simple 10.0.0.0/24 network here. My Ubuntu Server 18.04 box will have a physical IP of 10.0.0.1/24 and a loopback adapter with IP address of 1.1.1.1/32. The Cisco IOSv box will have a physical address of 10.0.0.2/24 and a loopback adapter with IP address 2.2.2.2/32. Remember, neither of these boxes “knows” about the other’s loopack address, so we’ll have to do some sort of routing. Static routes are a simple solution, but most networking peeps are going to be looking to do a routing protocol. I’ll do OSPF just for kicks.

Configuration

Since there’s no external software or packages to install, I’ll jump right into configuration. This blog post assumes you have IPv4 routing enabled on both the Ubuntu Server and the Cisco router, and you have configured the network interfaces for 10.0.0.0/24.

Ubuntu18.04-1

On Ubuntu 18.04, you can add a “dummy” adapter with a single command like this:

 $ip link add name loop1 type dummy

Then turn it on and give it an IP address:

$ip link set loop1 up
$ip addr add 1.1.1.1/32 dev loop1

And you’re done. Keep in mind these commands don’t stick when you reboot. You can put these commands in a startup script, I’ll cover that in a another post.

CiscoIOSv15.6(2)T-3

Creating a loopback adapter on a Cisco IOS is simple as well, although I will say it would be nice if it supported CIDR notation (like other Cisco OS’s). Go into config mode and issue these commands:

Router(config)#int loop1
Router(config-if)#ip add 2.2.2.2 255.255.255.255

Verification

Ubuntu18.04-1

A good old fashion ping from the Ubuntu box to its own loopback adapter will tell you if it’s up:

$ping 1.1.1.1
64 bytes from 1.1.1.1: icmp_seq=1 ttl=64 time=0.038 ms                                                                                                                 
64 bytes from 1.1.1.1: icmp_seq=2 ttl=64 time=0.049 ms

The “ip route” command won’t show your /32, so try issuing the “ip addr show” command. To just show your loop1 adapter, add that on the end:

$ip addr show loop1
4: loop1: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether de:9d:7c:37:7a:c2 brd ff:ff:ff:ff:ff:ff
    inet 1.1.1.1/32 scope global loop1
       valid_lft forever preferred_lft forever
    inet6 fe80::dc9d:7cff:fe37:7ac2/64 scope link 
       valid_lft forever preferred_lft forever

CiscoIOSv15.6(2)T-3

From enabled mode you can just issue “show ip int brief”:

Router#show ip int bri
Interface                  IP-Address      OK? Method Status                Protocol                                                                                                     
GigabitEthernet0/0         10.0.0.2        YES manual up                    up                                                                                                           
GigabitEthernet0/1         unassigned      YES NVRAM  administratively down down    
GigabitEthernet0/2         unassigned      YES NVRAM  administratively down down    
GigabitEthernet0/3         unassigned      YES NVRAM  administratively down down    
Loopback1                  2.2.2.2         YES manual up                    up

Optional – Static Routing

A static route is a quick and easy way (but not very scalable) to make the loopback adapters reachable across the network.

Ubuntu18.04-1

On Ubuntu, just issue a single command to add a static route to reach the Cisco router’s loopback. Make :

$ip route add 2.2.2.2/32 via 10.0.0.2

You can now ping it:

ping 2.2.2.2
64 bytes from 2.2.2.2: icmp_seq=1 ttl=255 time=1.36 ms
64 bytes from 2.2.2.2: icmp_seq=2 ttl=255 time=1.72 ms

CiscoIOSv15.6(2)T-3

It’s also a single command on the Cisco router’s configuration mode to add a static route to reach the Ubuntu box’s loopback adapter. You should be able to ping after adding it:

Router(config)#ip route 1.1.1.1 255.255.255.255 10.0.0.1
Router(config)#end
Router#ping 1.1.1.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 1.1.1.1, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/1 ms

Optional – OSPF Routing

I have removed the static routes, so routing is no longer in place.

Ubuntu18.04-1

For setting up OSPF routing on Ubuntu, please see my post on that. It’s not too bad, just some commands to install Cumulus Linux Free Range Routing (FRR) from the Canonical Snap Store. Assuming you got that done, jump into the FRR CLI:

$/snap/bin/frr.vtysh
frr#conf t
frr(config)#router ospf
frr(config-router)#network 10.0.0.0/24 area 0
frr(config-router)#network 1.1.1.1/32 area 0
frr(config-router)#end
frr#show ip route
O   1.1.1.1/32 [110/10] via 0.0.0.0, loop1 onlink, 00:02:16
C>* 1.1.1.1/32 is directly connected, loop1, 00:19:40
O>* 2.2.2.2/32 [110/101] via 10.0.0.2, eth0, 00:00:08
O   10.0.0.0/24 [110/100] is directly connected, eth0, 00:03:14
C>* 10.0.0.0/24 is directly connected, eth0, 00:20:25

You’ll get the above route output as soon as the OSPF neighborship is up. You should be able to ping the Cisco’s loopback at 2.2.2.2.

CiscoIOSv15.6(2)T-3

On a Cisco IOS router, OSPF is of course already installed, firing it up is just a couple commands in config mode. Once again, CIDR notation would be nice. Does the world really need wildcard masks? They probably keep it that way to support automated systems that use it, but they could add CIDR notation support. Just sayin’.

Router(config)#router ospf 1
Router(config-router)#network 10.0.0.0 0.0.0.255 area 0
Router(config-router)#network 2.2.2.2 0.0.0.0 area 0
Router(config-router)#end
Router#show ip route
      1.0.0.0/32 is subnetted, 1 subnets
O        1.1.1.1 [110/11] via 10.0.0.1, 00:00:35, GigabitEthernet0/0
      2.0.0.0/32 is subnetted, 1 subnets
C        2.2.2.2 is directly connected, Loopback1
      10.0.0.0/8 is variably subnetted, 2 subnets, 2 masks
C        10.0.0.0/24 is directly connected, GigabitEthernet0/0
L        10.0.0.2/32 is directly connected, GigabitEthernet0/0

You should now be able to ping 2.2.2.2 from the Ubuntu box, and ping 1.1.1.1 from the Cisco router.

As I mentioned earlier, your “ip” commands won’t survive a reboot.

Kea DHCP Server 1.6 on Ubuntu 18.04 with Cisco IOS

Background

The Internet Services Consortium is a 501 non-profit (according to Wikipedia) that has developed and maintains both BIND DNS Server and ISC-DHCP-Server. Both of these open-source projects are used all over the world and are deployed on many, many servers. While ISC-DHCP-Server is handy and easy to configure, ISC has developed a new DHCP server called Kea that they say will eventually replace ISC-DHCP-Server. ISC claims Kea is better in nearly every way, so I’m going to give it a shot.

While all of ISC’s software is open-source, like many open-source maintainers they generate revenue by selling professional support for the software that they develop. Check out their support page for more info.

Topology

Pretty basic here. Kea will be installed on my Ubuntu 18.04 server and an IPv4 network is configured. We’ll see if we can use DHCP to get an IP address on my Cisco IOSv 15.6 router. I’m doing a packet capture in the middle which is always helpful for troubleshooting and verification.

Installation

Installation was pretty easy, provided you are ok with downloading a script from the internet and running it on your system. This method has some serious security concerns, so if you’re planning to run it on a production system you’ll want to probably take a more cautious approach. However my environment is entirely in GNS3 on a cloned Ubuntu VM so I can destroy the whole thing when I’m done. Please be careful out there, don’t talk to strangers.

It took me some time to find the official documentation, but as usual it was at readthedocs.io.

Running the ISC-Consortium’s bash script went pretty smooth for me, this process installs the package repository that ISC provides:

$ curl -1sLf \
>   'https://dl.cloudsmith.io/public/isc/kea-1-6/cfg/setup/bash.deb.sh' \
>   | sudo bash
Executing the  setup script for the 'isc/kea-1-6' repository ...

  OK: Checking for required executable 'curl' ...
  OK: Checking for required executable 'apt-get' ...
  OK: Detecting your OS distribution and release using system methods ...
 ^^^^: OS detected as: ubuntu 18.04 (bionic)
FAIL: Checking for apt dependency 'apt-transport-https' ...
  OK: Updating apt repository metadata cache ...
  OK: Attempting to install 'apt-transport-https' ...
  OK: Checking for apt dependency 'gnupg' ...
  OK: Importing 'isc/kea-1-6' repository GPG key into apt ...
  OK: Checking if upstream install config is OK ...
  OK: Installing 'isc/kea-1-6' repository via apt ...
  OK: Updating apt repository metadata cache ...
 ^^^^: The repository has been installed successfully - You're ready to rock!

$

Once the repository is successfully installed, you can now install the packages:

$apt install isc-kea-dhcp4-server isc-kea-dhcp6-server isc-kea-admin

Configuration

We’ll start with something really simple. I’ll set the range to be from 10.0.0.100 to 10.0.0.200, the default gateway to 10.0.0.1 (the Ubuntu box) and database set to “memfile” which for Kea means written to a csv file. This is pretty much the exact same functionality as ISC-DHCP-Server.

Ubuntu18.04-1


The default configuration file on my Ubuntu 18.04 box was located at /etc/kea/kea-dhcp4.conf. I deleted all the pre-added stuff and replaced with the following basic configuration:

{
# DHCPv4 configuration starts on the next line
"Dhcp4": {

# First we set up global values
    "valid-lifetime": 4000,
    "renew-timer": 1000,
    "rebind-timer": 2000,

# Next we set up the interfaces to be used by the server.
    "interfaces-config": {
        "interfaces": [ "eth0" ]
    },

# And we specify the type of lease database
    "lease-database": {
        "type": "memfile",
        "persist": true,
        "name": "/var/lib/kea/dhcp4.leases"
    },

# Finally, we list the subnets from which we will be leasing addresses.
    "subnet4": [
        {
            "subnet": "10.0.0.0/24",
            "pools": [
                {
                     "pool": "10.0.0.100 - 10.0.0.200"
                }],
                
            "option-data":[
                {
                     "name": "routers",
                     "data": "10.0.0.1"
                }]
            
            }
        ]
    }
}
# DHCPv4 configuration ends with the next line

And restart Kea with

$systemctl restart isc-kea-dhcp4-server

#and also check status:

$systemctl status isc-kea-dhcp4-server

If everything is good, you should see a green dot from systemd telling you Kea is up and running.


In this step it’s really easy to get hung up because of invalid JSON. Make sure you close all your {‘s and [‘s.

If all is well, Kea should be running and ready to hand out DHCP leases.

CiscoIOSv16.6(2)T-1

Configuration of a DHCP client on an IOS router is dead simple. On the interface just issue “ip address dhcp”. The interface section of your show run will look like this:

interface GigabitEthernet0/0
 ip address dhcp
 duplex auto
 speed auto

Verification

Ubuntu18.04-1

Since we’ve specified in our config that we want to store DHCP leases in /var/lib/kea/dhcp4.leases, my first step is to check to see if there’s a lease in there for the Cisco router.

$cat /var/lib/kea/dhcp4.leases
address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context
10.0.0.100,0c:b0:c4:ed:c8:00,00:63:69:73:63:6f:2d:30:63:62:30:2e:63:34:65:64:2e:63:38:30:30:2d:47:69:30:2f:30,4000,1583687165,1,1,1,router,0,

It’s a little hard to read, but it’s there.

CiscoIOSv16.6(2)T-1

On the Cisco device you’ll be looking for a log message like this:

*Mar  7 22:39:47.135: %DHCP-6-ADDRESS_ASSIGN: Interface GigabitEthernet0/0 assigned DHCP address 10.0.0.100, mask 255.255.255.0, hostname

You’ll also want to check IP interfaces for an IP address and issue “show ip interface brief”:

Interface                  IP-Address      OK? Method Status                Protocol
GigabitEthernet0/0         10.0.0.100      YES DHCP   up                    up      
GigabitEthernet0/1         unassigned      YES unset  administratively down down    
GigabitEthernet0/2         unassigned      YES unset  administratively down down    
GigabitEthernet0/3         unassigned      YES unset  administratively down down

You’ll also want to check the packet capture for the standard DHCP set of 4 messages – Discover, Offer, Request, Acknowledge. Wireshark will recognize “bootp” as a filter:

Troubleshooting

The first sign of trouble will be that you’ll see nothing but “Discover” messages coming from the Cisco router, and no response from Kea. At that point, you’ll want to make sure Kea is running:

Then check the syslog file. On Ubuntu 18.04, this is in /var/log/syslog. Make sure to grep for “kea” because the file is usually pretty big, I used “cat /var/log/syslog | grep kea”:

In this particular instance, I had written invalid JSON in the Kea configuration file, so Kea failed to start.

I also had an instance where I was trying using a MySQL server to store leases, and my configuration wasn’t correct. Kea started but simply didn’t respond to DHCP requests. If you’re using SQL, make sure communication is happening between Kea and your SQL server.

Optional – MySQL Server for Leases storage

One of the options that Kea supports is storing lease data in a database. For this you can use a MySQL server (or others). ISC states in their documentation that the SQL database option is really for needs specific to large deployments, so it’s optional of course.

To install MySQL:

$sudo apt update
$sudo apt install mysql-server

You can check to make sure it’s running:

$systemctl status mysql

Status should be green:

Then you need to jump into mysql and create a database for Kea to use:

$mysql -u root -p
Enter password:
mysql>CREATE DATABASE james_kea;
Query OK, 1 row affected (0.00 sec)
mysql> CREATE USER 'james'@'localhost' IDENTIFIED BY 'james';
Query OK, 0 rows affected (0.01 sec)
mysql> GRANT ALL ON james_kea.* TO 'james'@'localhost';
Query OK, 0 rows affected (0.00 sec)
mysql> exit
Bye
$

Then initialize the Kea database with the kea-admin tool:

kea-admin db-init mysql -u james -p james -n james_kea

You’re going to need to change the “lease-database” section of /etc/kea/kea-dhcp4.conf to tell it to store leases in MySQL:

    "lease-database": {
    #    "type": "memfile", #I just commented these out.
    #    "persist": true,#I just commented these out.
    #    "name": "/var/lib/kea/dhcp4.leases" #I just commented these out.
        "type":"mysql",
        "name":"james_kea",
        "host":"127.0.0.1",
        "user":"james",
        "password":"james"
        "password":"james"
    },

Restart Kea for it to take effect.

As I mentioned before, if Kea can’t communicate with MySQL, it will start but simply not respond to DHCP requests. If you’re in that situation, make sure to check that your SQL and Kea configurations are correct.

You can take a look at the database via the mysql CLI to see if leases are being stored there:

$mysql -u root -p
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| james_kea          |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)
mysql> use james_kea
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+-------------------------------+
| Tables_in_james_kea           |
+-------------------------------+
| dhcp4_audit                   |
| dhcp4_audit_revision          |
| dhcp4_global_parameter        |
| dhcp4_global_parameter_server |
####EDITED FOR BREVITY####
| lease4                        |
| lease4_stat                   |
| lease6                        |
| lease6_stat                   |
| lease6_types                  |
| lease_hwaddr_source           |
| lease_state                   |
| logs                          |
| modification                  |
| parameter_data_type           |
| schema_version                |
+-------------------------------+
mysql> SELECT * FROM lease4;
+-----------+--------+-----------------------------+----------------+---------------------+-----------+----------+----------+----------+-------+--------------+
| address   | hwaddr | client_id                   | valid_lifetime | expire              | subnet_id | fqdn_fwd | fqdn_rev | hostname | state | user_context |
+-----------+--------+-----------------------------+----------------+---------------------+-----------+----------+----------+----------+-------+--------------+
| 167772260 | 
              ����      |  cisco-0cb0.c4ed.c800-Gi0/0 |           4000 | 2020-03-09 02:16:09 |         1 |        1 |        1 | router   |     0 | NULL         |
+-----------+--------+-----------------------------+----------------+---------------------+-----------+----------+----------+----------+-------+--------------+
1 row in set (0.00 sec)

mysql> 

It shows up better on a terminal with a lot of width, but there is lease data stored for my Cisco router. Success.

Optional – MySQL Server/REST API for Configuration Storage

I was really hoping to get to test this, however I learned this functionality is provided through external libraries, or “hooks” that the ISC team offers through a premium package.

The “Configuration Backend”, as they call it, offers the ability to store configurations for networks, some options, and other stuff in MySQL and not a JSON config file. Changes to these configurations can be made through REST calls to the kea-ctrl-agent which processes and strips away the HTTP headers and forwards the JSON data to Kea. Essentially, you could make configuration changes from a script or program on the fly without having to restart or reconfigure the server. Pretty cool, I can see why they charge for it.

As of this writing in March of 2020, the ISC website says you need a premium support subscription for the premium hooks package.

DMVPN (Sans-Ipsec) on Linux/Cisco with Free Range Routing

Background

Free Range Routing is a really cool fork of the Quagga project by Cumulus Linux, both are implementations of a number of network routing protocols for Linux. While Quagga implemented the classic protocols such as OSPF, BGP, RIP, and PIMD that have been in use for decades, FRR implements some new ones and adds features to the old protocols.

Free Range Routing is a bit tricky to build from source at the moment (February 2020) due to some newer packages such as libyang not being readily available in major distributions such as Ubuntu. However Ubuntu’s Snap technology makes installing a very recent version of Free Range Routing, well, a snap. I have another post that shows you how to install it and run EIGRP on Linux which is pretty cool because it’s Cisco proprietary (yes, Cisco still owns it despite publishing an RFC).

Another protcol that Free Range Routing implements is NHRP, which is the key component in another Cisco-originated technology, Dynamic Multipoint Virtual Private Network, or DMVPN for short. NHRP serves as the protocol that determines GRE tunnel endpoints.

For this post, I’m going to skip the encryption part of DMVPN that IPsec provides. It’s done with Strongswan on Linux, and it’s tricky to get right and I’ll cover it in another post. But you can get a nice unencrypted overlay network running with NHRP and GRE before you ever start encrypting. I don’t think there’s an official term for DMVPN without IPsec, but I’ve heard networking folk refer to it as “naked” DMVPN. Obviously you don’t want to do this on the Internet (despite the “Internet” node in my topology), but there are some viable use cases for naked DMVPN on a private network and I’ve personally seen it on production networks. Free Range Routing’s NHRP implementation is still experimental, so please be careful.

Topology

I don’t like how cluttered this drawing is, but I’m pretty sure all the info on there is relevant to the lab. The idea here is to get PC’s 1, 2 and 3 to be able to ping each other. The 2 Ubuntu routers and 1 Cisco router can’t establish routing with “The Internets” so they’ll need a tunneling mechanism to learn about the networks of the other routers that have PC’s connected. First I’ll get NHRP connected and get GRE tunnels working. Then I’ll run BGP to advertise 192.168.0.0/24, 172.16.0.0/24 and 172.17.0.0/24 over the “overlay” GRE network.

Configuration

This post assumes the basic IP connectivity between the Ubuntu and Cisco routers has been set up, as well as LAN connectivity for the PC’s. Specifically, networks 11.0.0.0/30, 22.0.0.0/30, 33.0.0.0/30, 192.168.0.0/24, 172.16.0.0/24, and 172.17.0.0/24 have all been configured. With all that boring stuff done, we’ll jump into more interesting things.

Ubuntu18.04-FRR-1

This is the DMVPN hub. First order of business is to create a GRE tunnel interface:

$ip tunnel add james_gre mode gre key 42 ttl 64 dev eth0
$ip addr add 10.0.0.1/32 dev james_gre
$ip link set james_gre up

You might be wondering why the GRE IP address is 10.0.0.1/32 and not 10.0.0.1/24. Well apparently it has something to do with the way the good folks at Cumulus Linux implemented it. I’m sure they had their reasons, and they outline a couple in their documentation and on their Github NHRP readme page. That’s just the way it’s done with FRR, so I don’t argue.

Next we need to jump into the FRR CLI and configure NHRP. Remember to type the full path as it didn’t get added to $PATH when FRR was installed (or you can add it to your $PATH, or cd to /snap/bin):

$/snap/bin/frr.vtysh
Hello, this is FRRouting (version 7.2.1).
Copyright 1996-2005 Kunihiro Ishiguro, et al.

# conf t
(config)#int james_gre
(config-if)#ip nhrp network-id 1
(config-if)#ip nhrp nhs dynamic nbma 11.0.0.1
(config-if)#ip nhrp registration no-unique
(config-if)#exit
(config)#

I’ve skipped setting up “NHRP redirect” (direct spoke-to-spoke communication) as I was unable to get it working in my lab between a Cisco spoke and Ubuntu spoke. I can make it work between two Ubuntu spokes or two Cisco spokes but that’s not really much fun. As I said this implementation is still a work in progress.

Next I’ll set up BGP routing to advertise the networks where the PC’s are connected. I’m using BGP because it lends itself to DMVPN for a number of reasons and also FRR’s NHRP implementation doesn’t support multicast yet, so no EIGRP or OSPF. The Hub will serve as a BGP “route reflector”, which basically means you can avoid a full mesh of BGP neighborships in iBGP (BGP where the AS is all the same). Sounds fancy but it actually simplifies things here, I promise.

(config)#router bgp 1
(config-router)#neighbor 10.0.0.2 remote-as 1
(config-router)#neighbor 10.0.0.3 remote-as 1
(config-router)#address-family ipv4 unicast
(config-router-af)#network 192.168.0.0/24
(config-router-af)#neighbor 10.0.0.2 route-reflector-client
(config-router-af)#neighbor 10.0.0.3 route-reflector-client
#

Ubuntu18.04-FRR-2

The Ubuntu spoke is pretty similar, except without any route reflector stuff.

$ip tunnel add james_gre mode gre key 42 ttl 64 dev eth0
$ip addr add 10.0.0.2/32 dev james_gre
$ip link set james_gre up
$/snap/bin/frr.vtysh
Hello, this is FRRouting (version 7.2.1).
Copyright 1996-2005 Kunihiro Ishiguro, et al.

# conf t
(config)#int james_gre
(config-if)#ip nhrp network-id 1
(config-if)#ip nhrp nhs dynamic nbma 11.0.0.1
(config-if)#ip nhrp registration no-unique
(config-if)#exit
(config)#ip route 10.0.0.0/24 10.0.0.1
(config)#router bgp 1
(config-router)#neighbor 10.0.0.1 remote-as 1
(config-router)#address-family ipv4 unicast
(config-router-af)#network 172.16.0.0/24
(config-router-af)#exit
#

You may have noticed an additional command – I added a static route via “10.0.0.0/24 10.0.0.1”. This was for compatibility with the Cisco device. I could avoid this step and use NHRP redirect with another Ubuntu spoke and they would communicate directly. However with a Cisco spoke in play I had to add the route and all traffic goes through the hub. Maybe it’ll be better next release. Or if there’s a configuration I’m missing, please comment or shoot me an email, I’d love to know because I practically broke my keyboard trying different configurations.

CiscoIOSv15.6(2)T-1

This is the tunnel and BGP configuration for my Cisco IOSv router:

interface Tunnel0
 ip address 10.0.0.3 255.255.255.0
 no ip redirects
 ip nhrp network-id 1
 ip nhrp nhs dynamic nbma 11.0.0.1
 ip nhrp registration no-unique
 ip nhrp shortcut
 tunnel source GigabitEthernet0/0
 tunnel mode gre multipoint
 tunnel key 42
!
router bgp 1
 bgp log-neighbor-changes
 network 172.17.0.0 mask 255.255.255.0
 neighbor 10.0.0.1 remote-as 1
!

You probably noticed that the IP address on the tunnel interface is not /32 like the Ubuntu routers, it’s a /24 which is ultimately the size of the DMVPN network. That’s the way Cisco does it, so I don’t argue.

Verification

You can run some useful show commands from the FRR CLI on the Ubuntu routers as well as the Cisco CLI. “show ip route” is really useful, you’ll notice “N” routes on the Ubuntu routers which is how FRR NHRP advertises other spoke IP addresses (Read the FRR docs for more detail):

# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued route, r - rejected route

K>* 0.0.0.0/0 [0/0] via 11.0.0.2, eth0, 1d01h01m
C>* 10.0.0.1/32 is directly connected, james_gre, 1d00h57m
N>* 10.0.0.2/32 [10/0] is directly connected, james_gre, 02:56:29
N>* 10.0.0.3/32 [10/0] is directly connected, james_gre, 12:37:57
C>* 11.0.0.0/30 is directly connected, eth0, 1d01h02m
B>  172.16.0.0/24 [200/0] via 10.0.0.2 (recursive), 01:17:14
  *                         via 10.0.0.2, james_gre onlink, 01:17:14
B>  172.17.0.0/24 [200/0] via 10.0.0.3 (recursive), 01:16:53
  *                         via 10.0.0.3, james_gre onlink, 01:16:53
C>* 192.168.0.0/24 is directly connected, eth1, 12:10:26
#

You can also run “show dmvpn” which should show the other endpoints:

# show dmvpn
Src                      Dst                      Flags  SAs  Identity                <br>
11.0.0.1                 22.0.0.1                 n      0                            <br>
11.0.0.1                 33.0.0.1                 n      0          

And a good old fashioned ping should confirm everything for you. Here I’m pinging from the PC behind the Ubuntu spoke to the PC behind the Cisco spoke:

PC2> ping 172.17.0.2
84 bytes from 172.17.0.2 icmp_seq=1 ttl=62 time=3.523 ms
84 bytes from 172.17.0.2 icmp_seq=2 ttl=62 time=3.828 ms

Troubleshooting

You can apparently send NHRP debugs to syslog, but I didn’t find them particularly helpful when I was troubleshooting. I found a packet capture to be the most useful. The the key is to get NHRP going, in a packet capture you should see a NHRP registration request and reply with Code=Success. If you see registration requests with no request at all, you have a problem. On my cisco I originally forgot to put in the tunnel key command, and it kept sending a request and getting an ICMP error response. Don’t forget your tunnel key.

DMVPN between Ubuntu and Cisco, pretty cool right?

EIGRP Routing on Linux via Free Range Routing

Background

If you are familiar with networking on Linux, you may know of the Quagga project, which is an old open source project that implemented some routing protocols. You may also have heard that a cool company called Cumulus Linux forked Quagga into an open source project they call “Free Range Routing” (Their github page has the most recent code, the project page has more info, and their documentation is extensive) . They have taken the development of the project into high gear and implemented many new features, new protocols, and generally gone above and beyond the original Quagga project which had seen little to no development activity for years.

While you may be able to find some binary packages out there, building from source is the best way to get the most recent code and the most recent features. However building from source can be time consuming if your not familiar with the project and/or the process of building from source. If you just want to poke around a little bit or do some testing, there is a nice alternative using Ubuntu’s “snap” technology. Snaps are kind of like containers, although they’re different from Docker or LXC. The idea is to package up all the libraries the application needs so it can run on any Linux distribution that can run the snap daemon. Stability, portability, and ease of installation are the goal. You can check out more about snap at the Snapcraft page.

Free Range Routing offers snaps through the snap store and installation is downright easy.

If you’re familiar with Cisco technologies, you may know that their proprietary (and yes, it’s still proprietary) routing protocol EIGRP is only available on Cisco devices. In 2013 Cisco published a draft RFC detailing how to implement EIGRP, opening the gate for anyone interested to implement it. There were a few attempts to add it on to Quagga, however none of them really got past alpha stage. Free Range Routing is the first major attempt to implement it, although it’s still in experimental stage. Installing the Free Range Routing snap will get you the latest EIGRP code, and you can run it on your Linux box.

Installation

There is a readme specific to installing the snap package that comes when you install the snap itself, but it’s at the Github page as well of course.

Ubuntu 18.04

Make sure to update, and install snapd:

sudo apt update
sudo apt install snapd

Then install the frr snap:

sudo snap install frr

Then you’ll need to give it permission to control the network:

snap connect frr:network-control core:network-control

Restart your box to finish.

Topology

Pretty simple idea here. We want PC1 to be able to reach PC2. There are no routes in place, so that’s where EIGRP comes in. The Ubuntu box doesn’t know about 172.16.0.0/24 and the Cisco IOSv Router doesn’t know about 192.168.0.0/24, and we’ll use EIGRP to tell each router about those networks. This blog assumes IPv4 routing has been enabled on the Ubuntu box and IP addresses have been configured on the interfaces shown.

Configuration

Ubuntu 18.04

You’ll find the snap files under “/snap”, so cd to “/snap/bin” where you’ll find all the frr stuff. Most importantly you’ll find frr.vtysh which is the command line interface for frr. Running that will take you into the frr CLI, or you can use an absolute path to it:

sudo /snap/bin/frr.vtysh

You’ll be greeted with the frr CLI which is very Cisco-like and based on the original Quagga CLI. You can configure EIGRP using Cisco-like commands:

#conf t
(config)#
(config-router)#network 192.168.0.0/24
(config-router)#network 172.16.0.0/24
(config-router)#end
#

CiscoIOSv15.6(2)T-1

I’ve abbreviated the output of my show run a bit to make things more readable:

interface GigabitEthernet0/0
 ip address 10.0.0.2 255.255.255.252
!
interface GigabitEthernet0/1
 ip address 172.16.0.1 255.255.255.0
!        
router eigrp 10
 network 10.0.0.0 0.0.0.3
 network 172.16.0.0 0.0.0.255

Verification

Ubuntu 18.04

You can use the same show commands within the frr CLI to see if you’re getting routes:

# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued route, r - rejected route

E   10.0.0.0/30 [90/28160] is directly connected, eth0, 00:43:35
C>* 10.0.0.0/30 is directly connected, eth0, 00:46:38
E>* 172.16.0.0/24 [90/5376] via 10.0.0.2, eth0, 00:42:36
E   192.168.0.0/24 [90/28160] is directly connected, eth1, 00:42:47
C>* 192.168.0.0/24 is directly connected, eth1, 00:45:56
# 

Of course you can always drop out with “exit” to the Bash shell and do “ip route”:

$ip route
10.0.0.0/30 dev eth0 proto kernel scope link src 10.0.0.1 
172.16.0.0/24 via 10.0.0.2 dev eth0 proto 192 metric 20 
192.168.0.0/24 dev eth1 proto kernel scope link src 192.168.0.1 
$ 

CiscoIOSv15.6(2)T-1

It’s nice when the commands are the same (similar) everywhere.

Router#show ip route
Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area 
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP
       a - application route
       + - replicated route, % - next hop override, p - overrides from PfR

Gateway of last resort is not set

      10.0.0.0/8 is variably subnetted, 2 subnets, 2 masks
C        10.0.0.0/30 is directly connected, GigabitEthernet0/0
L        10.0.0.2/32 is directly connected, GigabitEthernet0/0
      172.16.0.0/16 is variably subnetted, 2 subnets, 2 masks
C        172.16.0.0/24 is directly connected, GigabitEthernet0/1
L        172.16.0.1/32 is directly connected, GigabitEthernet0/1
D     192.168.0.0/24 [90/28416] via 10.0.0.1, 00:44:51, GigabitEthernet0/0
Router#

Finally, make sure PC1 can ping PC2:

PC1> ping 172.16.0.2
84 bytes from 172.16.0.2 icmp_seq=1 ttl=62 time=1.843 ms
84 bytes from 172.16.0.2 icmp_seq=2 ttl=62 time=1.879 ms
84 bytes from 172.16.0.2 icmp_seq=3 ttl=62 time=1.904 ms
84 bytes from 172.16.0.2 icmp_seq=4 ttl=62 time=1.906 ms

EIGRP on Linux!

IPsec on Linux – Strongswan Configuration (IKEv2, Policy-Based, PSK)

Background

The idea behind a VPN is to create a tunnel, that is to say, packets that have IP packets embedded inside IP packets. The private part means the this tunnel is encrypted so that an attacker listening along the path between the two tunnel endpoints will not be able to steal any meaningful data.

Some info about Strongswan – it’s an implementation of the IKE protocol (Internet Key Exchange) which is designed to secure exchange cryptographic key information across an untrusted network, usually the Internet, but not always. The actual encrypting of packets and sending them using a protocol called ESP (Encapsulating Security Payload) is already built into Linux, but Strongswan is needed to give ESP the correct key information to build the tunnel.

There are other IKE implementations, but I find Strongswan the be the best and most actively updated as of this writing in January 2020. You can find the Strongswan website and their documentation at https://www.strongswan.org/, as well as their Github page with the most recent code at https://github.com/strongswan/strongswan.

There are lots of ways to configure IPsec, but for this lab we’ll be using IKE version 2 with a policy-based configuration. The policy-based part means a policy negotiated by the two tunnel endpoints (routers) determines what traffic is allowed or not allowed into the tunnel. Another way to do this is route-based, where the routing tables determine tunneled traffic, allowing for more flexibility. But that’s a story for another day.

Installation

Strongswan can be installed with relative ease on a number of Linux distributions that have package managers, on Ubuntu this is pretty simple:

Ubuntu 18.04 Server

apt-get install strongswan

You can also build from source to get a more recent version, although you’ll probably want to get the instructions from their Github page, as building from source is more involved.

Topology

The idea here is to get UbuntuServer18.04-1 on the bottom left to be able to reach UbuntuServer18.04-2 on the bottom right. You can think of the U_18Router and CiscoIOSv15.6(2)T-3 as being connected to an Internet-like network, for this lab’s purposes. Needless to say, the subnet 192.168.0.0/24 on the left can’t reach 172.16.0.0/24 on the right without some sort of tunnel mechanism, and traffic needs to be encrypted as someone is capturing packets in the middle. A man in the middle, if you will 😛

I’ll be configuring Strongswan on an Ubuntu18.04 Server (U18_Router) on one end and a Cisco IOSv router on the other end. That way we can see that it actually works with other vendors.

Configuration

U18_Router (Ubuntu18.04)

First edit the text file /etc/ipsec.conf using your favorite text editor, I use vim. It should look something like this:

config setup
 strictctlpolicy=yes
 uniqueids=no

conn &default
 ikelifetime=1440m
 keylife=60m

conn james_tunnel
 left=1.1.1.1  #Outside interface of this router
 leftsubnet=192.168.0.0/24  #Inside network behind this router
 right=3.3.3.2  #Outside interface of the cisco router
 rightsubnet=172.16.0.0/24  #Inside network of the cisco router
 ike=aes256-sha2_256-modp1024!  #Corresponds to cisco ikev2 proposal
 esp=aes-sha2_256!  #Corresponds to cisco transform set
 keyingtries=0
 ikelifetime=1h
 lifetime=8h
 authby=secret
 auto=start
 keyexchange=ikev2
 type=tunnel  #Corresponds to transform set "mode"

Then edit /etc/ipsec.secrets, and put your pre-shared key in there along with tunnel endpoint IP addresses:

1.1.1.1 3.3.3.2 : PSK '12345'

Then restart strongswan to load your configuration

ipsec restart

CiscoIOSv15.6(2)T-3

This is an abbreviated version of the Cisco IOS router configuration, as they tend to include a lot of info that’s not relevant here:

crypto ikev2 proposal james-proposal 
 encryption aes-cbc-256
 integrity sha256
 group 2
!
crypto ikev2 policy james-policy 
 proposal james-proposal
!
crypto ikev2 keyring james-ring
 peer remote-router-james
  address 1.1.1.1
  pre-shared-key 12345
 !
!         
crypto ikev2 profile james-profile
 match identity remote address 1.1.1.1 255.255.255.255 
 authentication local pre-share
 authentication remote pre-share
 keyring local james-ring
!
crypto ipsec transform-set james-trans esp-aes esp-sha256-hmac 
 mode tunnel
!
crypto map james-map 1 ipsec-isakmp 
 set peer 1.1.1.1
 set transform-set james-trans 
 set ikev2-profile james-profile
 match address james-vpn
!         
interface GigabitEthernet0/0
 ip address 3.3.3.2 255.255.255.0
 duplex auto
 speed auto
 media-type rj45
 crypto map james-map
!
interface GigabitEthernet0/1
 ip address 172.16.0.1 255.255.255.0
 duplex auto
 speed auto
 media-type rj45
!
ip route 0.0.0.0 0.0.0.0 3.3.3.1
!
ip access-list extended james-vpn
 permit ip 172.16.0.0 0.0.0.255 192.168.0.0 0.0.0.255

Verification

U18_Router (Ubuntu18.04)

Verifying the status of your tunnel is fairly simple, just issue the command ‘ipsec statusall’. Below is an example of a tunnel that’s up an running:

root@uvm1804:/var/log# ipsec statusall
Status of IKE charon daemon (strongSwan 5.6.2, Linux 4.15.0-20-generic, x86_64):
  uptime: 19 hours, since Jan 15 21:48:59 2020
  malloc: sbrk 1626112, mmap 0, used 861840, free 764272
  worker threads: 11 of 16 idle, 5/0/0/0 working, job queue: 0/0/0/0, scheduled: 5
  loaded plugins: charon aes rc2 sha2 sha1 md4 md5 mgf1 random nonce x509 revocation constraints pubkey pkcs1 pkcs7 pkcs8 pkcs12 pgp dnskey sshkey pem openssl fips-prf gmp agent xcbc hmac gcm attr kernel-netlink resolve socket-default connmark stroke updown eap-mschapv2 xauth-generic counters
Listening IP addresses:
  1.1.1.1
  192.168.0.1
Connections:
      tunnel:  1.1.1.1...3.3.3.2  IKEv2
      tunnel:   local:  [1.1.1.1] uses pre-shared key authentication
      tunnel:   remote: [3.3.3.2] uses pre-shared key authentication
      tunnel:   child:  192.168.0.0/24 === 172.16.0.0/24 TUNNEL
Security Associations (1 up, 0 connecting):
      tunnel[26]: ESTABLISHED 6 minutes ago, 1.1.1.1[1.1.1.1]...3.3.3.2[3.3.3.2]
      tunnel[26]: IKEv2 SPIs: 8c847c9fa74fd6f6_i 5158e0ac789bebb4_r*, pre-shared key reauthentication in 40 minutes
      tunnel[26]: IKE proposal: AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1024
      tunnel{42}:  INSTALLED, TUNNEL, reqid 25, ESP SPIs: cda62364_i bb016c85_o
      tunnel{42}:  AES_CBC_128/HMAC_SHA2_256_128, 8400 bytes_i (100 pkts, 271s ago), 8400 bytes_o (100 pkts, 271s ago), rekeying in 36 minutes
      tunnel{42}:   192.168.0.0/24 === 172.16.0.0/24

CiscoIOSv15.6(2)T-3

On an Cisco IOS device, there are a few different show commands you can use to verify tunnel status however I find ‘show crypto session’ to quickly get you the info you need. You can get more info by adding ‘detail’ on the end:

Router#show crypto session
Crypto session current status

Interface: GigabitEthernet0/0
Profile: james-profile
Session status: UP-ACTIVE     
Peer: 1.1.1.1 port 500 
  Session ID: 95  
  IKEv2 SA: local 3.3.3.2/500 remote 1.1.1.1/500 Active 
  IPSEC FLOW: permit ip 172.16.0.0/255.255.255.0 192.168.0.0/255.255.255.0 
        Active SAs: 2, origin: crypto map

Router#

Packet Capture

A packet capture can really help you figure out how far you have progressed and what next steps should be taken to solve a configuration issue or troubleshoot a problem. After you configure Strongswan on Linux and Crypto map on your Cisco, you should be seeing ISAKMP (an extension of IKE) protocol messages in the packet capture that are negotiating tunnel parameters:

Finally, when you have a tunnel established, you should see ESP packets carrying the actual data when UbuntuServer18.04-1 and UbuntuServer18.04-2 ping each other. You should NOT see ICMP (ping) packets in your capture.

Troubleshooting

U18_Router (Ubuntu18.04)

The Strongswan IKE daemon is called “charon”, and it deposits its log messages in the system syslog file. On different distributions this is in different locations or uses a different name, in Ubuntu 18.04 it’s in /var/log/syslog. You can search this file for clues as to what’s going wrong, I suggest using grep to find what you’re looking for. A useful starting point would be to issue ‘cat /var/log/syslog | grep charon’ to show only charon messages. The following example is an example of a typo in the Strongswan configuration resulting in the charon exiting and not attempting to bring up the tunnel. You would not see any ISAKMP packets in your packet capture:

Jan 16 18:00:22 uvm1804 charon: 00[DMN] Starting IKE charon daemon (strongSwan 5.6.2, Linux 4.15.0-20-generic, x86_64)
Jan 16 18:00:22 uvm1804 charon: 00[CFG] loading ca certificates from '/etc/ipsec.d/cacerts'
Jan 16 18:00:22 uvm1804 charon: 00[CFG] loading aa certificates from '/etc/ipsec.d/aacerts'
Jan 16 18:00:22 uvm1804 charon: 00[CFG] loading ocsp signer certificates from '/etc/ipsec.d/ocspcerts'
Jan 16 18:00:22 uvm1804 charon: 00[CFG] loading attribute certificates from '/etc/ipsec.d/acerts'
Jan 16 18:00:22 uvm1804 charon: 00[CFG] loading crls from '/etc/ipsec.d/crls'
Jan 16 18:00:22 uvm1804 charon: 00[CFG] loading secrets from '/etc/ipsec.secrets'
Jan 16 18:00:22 uvm1804 charon: 00[CFG]   loaded IKE secret for 1.1.1.1 3.3.3.2
Jan 16 18:00:22 uvm1804 charon: 00[LIB] loaded plugins: charon aes rc2 sha2 sha1 md4 md5 mgf1 random nonce x509 revocation constraints pubkey pkcs1 pkcs7 pkcs8 pkcs12 pgp dnskey sshkey pem openssl fips-prf gmp agent xcbc hmac gcm attr kernel-netlink resolve socket-default connmark stroke updown eap-mschapv2 xauth-generic counters
Jan 16 18:00:22 uvm1804 charon: 00[LIB] dropped capabilities, running as uid 0, gid 0
Jan 16 18:00:22 uvm1804 charon: 00[JOB] spawning 16 worker threads
Jan 16 18:00:22 uvm1804 charon: 05[CFG] received stroke: add connection 'tunnel'
Jan 16 18:00:22 uvm1804 charon: 05[CFG] algorithm 'aes265' not recognized
Jan 16 18:00:22 uvm1804 charon: 05[CFG] skipped invalid proposal string: aes265-sha2_256-modp1024
Jan 16 18:00:22 uvm1804 charon: 07[CFG] received stroke: initiate 'tunnel'
Jan 16 18:00:22 uvm1804 charon: 07[CFG] no config named 'tunnel'
root@uvm1804:/var/log# 

You’ll notice on line 13 it says “algorithm ‘aes265’ not recognized”. That’s because AES265 isn’t a thing. It’s AES256.

Another example that may be harder to detect and requires some good “stare and compare” skills, would be mismatched encryption/authentication parameters. Your configurations may be valid but if parameters don’t match up, your tunnel won’t come up. The following is an example of when ESP parameters aren’t matching. On the Ubuntu side I’ve specified AES256 for ESP but the Cisco’s transform set calls for AES, which is short for AES128:

Jan 16 18:08:38 uvm1804 charon: 05[CFG] added configuration 'tunnel'
Jan 16 18:08:38 uvm1804 charon: 07[CFG] received stroke: initiate 'tunnel'
Jan 16 18:08:38 uvm1804 charon: 07[IKE] initiating IKE_SA tunnel[1] to 3.3.3.2
Jan 16 18:08:38 uvm1804 charon: 07[ENC] generating IKE_SA_INIT request 0 [ SA KE No N(NATD_S_IP) N(NATD_D_IP) N(FRAG_SUP) N(HASH_ALG) N(REDIR_SUP) ]
Jan 16 18:08:38 uvm1804 charon: 07[NET] sending packet: from 1.1.1.1[500] to 3.3.3.2[500] (334 bytes)
Jan 16 18:08:38 uvm1804 charon: 09[NET] received packet: from 3.3.3.2[500] to 1.1.1.1[500] (348 bytes)
Jan 16 18:08:38 uvm1804 charon: 09[ENC] parsed IKE_SA_INIT response 0 [ SA KE No V V N(NATD_S_IP) N(NATD_D_IP) ]
Jan 16 18:08:38 uvm1804 charon: 09[IKE] received Cisco Delete Reason vendor ID
Jan 16 18:08:38 uvm1804 charon: 09[IKE] received Cisco FlexVPN Supported vendor ID
Jan 16 18:08:38 uvm1804 charon: 09[IKE] authentication of '1.1.1.1' (myself) with pre-shared key
Jan 16 18:08:38 uvm1804 charon: 09[IKE] establishing CHILD_SA tunnel{1}
Jan 16 18:08:38 uvm1804 charon: 09[ENC] generating IKE_AUTH request 1 [ IDi N(INIT_CONTACT) IDr AUTH SA TSi TSr N(MOBIKE_SUP) N(ADD_4_ADDR) N(EAP_ONLY) N(MSG_ID_SYN_SUP) ]
Jan 16 18:08:38 uvm1804 charon: 09[NET] sending packet: from 1.1.1.1[4500] to 3.3.3.2[4500] (272 bytes)
Jan 16 18:08:38 uvm1804 charon: 10[NET] received packet: from 3.3.3.2[4500] to 1.1.1.1[4500] (160 bytes)
Jan 16 18:08:38 uvm1804 charon: 10[ENC] parsed IKE_AUTH response 1 [ V IDr AUTH N(NO_PROP) ]
Jan 16 18:08:38 uvm1804 charon: 10[IKE] authentication of '3.3.3.2' with pre-shared key successful
Jan 16 18:08:38 uvm1804 charon: 10[IKE] IKE_SA tunnel[1] established between 1.1.1.1[1.1.1.1]...3.3.3.2[3.3.3.2]
Jan 16 18:08:38 uvm1804 charon: 10[IKE] scheduling reauthentication in 2848s
Jan 16 18:08:38 uvm1804 charon: 10[IKE] maximum IKE_SA lifetime 3388s
Jan 16 18:08:38 uvm1804 charon: 10[IKE] received NO_PROPOSAL_CHOSEN notify, no CHILD_SA built
Jan 16 18:08:38 uvm1804 charon: 10[IKE] failed to establish CHILD_SA, keeping IKE_SA
root@uvm1804:/var/log# 

You’ll notice that the last message states “failed to establish CHILD_SA” which generally means your ESP parameters don’t match, on the Cisco side you’ll need to check your transform set. If your IKE proposal doesn’t match, you’ll get a different message about no proposal being chosen. On the Cisco side you’ll need to check your IKE proposal as well.

CiscoIOSv15.6(2)T-3

You’ll notice that when the Ubuntu Strongswan router isn’t trying to bring up it’s tunnel due to an invalid configuration, the Cisco IOS device says it’s tunnel is DOWN:

Router#show crypto session
Crypto session current status

Interface: GigabitEthernet0/0
Session status: DOWN
Peer: 1.1.1.1 port 500 
  IPSEC FLOW: permit ip 172.16.0.0/255.255.255.0 192.168.0.0/255.255.255.0 
        Active SAs: 0, origin: crypto map

Router#

When parameters mismatch, you’ll see an UP-IDLE session status:

Router#show crypto session
Crypto session current status

Interface: GigabitEthernet0/0
Profile: james-profile
Session status: UP-IDLE
Peer: 1.1.1.1 port 4500 
  Session ID: 101  
  IKEv2 SA: local 3.3.3.2/4500 remote 1.1.1.1/4500 Active 

Router#

IPsec is a compex protocol, but when it works it’s pretty cool. Shoot me a message or post a comment if you’re having trouble with a VPN tunnel, I’m always happy to help if I can.