By Wayne Lai, Tuan Thai, and Howard Xiao
Fundamental Concept of VPN
VPN can be a buzzword that people throw around to make a project sound cool (nervous laughter). It is no doubt a fundamentally cool concept — encrypting your traffic so no intermediate party until the endpoint can see what you are doing. If you haven’t heard of the encryption aspect of VPN, you would probably still at least be familiar with the idea that VPN can mask your actual IP. But how does it do that?
The creation of VPN has more to do with accessing network resources within an organization securely than “protecting online privacy”. In a VPN setup, there are two primary parties: the client, and the internal network that contains the VPN server. The goal of VPN is to make you look like you are inside the network as one of the hosts. Once you are inside the network, you will be sharing network resources with other hosts on the system.
In this picture, the computer at 184.108.40.206 (H1) is connecting to the VPN server on 224.12.09.45 (NET) and appearing as 10.0.0.2 (IP2) in the internal network. If H1 sends all the internet traffic through VPN, to the outside world, H1 would look like it has the ip of NET. Every traffic from H1 to the VPN is encrypted, but every traffic going from IP2 through NET would not be encrypted.
Here are some things VPN can achieve:
- protect against eavesdropping between H1 and VPN server.
- with enough hosts, anonymize the traffics going out of NET.
- circumvent location restrictions placed on H1 by a website.
Why do we need a VPN router?
When we designed this project, the primary users we had in mind were people in Seattle Community Network (SCN). Therefore, there were several assumptions that were made:
- The network is set up and provided by the SCN.
- The users are not tech savvy enough to set up the VPN connection themselves.
A VPN router that provides a VPN access point will help us achieve the goal of providing VPN to the end users while requiring little setup. So that takes care of the router part of the question. However, maybe the more important question is why do we need VPN in the first place?
To understand why, we need to go through the structure of SCN.
In SCN, the user connection is routed through an encrypted wireless link that then sends the packets to an internet service provider (ISP). The ISP may send the packets to the University of Washington (UW) network through the internet, then UW will spit the package back out to the internet heading to the destination (we would assume that it is a website). In this configuration, it is useful to consider the situation where the users do not want the ISP or the UW to know which website they are heading to.
Imagine we set up an external VPN server somewhere. The ISP and UW will see encrypted traffic going to the VPN server and cannot see the final destination. The website will see traffic coming from a VPN server. This setup prevents the ISP and UW from knowing which website the user is visiting and provides anonymity against the website. For SCN, this means that they can now claim to the users that their traffic will not be censored based on UW policy and they will have increased anonymity when using SCN.
Similar structure can be set up for networks that want to prevent the entities that are providing the internet to see the traffic going through it. Moreover, any VPN setup increases anonymity by having a single external ip representing all the users in the network.
Spinning Up the Server
It is important that we spinned up our external VPN server instead of using a commercial VPN provider. This lets us have full control over user management, allowing us to add as many users and routers as we’d like. More importantly, by setting up the server on our own, we have confidence about the security and the privacy guarantees that we want to provide to our users.
After comparing a few options as to VPN protocol, we settled on WireGuard. We felt like WireGuard is lightweight enough that connection management would be easier than options like OpenVPN, while still providing full Layer 3 tunneling. At the same time, WireGuard is also well-designed enough to almost appear stateless, and is quite resistant to blips in Internet connectivity.
WireGuard itself doesn’t have much user management functionality and instead is primarily configured by adding peers into configuration files. In order to make this process dynamic and interfaceable, we designed our own API using Python and FastAPI so that we could quickly take in a WireGuard public key submitted by the user, do server-side validation and assign an internal IP address back. Since we also maintain a list of all expected connections in the API, we could automatically generate connection profiles on the server side and reload WireGuard on-demand.
A script then fetches a list of connections in the database and generate a WireGuard configuration file using them:
Router and OpenWrt Firmware setup
Since the project idea came from Esther — an organizer for Seattle Community Network, we were sponsored with a router that will be deployed on their network.
The first step in the project is to flash the OpenWrt firmware onto the router. To do that, we first need to find the corresponding router hardware model on the OpenWrt Table of Hardware.
Once we had the OpenWrt firmware on the router, we could start programming!
Struggles Between Different Technologies
When we were looking at the various options we had for programming on OpenWrt, our eyes were drawn to C and Rust. C is a natural choice for OpenWrt because it has the most robust support on OpenWrt. The toolchain is tried and tested and overall we would have had a higher probability of success if we used C. We were also looking at Rust because of the work from Justin Kilpatrick at Althea. Rust is overall a much better language in terms of memory safety. The potential for us to write a bug-ridden program that creates security risk for the users made us seriously consider the viability of using Rust. The effort proved to be impractical as we have limited timeframe to explore the use of Rust on OpenWrt.
We were able to establish a C toolchain by following this tutorial https://electrosome.com/cross-compile-openwrt-c-program/, so we began coding in C. However, when we wanted to pull the information from the server, we encountered an issue. The backend server is written with RESTful API. While this is a great design for its simplicity, it required the use of curl, which was not a pre-installed library on OpenWrt. While researching, we discovered that there is a curl package which allows us to run curl on OpenWrt. This discovery sparkled a brilliant idea — instead of using compiled language, we could use shell script. Shell script is great because it can directly access any existing command line tools. We had the uci system which is used for controlling configuration, the iproute2 that is used for controlling routing, and other utilities like curl and jshn that we downloaded for communication with the backend. Therefore, in the end, we settled on using shell script for the client side implementation.
Router Networking for VPN
The proper networking for VPN requires configuring the interfaces, firewall zones, Wi-Fi SSIDs, and routing.
Interfaces are used to categorize devices into different communication groups. The devices in the same group can talk to each other while the communication between different groups are limited by the firewall zone rule. We set up VPN482 for interfacing of VPN over the internet; WLANX for SSID that receives Wi-Fi traffic going through VPN; and WLANY for SSID that receives Wi-Fi traffic going through regular internet.
You would notice that the VPN zone has essentially the same configuration as WAN. This is because they are the two public facing interfaces going out to the internet. The firewall zones are set up so that the VPN SSID can only send traffic through the VPN zone while the no VPN SSID sends its traffic through the normal WAN interface.
Once all the interfaces and zones are up and running, we create and attach the two SSIDs to their respective interfaces.
The existence of a firewall is not a guarantee that the packet will arrive at the correct destination; it merely ensures that different zones don’t talk to each other. This was the part where we had the most misunderstanding, so we were blocked on it for some time.
Where the packets travel is dictated by the routing rules. The “Target’’ will match the address and send the packet to the corresponding interface. Our main problem was that we wanted every packet to be sent to the VPN while the user is connected to the VPN network. Whereas if a user is connected to the no VPN network we want every packet to be going through WAN. This created conflicting match rules where we had VPN capturing every packet when WAN was also trying to capture every packet. The result was either the VPN access point works and the no VPN access point does not or vice versa. The solution was to create a separate table for VPN traffic and tell the WLANX interface to look at the VPN table by default. This way, when a user is connected to VPN SSID, the routing in the VPN table will take priority over the main table.
With these, we have a working auto-configuring VPN router. The users just need to input the endpoint URL and the API Token given to them and their router will be able to query the backend server for a new VPN connection.
Overall we found that within the scope of our class time and knowledge restraints, we were able to progress our project to a point we could be proud of. When we were drafting our plans, we had the goal of creating an easy and robust manner for less than tech-savvy users to have VPN access. In the end, we were able to make a multifaceted product that is capable of providing both serverside and clientside services for setting up a working VPN.
In the future, we would love to add better quality of life features to our product. For example, on the server side, making it more intuitive to modify our program in order to best suit the clients needs would be helpful. This would most likely be done through an interface or an equivalent external tool. Most of our focus was based on making the project as easy as possible for the VPN users to install and therefore not as much work was placed on the server side. We can also add in some network health utilities to assist administration. For example, reporting back ping speed periodically.
Along the way, we didn’t run into any major roadblocks but some of the steepest learning curves came with utilizing the OpenWrt toolchain and their APIs. Sticking close to the documentation and trying to expand from their basic examples helped us a lot during the learning process. Another good idea would be to set up VMs that house OpenWrt when experimenting with the scripts as this makes it easier to troubleshoot the LUCI configuration and limit hardware difficulties. In the case of hardware, most routers that we looked at had a limited amount of memory so being particularly careful with what packages you need to use is of utmost importance.
Credits and Acknowledgement
The team thanks the 482A course staff and the Seattle Community Network for their generous support of our work, from ideation to testing. The team additionally thanks Karl Koscher for his help in threat modeling and Tong Nguyen for (rather involuntarily) volunteering for user experience testing during OpenWrt package development.
We would like to hear from you about our programming. The team may be contacted by writing to Paul G. Allen School of Computer Science & Engineering, Box 352350, Seattle, WA 98195, in care of Howard Xiao. We conclude our broadcast day and will return to the air tomorrow morning. Good night.