How To Ping Sweep With Python in GNS3

I debated for a long time whether to include coding in my networking blog. But since it seems the future of networking lies in code and automation, I believe it is time for some code.

Today we’ll look at how we can quickly ping sweep a subnet using python. If you are looking for some resources on learning python, you might check out this free ebook on python for network engineers or getting a course on Udemy for python. If you’d like me to create a python learning resource in the future, please let me know in the comments or contact form!

Topology

Topology in GNS3

We’ll use a simple IP subnet of 172.16.0.0/24 to sweep. Connected devices have been placed randomly at 172.16.0.51, 172.16.0.121 and 172.16.0.253. We’ll write the sweep python code on the Ubuntu 20.04 server at 172.16.0.1. Let’s get started!

Simple python ping sweep script

The basic logic of the code will be to loop through all hosts in the 172.16.0.0/24 subnet, from 172.16.0.1 to 172.16.0.254 (.0 and .255 are the network and broadcast addresses, so no need to ping them) and ping each IP address once. If it responds, we’ll print to the CLI that it worked.

We’ll use two modules, ipaddress and subprocess. ipaddress is a handy network tool for working with IP addresses. Knowing where to start and stop in the loop is relatively simple with a /24 subnet, what if it were 172.16.0.0/19? Just incrementing the fourth octet by 1 each time won’t work. You’ll go to 172.16.0.256, which isn’t a valid IP address. That’s where ipaddress helps out. subprocess lets us call the ping command from python.

Here’s our code:

import ipaddress
import subprocess

mynet = ipaddress.ip_network('172.16.0.0/24') #create an ipaddress object for 172.16.0.0/24

for host in mynet.hosts():                    #loop through each host of 172.16.0.0/24
    host = str(host)                          #change from ipaddress object to string to hand to ping command
    proc = subprocess.run(                    #use subprocess to call ping command, split to multiple lines cuz its long
        ['ping', host, '-c', '1'],            #calling ping here, putting in host to ping
        stderr=subprocess.DEVNULL,            #silence ping command errors
        stdout=subprocess.DEVNULL             #silence ping command output
        )
    if  proc.returncode == 0:                 #return code of 0 from ping command means it got a reply
        print(f'{host} is alive!')            #say this host is alive if we got a reply

Hopefully this is pretty straightforward. The magic is happening in a couple spots. The first is with this line:

mynet = ipaddress.ip_network('172.16.0.0/24')

This creates an “object” that holds the network we’re working with. This object has super powers, one of them is visible in this line:

for host in mynet.hosts():

It lets us move through the hosts of the subnet, from 172.16.0.1 to 172.16.0.254, each time the host IP is assigned to host. We can then hand host to the ping command.

The second spot with magic is here:

    proc = subprocess.run( 
        ['ping', host, '-c', '1'],
        stderr=subprocess.DEVNULL, 
        stdout=subprocess.DEVNULL 
        )

This is just spawning another process, ping, from python.

When we run the script, we should see all the IP addresses that are alive!

python3 ping_sweep.py #might be python or python3 based on your OS

172.16.0.1 is alive!
172.16.0.51 is alive!
172.16.0.121 is alive!
172.16.0.253 is alive!

There’s only one problem – the script takes quite a while to run. The issue is that each time the ping command runs, python waits until it finishes before moving to the next. This is known as “blocking”, which basically means the script comes to a halt while it’s waiting for a ping process to finish. A common /24 size subnet of 254 hosts takes a good while to complete.

What if we could ping them all at the same time, or close to it? Well, this leads us into the dark, dangerous world of multi-threading, parallel processing, multiprocessing, and asynchronous processing. Even the words are ominous-sounding. But don’t worry, it’s not so bad with the help of a handy module called asyncio.

Super-charged ping sweep with asyncio

The issue we’re faced with is that we need to do multiple things at once. There are many ways to solve this problem, and some people spend their whole careers in this complex field. Recently though, the python asyncio is getting popular because it’s relatively easy to work with and not so terribly complicated compared to others. As of python 3.6, it’s part of the standard library.

Here’s the same ping sweep, this time written using the python asyncio module:

import ipaddress
import asyncio

async def ping(host):                              #add the "async" keyword to make a function asynchronous
    host = str(host)                               #turn ip address object to string
    proc = await asyncio.create_subprocess_shell(  #asyncio can smoothly call subprocess for you
            f'ping {host} -c 1',                   #ping command
            stderr=asyncio.subprocess.DEVNULL,     #silence ping errors
            stdout=asyncio.subprocess.DEVNULL      #silence ping output
            )
    stdout,stderr = await proc.communicate()       #get info from ping process
    if  proc.returncode == 0:                      #if process code was 0
        print(f'{host} is alive!')                 #say it's alive!

loop = asyncio.get_event_loop()                    #create an async loop
tasks = []                                         #list to hold ping tasks

mynet = ipaddress.ip_network('172.16.0.0/24')      #ip address module
for host in mynet.hosts():                         #loop through subnet hosts
    task = ping(host)                              #create async task from function we defined above
    tasks.append(task)                             #add task to list of tasks

tasks = asyncio.gather(*tasks)                     #some magic to assemble the tasks
loop.run_until_complete(tasks)                     #run all tasks (basically) at once

No denying it, this is more complicated. Might be a bit foreign even if you’re familiar with python. The key thing here is that we define a single ping task in an async function. Then when we loop through the subnet hosts, we create a task from that function, instead of running it on the spot. Then we call the asyncio module at the end to gather up the tasks and run them all asynchronously, which has the effect of appearing to run them all at once.

Also note that there’s no subprocess module here. Asyncio has built-in subprocess management (check the documentation here), so no need for the standard subprocess module.

While the output is the same, you’ll notice this takes about a second to run compared to minutes for the first script:

python3 ping_sweep.py

172.16.0.1 is alive!
172.16.0.51 is alive!
172.16.0.121 is alive!
172.16.0.253 is alive!

Please let me know in the comments if you want to see more content like this!

IPv6: IPsec Not Mandatory to Use or Exist

One thing is clear, there is a serious amount of misinformation about IPv6. There are many blog posts and even official documents from respected sources that have blatantly incorrect information. Many blog posts that start out with an introduction such as this one, but end up spreading misinformation about IPv6 anyway. To avoid spreading misinformation myself, I’m going to cite the only sources of truth in the network world: the IETF RFC’s. If you’re looking for a clear and simple one-liner to sum up this article here it is:

