VPN Lab: The Container Version
VPN Lab: The Container Version
VPN Lab: The Container Version
1 Overview
A Virtual Private Network (VPN) is a private network built on top of a public network, usually the Internet.
Computers inside a VPN can communicate securely, just like if they were on a real private network that is
physically isolated from outside, even though their traffic may go through a public network. VPN enables
employees to securely access a company’s intranet while traveling; it also allows companies to expand their
private networks to places across the country and around the world.
The objective of this lab is to help students understand how VPN works. We focus on a specific type of
VPN (the most common type), which is built on top of the transport layer. We will build a very simple VPN
from the scratch, and use the process to illustrate how each piece of the VPN technology works. A real VPN
program has two essential pieces, tunneling and encryption. This lab only focuses on the tunneling part,
helping students understand the tunneling technology, so the tunnel in this lab is not encrypted. There is
another more comprehensive VPN lab that includes the encryption part. The lab covers the following topics:
Readings and videos. Detailed coverage of the TUN/TAP virtual interface and how VPN works can be
found in the following:
• Chapter 19 of the SEED Book, Computer & Internet Security: A Hands-on Approach, 2nd Edition,
by Wenliang Du. See details at https://www.handsonsecurity.net.
• Section 8 of the SEED Lecture, Internet Security: A Hands-on Approach, by Wenliang Du. See details
at https://www.handsonsecurity.net/video.html.
Related lab. This lab only covers the tunneling part of a VPN, while a complete VPN also needs to protect
its tunnel. We have a separate lab, called VPN Lab, which is a comprehensive lab, covering both tunneling
and the protection part. Students can work on this tunneling lab first. After learning the PKI and TLS, they
can then move on to the comprehensive VPN lab.
Lab environment. This lab has been tested on the SEED Ubuntu 20.04 VM. You can download a pre-built
image from the SEED website, and run the SEED VM on your own computer. However, most of the SEED
labs can be conducted on the cloud, and you can follow our instruction to create a SEED VM on the cloud.
SEED Labs – VPN Lab: The Container Version 2
Network: 10.9.0.0/24
10.9.0.11
VPN Client VPN Server
` (Router)
User
(Host U) 10.9.0.5 192.168.60.11
Network: 192.168.60.0/24
Host V
` ` `
192.168.60.5 192.168.60.6 192.168.60.7
In practice, the VPN client and VPN server are connected via the Internet. For the sake of simplicity,
we directly connect these two machines to the same LAN in this lab, i.e., this LAN simulates the Internet.
The third machine, Host V, is a computer inside the private network. Users on Host U (outside of the
private network) want to communicate with Host V via the VPN tunnel. To simulate this setup, we connect
Host V to VPN Server (also serving as a gateway). In such a setup, Host V is not directly accessible from
the Internet; nor is it directly accessible from Host U.
Lab setup. Please download the Labsetup.zip file to your VM from the lab’s website, unzip it, enter
the Labsetup folder, and use the docker-compose.yml file to set up the lab environment. Detailed
explanation of the content in this file and all the involved Dockerfile can be found from the user manual,
which is linked to the website of this lab. If this is the first time you set up a SEED lab environment using
containers, it is very important that you read the user manual.
In the following, we list some of the commonly used commands related to Docker and Compose. Since
we are going to use these commands very frequently, we have created aliases for them in the .bashrc file
(in our provided SEEDUbuntu 20.04 VM).
$ docker-compose build # Build the container image
$ docker-compose up # Start the container
$ docker-compose down # Shut down the container
All the containers will be running in the background. To run commands on a container, we often need
to get a shell on that container. We first need to use the "docker ps" command to find out the ID of
the container, and then use "docker exec" to start a shell on that container. We have created aliases for
them in the .bashrc file.
$ dockps // Alias for: docker ps --format "{{.ID}} {{.Names}}"
$ docksh <id> // Alias for: docker exec -it <id> /bin/bash
$ docksh 96
root@9652715c8e0a:/#
If you encounter problems when setting up the lab environment, please read the “Common Problems”
section of the manual for potential solutions.
Shared folder. In this lab, we need to write our own code and run it inside containers. Code editing is
more convenient inside the VM than in containers, because we can use our favorite editors. In order for the
VM and container to share files, we have created a shared folder between the VM and the container using
the Docker volumes. If you look at the Docker Compose file, you will find out that we have added the
following entry to some of the containers. It indicates mounting the ./volumes folder on the host machine
(i.e., the VM) to the /volumes folder inside the container. We will write our code in the ./volumes
folder (on the VM), so they can be used inside the containers.
volumes:
- ./volumes:/volumes
Packet sniffing. Being able to sniffing packets is very important in this lab, because if things do not go
as expected, being able to look at where packets go can help us identify the problems. There are several
different ways to do packet sniffing:
• Running tcpdump on containers. We have already installed tcpdump on each container. To sniff
the packets going through a particular interface, we just need to find out the interface name, and then
do the following (assume that the interface name is eth0):
# tcpdump -i eth0 -n
It should be noted that inside containers, due to the isolation created by Docker, when we run
tcpdump inside a container, we can only sniff the packets going in and out of this container. We
SEED Labs – VPN Lab: The Container Version 4
won’t be able to sniff the packets between other containers. However, if a container uses the host
mode in its network setup, it can sniff other containers’ packets.
• Running tcpdump on the VM. If we run tcpdump on the VM, we do not have the restriction on the
containers, and we can sniff all the packets going among containers. The interface name for a network
is different on the VM than on the container. On containers, each interface name usually starts with
eth; on the VM, the interface name for the network created by Docker starts with br-, followed by
the ID of the network. You can always use the ip address command to get the interface name on
the VM and containers.
• We can also run Wireshark on the VM to sniff packets. Similar to tcpdump, we need to select what
interface we want Wireshark to sniff on.
Testing. Please conduct the following testings to ensure that the lab environment is set up correctly:
import fcntl
import struct
SEED Labs – VPN Lab: The Container Version 5
import os
import time
from scapy.all import *
TUNSETIFF = 0x400454ca
IFF_TUN = 0x0001
IFF_TAP = 0x0002
IFF_NO_PI = 0x1000
while True:
time.sleep(10)
Once the program is executed, it will block. You can go to another terminal and get a new shell on the
container. Then print out all the interfaces on the machine. Please report your observation after running the
following command:
# ip address
You should be able to find an interface called tun0. Your job in this task is to change the tun.py
program, so instead of using tun as the prefix of the interface name, use your last name as the prefix. For
example, if your last name is smith, you should use smith as the prefix. If your last name is long, you can
use the first five characters. Please show your results.
To make life easier, students can add the following two lines of code to tun.py, so the configuration
can be automatically performed by the program.
os.system("ip addr add 192.168.53.99/24 dev {}".format(ifname))
os.system("ip link set dev {} up".format(ifname))
After running the two commands above, run the "ip address" command again, and report your
observation. How is it different from that before running the configuration commands?
Please run the revised tun.py program on Host U, configure the TUN interface accordingly, and then
conduct the following experiments. Please describe your observations:
• On Host U, ping a host in the 192.168.53.0/24 network. What are printed out by the tun.py
program? What has happened? Why?
• On Host U, ping a host in the internal network 192.168.60.0/24, Does tun.py print out
anything? Why?
• After getting a packet from the TUN interface, if this packet is an ICMP echo request packet, construct
a corresponding echo reply packet and write it to the TUN interface. Please provide evidence to show
that the code works as expected.
• Instead of writing an IP packet to the interface, write some arbitrary data to the interface, and report
your observation.
The server program tun server.py. We will run tun server.py program on VPN Server. This
program is just a standard UDP server program. It listens to port 9090 and print out whatever is received.
The program assumes that the data in the UDP payload field is an IP packet, so it casts the payload to a
Scapy IP object, and print out the source and destination IP address of the enclosed IP packet.
IP_A = "0.0.0.0"
PORT = 9090
while True:
data, (ip, port) = sock.recvfrom(2048)
print("{}:{} --> {}:{}".format(ip, port, IP_A, PORT))
pkt = IP(data)
print(" Inside: {} --> {}".format(pkt.src, pkt.dst))
Implement the client program tun client.py. First, we need to modify the TUN program tun.py.
Let’s rename it, and call it tun client.py. Sending data to another computer using UDP can be done
using the standard socket programming.
Replace the while loop in the program with the following: The SERVER IP and SERVER PORT
should be replaced with the actual IP address and port number of the server program running on VPN
Server.
# Create UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
# Get a packet from the tun interface
SEED Labs – VPN Lab: The Container Version 8
Testing. Run the tun server.py program on VPN Server, and then run tun client.py on Host
U. To test whether the tunnel works or not, ping any IP address belonging to the 192.168.53.0/24
network. What is printed out on VPN Server? Why?
Our ultimate goal is to access the hosts inside the private network 192.168.60.0/24 using the
tunnel. Let us ping Host V, and see whether the ICMP packet is sent to VPN Server through the tunnel.
If not, what are the problems? You need to solve this problem, so the ping packet can be sent through
the tunnel. This is done through routing, i.e., packets going to the 192.168.60.0/24 network should
be routed to the TUN interface and be given to the tun client.py program. The following command
shows how to add an entry to the routing table:
# ip route add <network> dev <interface> via <router ip>
Please provide proofs to demonstrate that when you ping an IP address in the 192.168.60.0/24
network, the ICMP packets are received by tun server.py through the tunnel.
Before running the modified tun server.py, we need to enable the IP forwarding. Unless specifi-
cally configured, a computer will only act as a host, not as a gateway. VPN Server needs to forward packets
between the private network and the tunnel, so it needs to function as a gateway. We need to enable the IP
forwarding for a computer to behave like a gateway. IP forwarding has already been enabled on the router
container. You can see in docker-compose.yml that the router container has the following entry:
sysctls:
- net.ipv4.ip_forward=1
Testing. If everything is set up properly, we can ping Host V from Host U. The ICMP echo request
packets should eventually arrive at Host V through the tunnel. Please show your proof. It should be noted
that although Host V will respond to the ICMP packets, the reply will not get back to Host U, because we
have not set up everything yet. Therefore, for this task, it is sufficient to show (using Wireshark or tcpdump)
that the ICMP packets have arrived at Host V.
SEED Labs – VPN Lab: The Container Version 9
while True:
# this will block until at least one interface is ready
ready, _, _ = select.select([sock, tun], [], [])
for fd in ready:
if fd is sock:
data, (ip, port) = sock.recvfrom(2048)
pkt = IP(data)
print("From socket <==: {} --> {}".format(pkt.src, pkt.dst))
... (code needs to be added by students) ...
if fd is tun:
packet = os.read(tun, 2048)
pkt = IP(packet)
print("From tun ==>: {} --> {}".format(pkt.src, pkt.dst))
... (code needs to be added by students) ...
Students can use the code above to replace the while loop in their TUN client and server programs.
The code is incomplete; students are expected to complete it.
Testing. Once this is done, we should be able to communicate with Machine V from Machine U, and the
VPN tunnel (un-encrypted) is now complete. Please show your wireshark proof using about ping and
telnet commands. In your proof, you need to point out how your packets flow.
SEED Labs – VPN Lab: The Container Version 10
// Add an entry
# ip route add <network prefix> via <router ip>
This setup simulates a situation where an organization has two sites, each having a private network. The
only way to connect these two networks is through the Internet. Your task is to set up a VPN between these
two sites, so the communication between these two networks will go through a VPN tunnel. You can use
the code developed earlier, but you need to think about how to set up the correct routing, so packets between
these two private networks can get routed into the VPN tunnel. In your report, please describe and explain
SEED Labs – VPN Lab: The Container Version 11
VPN Client VPN Server
(Router) (Router)
192.168.50.12 192.168.60.11
192.168.50.0/24 192.168.60.0/24
Host V
User ` `
(Host U) ` `
192.168.50.5 192.168.50.6 192.168.60.5 192.168.60.6
what you did. You need to provide proofs to show that the packets between the two private networks are
indeed going through a VPN tunnel.
while True:
packet = os.read(tap, 2048)
if packet:
ether = Ether(packet)
print(ether.summary())
SEED Labs – VPN Lab: The Container Version 12
The code above simply reads from the TAP interface. It then casts the data to a Scapy Ether object,
and prints out all its fields. Try to ping an IP address in the 192.168.53.0/24 network; report and
explain your observations.
To make this more interesting, once you get an ethernet frame from the TAP interface, you can check
whether it is an ARP request; if it is, generate a corresponding ARP reply and write it to the TAP interface.
A sample code is provided in the following:
while True:
packet = os.read(tun, 2048)
if packet:
print("--------------------------------")
ether = Ether(packet)
print(ether.summary())
To test your TAP program, you can run the arping command on any IP address. This command sends
out an ARP request for the specified IP address via the specified interface. If your spoof-arp-reply TAP
program works, you should be able to get a response. See the following examples.
arping -I tap0 192.168.53.33
arping -I tap0 1.2.3.4
11 Submission
You need to submit a detailed lab report, with screenshots, to describe what you have done and what you
have observed. You also need to provide explanation to the observations that are interesting or surprising.
Please also list the important code snippets followed by explanation. Simply attaching code without any
explanation will not receive credits.