Since the IETF issued RFC 6434 in 2011, the IPv6 standard does not require devices to implement (to be capable of) IPsec, nor does it require the enablement or use of IPsec for IPv6 nodes that do implement (are capable of) IPsec.

Yes, you read that right. You cannot count on IPsec even existing in nodes and devices running IPv6. For many years now a great number of sources have been trumpeting that IPv6 will herald an era of not having to think about network security anymore. Most respectable sources reject this notion, saying that it’s required to be implemented (to exist) in all IPv6 nodes, but not required to be turned on. Unfortunately that is also false.

RFC 1883 – where it all started

In December 1995, the IETF (Internet Engineering Task Force, a standards body accepted as the source of truth in the networking world) released RFC 1883, which defined IPv6 as a successor to IPv4.

Page 36 of that RFC briefly and quietly stated that Authentication Header (AH) and Encapsulating Security Payload (ESP) be required to secure IPv6 traffic:

This document specifies that the IP Authentication Header [RFC-1826] and the IP Encapsulating Security Payload [RFC-1827] be used with IPv6, in conformance with the Security Architecture for the Internet Protocol [RFC-1825].

https://datatracker.ietf.org/doc/html/rfc1883

This lead to a lot of folks being really excited about the ramifications of mandated secure connections in IPv6. While I can’t find any articles from 1995, this blog post from Bitdefender (a respected name in security) from 2012 is a really good example of an article making really misleading statements about how IPsec works with IPv6:

IPv6 comes with built-in IPSec , a technology that ensures secure host-to-host communication. This means that two clients communicating over IPv6 can automatically do authentication, message integrity and encryption or any combination of those.

https://www.bitdefender.com/blog/hotforsecurity/ipv6-is-here-ready-to-embrace-it

While the implementation (the capability) of IPsec was indeed mandatory in IPv6 for the first 16 years after RFC 1883 was released, no one ever said that the use or enablement of it would be mandatory.

RFC 6434 – IPsec from MUST to SHOULD

In December 2011, the IETF updated the IPv6 Node Requirements RFC with RFC 6434. In section 11 (Security), they make this unmistakable statement:

Previously, IPv6 mandated implementation of IPsec and recommended the key management approach of IKE. This document updates that recommendation by making support of the IPsec Architecture [RFC4301] a SHOULD for all IPv6 nodes.

https://datatracker.ietf.org/doc/html/rfc6434

While IPv6 IPsec is implemented (the capability exists) in major desktop/laptop OS’s such as Windows and macOS, the Internet is made up of much more than that. Internet of Things comes to mind.

It seems the IETF realized that it doesn’t make sense (or in many cases it’s not possible) to include the complexity of IPsec in an IPv6 implementation. They went on to say in RFC 6434:

This document recognizes that there exists a range of device types and environments where approaches to security other than IPsec can be justified.

https://datatracker.ietf.org/doc/html/rfc6434

Loads of misinformation

So yes, starting in 2011, IPsec even existing in a device running IPv6 is a big maybe. Despite this, some of the most respected companies in the world write documentation that describe the mandatory-ness of IPsec in IPv6. Take this IOS configuration guide from Cisco, written in August 2012:

IPsec is a mandatory component of IPv6 specification.

https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/ipv6/configuration/15-2s/ipv6-15-2s-book/ip6-ipsec.html

Or this article from Microsoft written in 2020:

Internet Protocol Security (IPsec) is a set of security protocols used to transfer IP packets confidentially across the Internet. IPsec is mandatory for all IPv6 implementations and optional for IPv4.

https://docs.microsoft.com/en-us/windows/win32/fwp/ipsec-configuration

Or this article from Redhat written in 2019:

In What you need to know about IPv6, we mentioned that Internet Protocol Security (IPSec) is incorporated into IPv6. This statement simply means that communication between the two endpoints is either authenticated, encrypted, or both, via the extension headers. There is a long-running discussion on the internet regarding whether the interpretation of “IPSec being mandatory” in IPv6 is correct or not. If you need to know more about this topic, see RFC 6434.

https://www.redhat.com/sysadmin/ipv6-packets-and-ipsec

This last one from Redhat is particularly confusing. They mention the very RFC where IPsec became optional and even mention the ongoing debate, but continue to say that communications in IPv6 are either authenticated or encrypted, or both.

What’s actually happening? Not IPv6 IPsec.

All of this RFC and protocol stuff is a bunch of theoretical pie in the sky. What’s actually happening out in the wild? Well, I can’t speak to the whole Internet. Maybe folks are using IPsec like they do in IPv4 – for site-to-site tunnels. Or maybe something else. But to test typical web traffic, I have native IPv6 capability in my home. I took some Wireshark captures to various websites.

Here’s google.com:

IPv6 capture of google.com

Here’s microsoft.com:

IPv6 capture of microsoft.com

Here’s wikipedia.org:

IPv6 capture of wikipedia.org

Where’s the IPsec? I don’t see any IKE, AH or ESP. Looks like typical TLS traffic to me. So… no change in security from IPv4, then.

iperf2 vs iperf3: What’s the difference?

At first glance, you might be tempted to use iperf3 simply because it is one more than iperf2 (don’t worry, I’m guilty of this crime as well). It’s not an unfair assumption to think that iperf3 is the most recent version of the software, because of the name. It’s common to have two different versions of software in parallel existence, so the new one can take hold while the older version slowly dies away. Python2 and Python3 come to mind. This is not the case with iperf, however.

I recently wrote a post on how to use iperf3 to test bandwidth. Shortly after that one of the authors of iperf2, Bob McMahon, reached out to me. He pointed out that iperf2 is very much actively developed with some cool new features having been added recently. Under the surface they are very different projects, maintained by different teams with different goals.

Today we’ll take a look at some of the differences between the two.

Topology

Ubuntu 20.04 and Rocky Linux 8.5 VM’s in GNS3

We have a really basic topology here, Ubuntu 20.04 and Rocky Linux 8.5 connected on a single link with IP subnet 10.0.0.0/30. Both VM’s have iperf2 and iperf3 installed.

Bandwidth Test

For a bandwidth test, the two are almost identical. You can perform a bandwidth test using either with the same commands. For this test, the Ubuntu VM will be the client, and Rocky the server. Start the server on Rocky like this:

iperf -s

------------------------------------------------------------
Server listening on TCP port 5001
TCP window size: 85.3 KByte (default)
------------------------------------------------------------

And from Ubuntu perform a test like this:

iperf -c 10.0.0.2
------------------------------------------------------------
Client connecting to 10.0.0.2, TCP port 5001
TCP window size:  238 KByte (default)
------------------------------------------------------------
[  3] local 10.0.0.1 port 36528 connected with 10.0.0.2 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec  1.90 GBytes  1.63 Gbits/sec

These commands will work using iperf2 or iperf3, however it should be noted you can’t use an iperf2 client with an iperf3 server, and vice-versa. Also, they use different TCP ports by default. Even if you used an iperf3 client with an iperf2 server and manually set the TCP port to be the same, you will get an error. They are not compatible:

iperf3 -c 10.0.0.2 -p 5001
iperf3: error - received an unknown control message

Supported Operating Systems

iperf2 is the clear winner here, primarily because it has up-to-date Windows packages available for easy download right on the sourceforge page. I avoid Windows when I can, but it has a tendency to be unavoidable due to it’s sheer installation base. iperf3 apparently had some unofficial builds a while back but nothing officially supported. You’ll need to compile it yourself to work on Windows which can be an inconvenience at best.

iperf2 downloads page

For Linux, many operating systems come with iperf2 preinstalled, Ubuntu 20.04 is one such example. iperf3 is just a command away though, with package managers. For macOS, the Homebrew package manager can quickly get you iperf2 or iperf3.

Feature: iperf3 authentication (not encryption)

Description of authentication features in iperf3

iperf3 supports authenticating clients to the server using public key/private key as well as a users file. I decided to try it out. To avoid a hassle I just used the exact commands they provided in the man file. You first generate a public key and private key on the server:

openssl genrsa -des3 -out private.pem 2048
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
openssl rsa -in private.pem -out private_not_protected.pem -outform PEM

Then create a “credentials.csv” file with hashed passwords. The following commands will get a hashed password for you:

S_USER=james S_PASSWD=james
echo -n "{$S_USER}$S_PASSWD" | sha256sum | awk '{ print $1 }'
----
0b0c98028105e9e4d3f100280eac29bba90af614d1c75612729228e4d160c601 #This is the hash of "james"

Then create a “credentials.csv” file that looks like this:

username,sha256
james,0b0c98028105e9e4d3f100280eac29bba90af614d1c75612729228e4d160c601

Now start the server:

iperf3 -s --rsa-private-key-path ./private_not_protected.pem --authorized-users-path ./credentials.csv

Then from the client, copy the public key over:

scp james@10.0.0.1:public.pem .

Then run the client:

iperf3 -c 10.0.0.1 --rsa-public-key-path ./public.pem --username james

You’ll be asked for the password. If you get it right, the server will display a message that authentication succeeded:

-----------------------------------------------------------
Server listening on 5201
-----------------------------------------------------------
Authentication successed for user 'james' ts 1639396545
Accepted connection from 10.0.0.2, port 32784
[  5] local 10.0.0.1 port 5201 connected to 10.0.0.2 port 32786
[ ID] Interval           Transfer     Bitrate
[  5]   0.00-1.00   sec   194 MBytes  1.63 Gbits/sec                  
[  5]   1.00-2.00   sec   204 MBytes  1.71 Gbits/sec

Feature: iperf2 isochronous mode

One of the coolest features of iperf2 is its “isochronous” option. This option is designed to simulate video streaming network traffic. You can hear Bob McMahon explain it himself on his youtube video on this feature.

Using the parameters and commands he describes in his video, we’ll run on a test. The Ubuntu server will be the iperf2 server:

iperf -s -e -i 1

Then on Rocky Linux we’ll run the client test:

[james@localhost ~]$ iperf -c 10.0.0.1 -i 1 --isochronous=60:40m,10m
------------------------------------------------------------
Client connecting to 10.0.0.1, TCP port 5001 with pid 1640
UDP isochronous: 60 frames/sec mean=40.0 Mbit/s, stddev=10.0 Mbit/s, Period/IPG=16.67/0.005 ms
TCP window size:  340 KByte (default)
------------------------------------------------------------
[  3] local 10.0.0.2 port 49150 connected with 10.0.0.1 port 5001 (ct=1.44 ms)
[ ID] Interval        Transfer    Bandwidth       Write/Err  Rtry     Cwnd/RTT        NetPwr
[  3] 0.00-1.00 sec   214 MBytes  1.79 Gbits/sec  1708/0          0       67K/562 us  398346.93
[  3] 1.00-2.00 sec   217 MBytes  1.82 Gbits/sec  1738/0        230      145K/608 us  374676.21
[  3] 2.00-3.00 sec   205 MBytes  1.72 Gbits/sec  1640/0        427      142K/583 us  368710.26
[  3] 3.00-4.00 sec   212 MBytes  1.78 Gbits/sec  1697/0        575      118K/920 us  241770.85
[  3] 4.00-5.00 sec   200 MBytes  1.68 Gbits/sec  1599/0        371      134K/853 us  245702.38
[  3] 5.00-6.00 sec   200 MBytes  1.68 Gbits/sec  1598/0        423      117K/529 us  395941.50

On the server we get our output:

james@u20vm:~$ iperf -s -e -i 1
------------------------------------------------------------
Server listening on TCP port 5001 with pid 3045
Read buffer size:  128 KByte
TCP window size:  128 KByte (default)
------------------------------------------------------------
[  4] local 10.0.0.1 port 5001 connected with 10.0.0.2 port 49150
[ ID] Interval            Transfer    Bandwidth       Reads   Dist(bin=16.0K)
[  4] 0.0000-1.0000 sec   213 MBytes  1.79 Gbits/sec  4631    503:1500:1008:577:276:191:138:438
[  4] 1.0000-2.0000 sec   217 MBytes  1.82 Gbits/sec  4018    570:838:812:502:255:231:164:646
[  4] 2.0000-3.0000 sec   204 MBytes  1.71 Gbits/sec  5074    590:1537:1637:511:316:152:115:216
[  4] 3.0000-4.0000 sec   212 MBytes  1.78 Gbits/sec  3924    599:805:717:464:266:264:246:563
[  4] 4.0000-5.0000 sec   200 MBytes  1.68 Gbits/sec  3876    575:953:672:462:258:242:188:526
[  4] 5.0000-6.0000 sec   200 MBytes  1.68 Gbits/sec  4046    656:1040:687:476:258:242:238:449

Unfortunately the version of iperf that is available in Ubuntu 20.04 repositories (2.0.13) doesn’t support isochronous TCP mode mentioned in the video. You would need to compile from source or use Windows for that. A newer version will be included (probably already has been by the time you’re reading this) in Ubuntu 22.04 LTS.

Various smaller differences

There are many other spots that iperf2 and iperf3 are different.

  • iperf2 supports an “enhanced output mode” using -e that is totally revamped (used it above in the isochronous section).
  • iperf3 supports json output using the -j option.
  • iperf2 supports a bidirectional test which performs tests from the client and server simultaneously using -d
  • iperf2 uses a multi-threaded architecture, while iperf3 uses single-threaded. To be honest, I haven’t seen any way that this actually affects performance of the application. I’d be really curious if anyone has some input on this.

I hope this was helpful, and I hope I did both of these cool programs a small amount of justice. I’m really curious to see if anyone has any other input or differences they know about. Please fee free to comment or reach out directly.

How To Install Free Range Routing (FRR) on Ubuntu 20.04 and Rocky Linux 8.5

The latest version of my favorite routing protocol software, Free Range Routing 8.1 was recently released on November 9th.

Free Range Routing is a fork of the Quagga project that improves upon it and adds lots more features and new protocols. My favorite protocol that is added is EIGRP, which was originally a Cisco proprietary protocol until Cisco released a draft RFC in 2013. Free Range Routing makes it easy to spin up a Linux router and exchange routes via EIGRP. Since Cisco routers speak EIGRP, you can also exchange routes with them too! Today we’ll just exchange routes between Ubuntu 20.04 and Rocky Linux 8.5 via EIGRP.

Topology

Ubuntu and Rocky Linux in GNS3

We have a simple network here with an Ubuntu and Rocky Linux VM’s acting as IP routers. Without adding routes, Ubuntu does not know about 172.16.0.0/24, and Rocky does not know about 192.168.0.0/24. EIGRP can educate them. I should mention – each of the Alpine nodes has a default route pointing to the .1 in their subnet (Ubuntu and Rocky), which is a typical setup in most networks.

Installation

In a previous post, I installed FRR on Ubuntu 18.04 via the snap store. You can still do that, but it looks like the snap version hasn’t been updated with 8.1. I’m sure it will be updated soon, but let’s install it via the binary packages that FRR provides just to do something different.

For Rocky Linux, you can find instructions here. They are RPM packages for CentOS, and in my testing I found them to work fine for Rocky Linux. Per their instructions, we’ll run these commands:

FRRVER="frr-stable"
curl -O https://rpm.frrouting.org/repo/$FRRVER-repo-1-0.el8.noarch.rpm
sudo yum install ./$FRRVER*
sudo yum install frr frr-pythontools

We’ll need to modify /etc/frr/daemons and turn on the protocols we want, in this case EIGRP:

vi /etc/frr/daemons
---

eigrpd=yes #find this line and set to yes

Then you’ll need to restart frr:

systemctl restart frr

The process is similar on Ubuntu. The debian-based instructions are on this page. Following those, we’ll run these commands:

curl -s https://deb.frrouting.org/frr/keys.asc | sudo apt-key add -
FRRVER="frr-stable"
echo deb https://deb.frrouting.org/frr $(lsb_release -s -c) $FRRVER | sudo tee -a /etc/apt/sources.list.d/frr.list
sudo apt update && sudo apt install frr frr-pythontools

We’ll need do modify the daemons file similar to above and run the exact same systemctl command to restart frr.

Installation is complete!

Configure FRR and EIGRP

To setup EIGRP routing, we’ll enter the FRR vtysh configuration tool that should be familiar if you’ve used either Quagga or Cisco IOS routers. On Ubuntu we’ll do this:

vtysh
---

Hello, this is FRRouting (version 8.1).
Copyright 1996-2005 Kunihiro Ishiguro, et al.

u20vm# conf t
u20vm(config)# router eigrp 10
u20vm(config-router)# network 10.0.0.0/30
u20vm(config-router)# network 192.168.0.0/24
u20vm(config-router)# ^Z
u20vm# exit

On Rocky Linux, it’s almost exactly the same but the second network to add is 172.16.0.0/24:

vtysh
---

Hello, this is FRRouting (version 8.1).
Copyright 1996-2005 Kunihiro Ishiguro, et al.

rl85vm# conf t
rl85vm(config)# router eigrp 10
rl85vm(config-router)# network 10.0.0.0/30
rl85vm(config-router)# network 172.16.0.0/24
rl85vm(config-router)# ^Z
rl85vm# exit

Since Rocky is runnning firewalld by default, you’ll need to either stop it with systemctl stop firewalld or go through the process to allow EIGRP-related traffic through the firewall.

We should be able to see that each router has the other’s connected route now installed in its table. On Ubuntu we can see 172.16.0.0/24 is installed from vtysh with show ip route (edited somewhat for brevity):

u20vm# show ip route
---

E   10.0.0.0/30 [90/28160] is directly connected, ens3, weight 1, 00:41:19
C>* 10.0.0.0/30 is directly connected, ens3, 00:41:57
E>* 172.16.0.0/24 [90/30720] via 10.0.0.2, ens3, weight 1, 00:40:55
E   192.168.0.0/24 [90/28160] is directly connected, ens4, weight 1, 00:40:43
C>* 192.168.0.0/24 is directly connected, ens4, 00:41:57

And likewise on Rocky Linux we can see 192.168.0.0/24 is installed:

rl85vm# show ip route
---

E   10.0.0.0/30 [90/28160] is directly connected, ens3, weight 1, 00:43:07
C>* 10.0.0.0/30 is directly connected, ens3, 00:43:45
E   172.16.0.0/24 [90/28160] is directly connected, ens4, weight 1, 00:42:50
C>* 172.16.0.0/24 is directly connected, ens4, 00:43:45
E>* 192.168.0.0/24 [90/30720] via 10.0.0.1, ens3, weight 1, 00:42:38
localhost.localdomain# 

A wireshark (if you’re running GNS3) will show the EIGRP messages flowing. If you catch it right at the start, you can see updates messages and not just hellos:

Wireshark capture of EIGRP traffic between Ubuntu and Rocky Linux

Verify

This should be easy, we’ll just ping between the Alpine Linux nodes. (make sure each has a default route pointing to .1)

/ # ping 172.16.0.1
PING 172.16.0.1 (172.16.0.1): 56 data bytes
64 bytes from 172.16.0.1: seq=0 ttl=63 time=2.662 ms

It works!

Hope you liked it.

How To Test Network Bandwidth With iperf3 in Linux

Testing network bandwidth in multiple flavors in Linux is simple with a tool called iperf. There’s two main versions – iperf2 and iperf3. Project maintainers apparently completely rewrote iperf3 from scratch to make the the tool simpler and to support some new features.

Update 12/12/2021: One of the authors of iperf2 reached out to me. Iperf2 is currently very much actively developed. You can find the most recent code on its sourceforge.net page. Iperf3 was indeed rewritten from scratch as the wikipedia page says, but mostly to meet the U.S. Department of Energy’s use cases. Iperf3’s github page clearly states the the DoE owns the project.

For testing bandwidth properly, you need to be running in server mode on one endpoint and client mode on the other. For this experiement, we will run the server on Rocky Linux 8.5 and the client on Ubuntu 20.04.

Topology

iperf3 test in GNS3

This is about as simple of a topology as I can think of. Two nodes on either end of a single link, Ubuntu at 10.0.0.1/30 running iperf3 client and Rocky at 10.0.0.2/30 running iperf3 server.

Iperf3 installation

On Ubuntu, iperf3 can be installed from distribution sources with apt-get:

apt-get install iperf3

Same on Rocky Linux but with yum:

yum install iperf3

Run iperf3 bandwidth test

First we need to start the server process on Rocky Linux with one command:

iperf3 -s

Then you should see the server listening for incoming tests:

iperf3 server listening on Rocky Linux 8.5

Then from the Ubuntu client, one command will run the test:

iperf3 -c 10.0.0.2

The output will give us our bandwith test results, which can be see on either the client or server:

Connecting to host 10.0.0.2, port 5201
[  5] local 10.0.0.1 port 59628 connected to 10.0.0.2 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec   176 MBytes  1.48 Gbits/sec  685    230 KBytes       
[  5]   1.00-2.00   sec   173 MBytes  1.45 Gbits/sec  738    113 KBytes       
[  5]   2.00-3.00   sec   170 MBytes  1.42 Gbits/sec  1004    191 KBytes       
[  5]   3.00-4.00   sec   175 MBytes  1.47 Gbits/sec  714    123 KBytes       
[  5]   4.00-5.00   sec   182 MBytes  1.52 Gbits/sec  458    163 KBytes       
[  5]   5.00-6.00   sec   204 MBytes  1.71 Gbits/sec  443    314 KBytes       
[  5]   6.00-7.00   sec   180 MBytes  1.51 Gbits/sec  910    130 KBytes       
[  5]   7.00-8.00   sec   191 MBytes  1.60 Gbits/sec  849    123 KBytes       
[  5]   8.00-9.00   sec   172 MBytes  1.44 Gbits/sec  564    170 KBytes       
[  5]   9.00-10.00  sec   184 MBytes  1.54 Gbits/sec  412    225 KBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  1.76 GBytes  1.52 Gbits/sec  6777             sender
[  5]   0.00-10.04  sec  1.76 GBytes  1.51 Gbits/sec                  receiver

iperf Done.

A wireshark capture in GNS3 between the two hosts (or tcpdump on the links if you’re not in GNS3) will show the packets flying while the test is running:

Wireshark capture from GNS3 of iperf3 test

Hope you liked it!

How To Install (and lab) Keepalived on Ubuntu 20.04 and Rocky Linux 8.5

Keepalived is an open source software project that can do many things related to high availability. One of these many things is the Virtual Router Redundancy Protocol, which provides for high availability for IP networking. In other words, you can have two routers and if one goes down, the second one kicks in automatically.

The way this works is two or more routers exchange VRRP messages on a subnet and based on their configuration, decide who is the master and who is the backup. Once this is decided they will agree on a pre-configured “virtual” IP, or an IP that is not configured on an interface, but a floating one that either router can assume responsibility for should the other one fail for some reason and VRRP messages stop flowing.

Topology

VRRP lab in GNS3

The relevant network here is on the bottom half, where a subnet of 192.168.0.0/24 is configured. The Ubuntu server has 192.168.0.2/24 on its ens3 interface, while Rocky has 192.168.0.3/24 on its ens3 interface. They will both have keepalived installed and through VRRP share virtual IP of 192.168.0.1/24. The Alpine linux “PC” at the bottom which is acting as a workstation or desktop computer will have its default route configured to point to 192.168.0.1, the VRRP address.

Ubuntu 20.04 configuration

On the Ubuntu server we’ll install the keepalived available from the package manager with this:

apt-get install keepalived

Once that’s installed, we’ll write the configuration file which is in /etc/keepalived/keepalived.conf. You’ll need to create the keepalived.conf file:

vrrp_instance VI_1 {
	state MASTER
	interface ens3
	virtual_router_id 51
	priority 100
	advert_int 1
	authentication {
		auth_type PASS
		auth_pass james
	}
	virtual_ipaddress {
		192.168.0.1/24
	}
}

I’ll go through the parameters here:

  • state MASTER: the state that the router will start in.
  • interface ens3: VRRP protocol messages should flow is ens3.
  • virtual_router_id: An integer that both routers should have configured to the same thing.
  • priority: who wins the master/backup election – higher numerical means higher priority.
  • advert_int: backup waits this long (multiplied by 3) after messages from master fail before becoming master
  • authentication: a clear text password authentication.
  • virtual_ipaddress: the agreed-upon virtual IP that the routers will share

Restart keepalived to load the config:

systemctl restart keepalived

Rock Linux configuration

Since Rocky uses yum for package management, we install keepalived like this:

yum install keepalived

And in the /etc/keepalived/keepalived.conf file we’ll write this:

vrrp_instance VI_1 {
	state BACKUP
	interface ens3
	virtual_router_id 51
	priority 99
	advert_int 1
	authentication {
		auth_type PASS
		auth_pass james
	}
	virtual_ipaddress {
		192.168.0.1/24
	}
}

Make sure to restart keepalived to load the config:

systemctl restart keepalived

The only parameters that are different are the state and the priority.

Verify keepalived and VRRP

The first thing you can check on Ubuntu is the /var/log/syslog file to make sure keepalived started and is in the correct state:

tail /var/log/syslog
---
Dec  1 12:23:47 u20vm Keepalived[12349]: Opening file '/etc/keepalived/keepalived.conf'.
Dec  1 12:23:47 u20vm Keepalived[12349]: Starting VRRP child process, pid=12360
Dec  1 12:23:47 u20vm Keepalived_vrrp[12360]: Registering Kernel netlink reflector
Dec  1 12:23:47 u20vm Keepalived_vrrp[12360]: Registering Kernel netlink command channel
Dec  1 12:23:47 u20vm Keepalived_vrrp[12360]: Opening file '/etc/keepalived/keepalived.conf'.
Dec  1 12:23:47 u20vm Keepalived_vrrp[12360]: Registering gratuitous ARP shared channel
Dec  1 12:23:47 u20vm Keepalived_vrrp[12360]: (VI_1) Entering BACKUP STATE (init)
Dec  1 12:23:48 u20vm Keepalived_vrrp[12360]: (VI_1) received lower priority (99) advert from 192.168.0.3 - discarding
Dec  1 12:23:51 u20vm Keepalived_vrrp[12360]: message repeated 3 times: [ (VI_1) received lower priority (99) advert from 192.168.0.3 - discarding]
Dec  1 12:23:51 u20vm Keepalived_vrrp[12360]: (VI_1) Entering MASTER STATE

On Rocky the journalctl -e command showed me the keepalived logs.

Once you’ve confirmed that keepalived is in the right state, you can prove it further with a wireshark capture. If you’re doing this lab in GNS3 like I am, it’s easy, just right click the link (in this case, between the Ubuntu server and the switch) and capture on it. Otherwise you can use something like tcpdump on the Ubuntu/Rocky routers themselves. Right when you restart the keepalived process you will see packets going back and forth. That’s the master election process and exchange of parameters/neighbor establishment:

VRRP election in wireshark

But once the routers are in agreement and VRRP is working, packets will only flow from the master to the designated (in the RFC) VRRP multicast address at 224.0.0.18:

VRRP keepalives in wireshark

We can also further prove that when we initiate a ping to 8.8.8.8 from the Alpine “PC” (it’s a docker container), we can see that traffic is flowing through the Ubuntu router.

Simulate a failure

We’ll simulate a failure by shutting the ens3 interface on the Ubuntu router, like so:

ip link set ens3 down

The Rocky router will observe that VRRP “hello” messages are no longer going to 224.0.0.18, and quickly assume the role of master and take over for 192.168.0.1. I did a continuous ping on the Alpine PC and it didn’t actually show any failed pings! We can see that traffic is now flowing through the Rocky router:

Now let’s “fail back” to the Ubuntu router by re-enabling the ens3 interface:

ip link set ens3 up
ip addr add 192.168.0.1/24 dev ens3

And we should be able to see keepalived resuming master responsibilities in the Ubuntu /var/log/syslog:

Dec  1 12:44:53 u20vm Keepalived_vrrp[12379]: Netlink reports ens3 up
Dec  1 12:44:53 u20vm systemd-networkd[602]: ens3: Gained carrier
Dec  1 12:44:55 u20vm systemd-networkd[602]: ens3: Gained IPv6LL
Dec  1 12:45:04 u20vm Keepalived_vrrp[12379]: (VI_1) Entering BACKUP STATE
Dec  1 12:45:04 u20vm Keepalived_vrrp[12379]: (VI_1) received lower priority (99) advert from 192.168.0.3 - discarding
Dec  1 12:45:07 u20vm Keepalived_vrrp[12379]: message repeated 3 times: [ (VI_1) received lower priority (99) advert from 192.168.0.3 - discarding]
Dec  1 12:45:08 u20vm Keepalived_vrrp[12379]: (VI_1) Entering MASTER STATE

While the Rocky log will show a similar message about becoming backup. We can see that pings are once again flowing through the Ubuntu router:

Hope you liked it.

How To Check If A UDP Port Is Listening Using Netcat

Netcat is a super cool tool to perform a great number of different testing and troubleshooting tasks. On Ubuntu Linux, netcat comes built-in so you don’t even have to install it. A common question I get from people trying to troubleshoot a UDP-based application is how to check if a UDP port is “listening” or not. Most folks are familiar with the telnet-based method of checking if a TCP port is listening (e.g., telnet google.com 80), but UDP remains elusive.

Today let’s take a look at why UDP is tricky to check and a method using the netcat tool to see if a server is listening on a particular UDP port.

Topology

We’re using a very simple topology here, my PC is at 192.168.122.1 and the server we’re going to check for an open UDP port is at 192.168.122.252. My PC runs an Ubuntu 20.04 Desktop OS, while the server is running the Ubuntu Server version.

TCP and UDP are different protocols

TCP is designed to establish “reliable” connections, meaning packets are either delivered on-time and in order, or the connection fails. To guarantee delivery, it must first establish a “socket”, which is where the TCP handshake comes in. All TCP sockets start like this:

Client –> [SYN] –> Server
Client <– [SYN/ACK] <– Server
Client –> [ACK] –> Server

Once this handshake is done, a socket and connection is established.

A telnet test will succeed if this TCP handshake completes properly, letting you know that the port you’re testing is open.

The problem with UDP is that no such handshake exists, because it was designed specifically to not have connections or reliability. Neither is “better”, they’re just used for different things.

So how do you test for an open UDP connection if there’s not a protocol handshake or guaranteed response? If an application is NOT listening on a particular UDP port, and a UDP packet arrives on that port, the OS will send back an ICMP message of type 3 and code 3, meaning “destination unreachable” and “port unreachable”, respectively. Because UDP lacks a protocol message for this purpose, the ICMP message serves to notify the sender that they’re barking up the wrong tree.

Set up a netcat UDP server

Setting up a server to listen on a UDP port of our choosing is easy using netcat. Just run this command on the Ubuntu server:

netcat -ul 2000 &

The -ul switches specify UDP and “listen” mode, respectively. The ampersand at the end puts the process in the background so we can get our shell back. After running this command, we can do a quick netstat to see that there is indeed a UDP server listening on port 2000:

netstat -an | grep 2000
udp        0      0 0.0.0.0:2000            0.0.0.0:*

Use netcat on the client to test the UDP port

From “James’s PC” on the diagram, I’ll run the following command:

nc -vz -u 192.168.122.252 2000

The -v and -u options specify verbose and UDP mode, respectively. The -z option tells netcat not to send any data with the packet. At the end are the IP address of the server and to UDP port 2000. If the port is open, we should see output like this:

Connection to 192.168.122.252 2000 port [udp/*] succeeded!

The message is well and good, but well all know packets don’t lie. If we take a wireshark capture of the interaction, we can see why this success message is produced:

Five UDP packets were sent by netcat on James’s PC to make sure no response came back from the server. If there’s no ICMP response, we can infer that the port is indeed open.

The netcat UDP server will shut down after hitting it once with UDP packets, so it’s no longer listening on port 2000. If we run our command again, we’ll get no output from netcat and wireshark clearly shows the ICMP type 3 code 3 message being sent back from the server:

UDP port testing is not like TCP at all, since we can only infer the port’s open state, not guarantee it like TCP.

I hope this one was helpful!

SSH IP VPN Tunnel on Ubuntu 20.04

Today we will create an virtual interface to which you can assign an IP address and use like any other IP interface on Ubuntu. It’s transmissions are encrypted by SSH. This is not SSH port-forwarding. I repeat, this is not layer 4 SSH port-forwarding or what is commonly known as SSH-tunneling. This is full layer-3 connectivity on top of SSH.

SSH is a common tool for network engineers and systems administrators to securely access the CLI (command-line interface) of various systems. OpenSSH is an open-source implementation of the protocol and is included or available to install on most Linux distributions. While it’s a great tool for CLI access, SSH has other, darker powers that some consider to be hacking tools or black magic.

One of OpenSSH’s tools that is somewhat well known is the “SSH Tunnel”, and is basically a port forwarding technique that allows the sending of a single TCP or UDP port through an SSH connection. A Much less known feature is OpenSSH’s ability to create a virtual Ethernet adapter on top of an SSH connection. This allows full layer-3 IP connectivity, not just a single layer-4 TCP or UDP port. You can add routes that point through this virtual connection, just like you would any other Ethernet interface. You can even run a routing protocol across it.

Topology

We are setting up an SSH IP tunnel from Ubuntu20.04Server-1 on the left side at private physical IP 192.168.0.2 to Ubuntu20.04Server-3 on the right with a public physical IP of 12.0.0.2. The SSH tunnel will use a network of 10.0.0.0/30. One fun fact here is that this tunnel is traversing a NAT (PAT) that I set up on the Cisco router that is connecting Ubuntu20.04Server-1 to the Internet. No issues traversing NAT for SSH IP tunnel. Finally, we will add some static routes to allow Ubuntu20.04Server-4 to ping Ubuntu20.04Server-3 through the SSH tunnel.

Installation

Ubuntu20.04Server-3

You probably installed Ubuntu’s OpenSSH server when you installed the OS but you’ll also need a tool called autossh, so run this command on Ubuntu20.04Server-3:

apt-get install openssh-server

Now we’re going to make some changes to the SSH server configuration. Root login is required from the client in order to create a TUN adapter, so we’ll be enabling that. Edit the /etc/ssh/sshd_config file. You will make these changes:

  • Uncomment and change “PermitRootLogin prohibit-password” to “PermitRootLogin without-password”
  • Uncomment and change “PermitTunnel no” to “PermitTunnel yes”
vi /etc/ssh/sshd_config
PermitRootLogin without-password
PermitTunnel yes

The above configuration is confusing – it will allow login as root, but not without a key. It’s relatively secure. Then restart the OpenSSH server:

systemctl restart sshd

Ubuntu20.04Server-1

On Ubuntu20.04Server-1, you’ll need a tool called “autossh” that watches SSH sessions and restarts them if they die. Run this command:

apt-get install autossh

Let’s set up key authentication, so we can log in as root to the server. :

ssh-keygen -t rsa #create an RSA key
cat ~/.ssh/id_rsa.pub | ssh james@12.0.0.2 "mkdir -p ~/.ssh && cat >>  ~/.ssh/authorized_keys" #Copy key to server

Connecting the tunnel

We’re ready to build our tunnel! From Ubuntu 20.04Server-1 (the client at 192.168.0.2), run the following magical command:

autossh -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" -NTC -o Tunnel=point-to-point -w 0:0 12.0.0.2 &

There’s a fair amount going on here, I’ll break it down:

  • ‘-M 0’ refers to monitoring tcp port, do not use
  • ‘-o “ServerAliveInterval 30”’ sends a keepalive every 30 seconds
  • ‘-o “ServerAliveCountMax 3″’ retries keepalive a maximum of 3 times. Autossh ends here, SSH native commands start from next option.
  • ’-N’ instructs SSH not to execute a remote command
  • ’-T’ disables pseudo-tty allocation
  • ’-C’ compression, may improve performance, may degrade
  • ‘-o Tunnel=point-to-point’ creates a virtual interface
  • ’-w 0:0’ gives the local and remote tun adapters a number, in this instance 0. Left side of ‘:’ is local, right side is remote.
  • 12.0.0.2 is the tunnel destination
  • The final ampersand runs the command in the background so you can get your shell back.

If you have done everything correctly, you now have a “tun0” device on both the server and client:

ip link

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 0c:88:6c:69:00:00 brd ff:ff:ff:ff:ff:ff
9: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN mode DEFAULT group default qlen 500
    link/none 

Now you can configure it with IP settings. You can use “ip route” commands for testing, or netplan to survive reboot. On the server, we’ll add a static route for 192.168.0.0/24 pointing through the newly created tunnel so it can access that network. And on Ubuntu 20.04Server-4 (an innocent bystander in the 192.168.0.0/24 network at 192.168.0.3), we’ll also add a route for the 10.0.0/30 network pointing to 192.168.0.2 so we can see that the tunnel works to route all IP traffic, not just traffic between the client and server. Make sure the client has IP forwarding enabled or that won’t work.

#On Ubuntu 20.04Server-1 (the client)

ip addr add 10.0.0.1/30 dev tun0
ip link set tun0 up

#On Ubuntu 20.04Server-3 (the server)
ip addr add 10.0.0.2/30 dev tun0
ip link set tun0 up
ip route add 192.168.0.0/24 via 10.0.0.1

#On Ubuntu 20.04Server-4 (innocent bystander at 192.168.0.3)
ip route add 10.0.0.0/30 via 192.168.0.2

Let’s trying pinging from Ubuntu 20.04Server-4 to Ubuntu 20.04Server-3:

james@u20vm:~$ ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=63 time=3.87 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=63 time=3.73 ms

It works! Now let’s try to ping from the server Ubuntu 20.04Server-3 all the way through to Ubuntu 20.04Server-4, going right through that NAT at the Cisco router:

james@u20vm:~$ ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=63 time=3.30 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=63 time=3.69 ms

It works!

Hope you enjoyed this one, SSH IP tunnels are one of my favorite Linux hacks.

Dockerized phpipam in GNS3

If you’re keeping track of all your IP addresses from your environment in a really big and messy Excel file, you may want to consider switching to an IP address management tool. One such tool is phpipam, which is a web-based tool that allows you to store your IP addresses in a central database (a SQL database, to be specific). The reasons why that approach would be far superior to an Excel file are pretty clear – first of all no more emailing a million different copies of that Excel file. But it has other advantages as well, for example if your software development team wants to check the availability/reserve a new IP address, subnet or vlan from code, they can do it via the phpipam API without ever clicking on anything.

A testing instance of phpipam can be brought into your GNS3 environment quickly using Docker! It requires a little hacking, but nothing too ambitious. If you haven’t got GNS3 or Docker installed or you don’t know how to add a Docker image to GNS3, check out my post on that topic.

Topology

We’re not doing anything fancy here, just the phpipam docker container connected to the “NAT” cloud node. By default, the NAT cloud node uses a virtual adapter with IP subnet 192.168.122.0/24. The NAT adapter is at .1, and I’ll set my phpipam container to use .2. This setup will allow us to access the phpipam web server in the container at 192.168.122.2 via a web browser from our desktop computer that runs GNS3.

Build a custom docker image

We’re going to quickly build a custom docker image from the official phpipam image on Docker Hub. If you’re using a GNS3 VM, you can do this via a cli session on the VM. If you’re using Linux, just do this from any terminal. Make a directory for your Dockerfile:

mkdir jamesphpipam
vi Dockerfile

Now we’re going to write the docker commands for our custom image. MySQL server needs to be installed, and also the directory “/run/mysqld” needs to be created as well so MySQL can create a Unix socket there:

FROM phpipam/phpipam-www

RUN apk add mysql mysql-client
RUN mkdir /run/mysqld

Now we have an image (it’s based on Alpine Linux) ready to fire up in GNS3. You’ll need to add it from GNS3 preferences -> docker containers -> new. Go through all the screens and use defaults, except you’ll want to set the “start command” to “/bin/sh” to give you command line access when you double click on it from the GNS3 canvas.

Configure MySQL and Apache

First we need to open up the cli on the container and set its IP address to 192.168.122.2 (ip addr add 192.168.122.2/24 dev eth0). Start up both mysqld and httpd (MySQL Server and Apache Web Server), like this:

httpd
mysqld --user=root &

Make sure you use the ampersand at the end of your mysqld command, so it runs in the background.

To set the MySQL user and password, I had to login to the MySQL cli and run these commands in the phpipam docker container:

mysql -u root
ALTER USER 'root'@'localhost' IDENTIFIED BY 'SomeSecret';

Now we should be able to access the phpipam page at 192.168.122.2 from any web browser!

Configure new phpipam installation

If you click on “New phpipam installation”, it will take you to a page to select the SQL database installation type:

Let’s select “Automatic database installation”. Then we just put in the user “root” and password “SomeSecret” that we entered in our mysql cli earlier:

And our database is installed! Now we just need to set the admin password on the next screen:

Click on “Proceed to login”, login with user “admin” and the password you just set. You’ll be taken to the main phpipam page!

Hit me up if you run into any snags!

Install GNS3 and Docker on Ubuntu 20.04 for Cisco and Linux Network Labs

Every major OS has its place, so I’m not hoping to get into that discussion, but I find that Ubuntu Linux works really well for creating network labs in GNS3. If you’re not familiar with GNS3, you’re missing out. It allows you to pull in real VM’s, and even Docker containers into an emulated network environment for testing and experimentation. You can run Cisco routers and switches, other vendor network vendors, Windows Desktop and Server, Linux and any other OS that is supported by Linux’s QEMU/KVM hypervisor which is pretty much anything. GNS3 has many features, but today we’ll just look at getting it installed, along with Docker.

Why is Ubuntu better to run GNS3? You may have noticed that on Windows or Mac versions of GNS3, the server has to run on a VM to work properly. That server VM runs a Linux OS, specifically Ubuntu. So using Ubuntu as your desktop OS means you’re cutting out all of that complexity with the server VM, not to mention the additional RAM consumed. Simply put, GNS3 runs the way it’s supposed to on Ubuntu. Not to knock the Windows and Mac versions, the GNS3 team worked hard on those. But in my humble and honest opinion, Ubuntu just works better for GNS3.

Most folks stick to using VM’s in GNS3, but the Docker integration is pretty awesome and has some very real benefits over VM’s. Any docker container you have installed on the same system as the GNS3 server can be pulled into GNS3, although whether it will work properly depends somewhat on what the container has installed in it.

GNS3 Installation

The official GNS3 Ubuntu releases can be found at their PPA at:

https://launchpad.net/~gns3/+archive/ubuntu/ppa

The PPA can be added and GNS3 installed with just a few quick commands, although it’s a relatively big download:

sudo add-apt-repository ppa:gns3/ppa
sudo apt-get update
sudo apt-get install gns3-gui

When you first run GNS3, you’ll notice that the default option is not a VM, it’s to run the server locally. No VM needed!

At this point, GNS3 is installed, although you may have to run this command to get wireshark captures working:

chmod 755 /usr/bin/dumpcap

Docker Installation

I’ll just be following the official Docker instructions here, they work great:

https://docs.docker.com/engine/install/ubuntu/

These are to install the repository, which is probably the “best” option. There is a convenience one-liner script, but we all know that’s not a good habit to get into, so we’ll avoid that.

First install dependencies:

 sudo apt-get update
 sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

Add the Docker official GPG key:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

Add the stable repository:

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

And install:

 sudo apt-get update
 sudo apt-get install docker-ce docker-ce-cli containerd.io

To avoid getting permissions errors in GNS3, you’ll need to add your user to the docker group. You’ll need to log out/log in or restart for this to take effect:

sudo usermod -aG docker ${USER}

Add a Docker container to your GNS3

Now that Docker is installed, pulling a Docker image from the Docker Hub is easy. A popular one is Alpine Linux because it’s so small, but packs lots of popular tools and libraries:

docker pull alpine

Now you should be able to add this image to GNS3. Go into GNS3, go to preferences, and all the way at the bottom where it says “Docker containers”. Click on “new”, and you should be able to select the Alpine Linux image from the drop-down menu:

Click through and leave the defaults, but you might want two network adapters instead of one, in case you want it to be a router. Now just drag a couple containers out onto the canvas:

At this point, you should be able to double click on these and get a busybox shell, which will let you configure IP settings and the like. You may have noticed that the startup of these containers is near-instantaneous, and they consume very little RAM. One of the many perks of the lightweight nature of Docker containers. Enjoy!