Bypassing Deep Packet Inspection for VPN
When Wireguard and typical VPN gets blocked in your country and you're needing a little bit more...
Background
Following some civil unrests in my country, the government has slowly began to block off civilians from accessing a lot of sites. Unsurprisingly, people just got around this by simply downloading VPNs and using them to bypass government blockades. This worked for a while but then they started cracking down on VPNs too. Most of the free VPNs would simply refuse to work. For the paid apps, the government would setup checkpoints on the road where they would stop the cars and search the phones for VPN or other unwanted information and would be made to pay huge fines. This was something me and my family had to deal with and we would always have to delete the VPN apps when traveling downtown.
To bypass this, I deployed a wireguard server on Digital Ocean, and setup a Wireguard server which was cheaper then the monthly subscriptions that we were paying for VPNs. However, this didn't last long either as only a few months passed before the Wireguard connections were being throttled and sometimes not connecting at all.
I suspected that Wireguard connections were being blocked by DPI. Deep Packet Inspection (DPI) is a form of network packet filtering that examines the data part (and possibly also the header) of a packet as it passes an inspection point.DPI systems can recognize the patterns and signatures unique to Wireguard's protocol, even if the traffic is encrypted. This allows them to identify and block Wireguard connections. That is when I found ShadowSocks.
Solution
Shadowsocks is a secure socks5 proxy designed to protect your internet traffic. It was initially created by a Chinese developer to circumvent the Great Firewall of China, which imposes strict censorship on internet access. Shadowsocks works by setting up a proxy server. When you connect to the internet through this proxy, your internet traffic is encrypted and sent to the proxy server, which then forwards the traffic to its final destination. Shadowsocks is designed to make its traffic look like regular HTTPS traffic, which helps in evading DPI systems that block or throttle VPN traffic. A bonus is that Shadowsocks is less resource-intensive compared to traditional VPNs, resulting in faster performance and lower latency.
Configuring ShadowSocks server
First I created another droplet server on Digital Ocean with Ubuntu 24.04 LTS as the OS and with the data center nearest to my country

Next, I decided to go with lightest possible ShadowSocks implementation available which is the ShadowSocks-libev written in C#. There were few drawbacks like not having Workers - which handles multiple high traffic connections efficiently and also does not have Graceful Restart - which allows connections to be maintained even if the service was restarted.
My use case was for my family who does mostly browsing and social media so the traffic load wouldn't be high and a simple reconnection won't cause any significant issues for family use. A full table of the different implementations of ShadowSocks and its clients can be found here.
After setting up the server on Digital Ocean, I ssh into it. Then I installed the shadowsocks-libev:
sudo apt install shadowsocks-libevConfirm to complete the installation. Next is to setup the password and ports. I use nano as my editor for simplicity but anything else is also fine.
sudo mkdir -p /etc/shadowsocks-libev
sudo nano /etc/shadowsocks-libev/config.jsonThis will open up config.json file similar to this:
{
"server": "0.0.0.0",
"server_port": 8388,
"local_port": 1080,
"password": "yourstrongpassword",
"timeout": 300,
"method": "aes-256-gcm",
"plugin": "",
"plugin_opts": ""
}Change the server port to a less known port. Put a strong and secure password.
Method is the encryption method used for the connection. I have tried many different methods and some did not work well with the android clients or are not supported at all. Some desktop clients also do not support all available encryption. The ones that works best for me is "chacha20-ietf-poly1305" and "aes-256-gcm" which is also the recommended one but feel free to try out all encryptions. You can find the link to all types of encryption here. After that, save your config.json file.
Now all you need to do is start the service:
sudo systemctl start shadowsocks-libev
sudo systemctl enable shadowsocks-libevAfter that check if its running:
sudo systemctl status shadowsocks-libevThis should return something like this:
Active: active (running) since Tue 2024-06-16 20:33:40 UTC; 1 month 0 days agoSetting up client
When I used Wireguard, wg-easy had a convenient interface allowing for me to easily generate QR codes. This was a bonus for me as my parents are not tech-savvy at all and typing the IP address and port number with passwords would greatly confuse them. However, this feature is not available in the shadowsocks-libev. However, I found a way to generate one through the client.
First, I downloaded ShadowSocks for Windows and ran the application. A grey icon resembling a paper-plane should appear in your system tray.
Right click on the icon
Go to servers
Click on Edit Servers
Select and delete the sample server that was given
Click add
Fill the ip address and password (in config.json) for your server
Next select the encryption that you chose
Click ok
Now go back to ShadowSocks icon in the system tray, right click on it
Go to System Proxy
Choose Global
This should now enable and redirect all your system traffic through the proxy
Check and make sure all the website and connections are working. I ran a few speedtests as well to see how much load my server was accumulation. It was hitting barely 15% with two simultaneous speedtest (around 250 Mbps). If you are satisfied with the tests its now time to generate QR code.
Go to the system tray
Right click on the ShadowSocks icon
Select Edit Servers
Click on Share Server Config
Now a QR code should pop up in the menu
Make sure you choose the right server and take a screenshot of the QR
Now it can be shared to anyone and imported easily
(Optional) but you can also share the server url instead it works on most Windows and Android devices.
For Multiple users and passwords
I only use one password and user for my family so multiple passwords and ports are not necessary but there is a way to do so. All you have to do is duplicate the config.json file and create another service. Shadowsocks is written in a way that there's very little memory overhead even if multiple processes are run.
After writing another config.json file lets say config1.json
{
"server": "0.0.0.0",
"server_port": 8888,
"local_port": 1081,
"password": "yourstrongpassword",
"timeout": 300,
"method": "aes-256-gcm",
"plugin": "",
"plugin_opts": ""
}Make sure your port and local port are different for each config files. Then run this command:
ss-server -c config1.json -f /var/run/ss-server1.pidss-server is the shadowsocks-libev service
-c config1.json is the path to the .json file
-f /var/run/ss-server1.pid is used to the pid (process number) of this specific instance. This is useful if you want to stop this specific instance for any reason
If you want to kill a specific shadowsocks instance all you have to do is run:
A side note: I had this error on the Journalctl
Too many open files
This means the amount of file descriptors available for this shadowsocks process was exceeded. This made to enter a loop where it continuously opens and closes each file descriptor trying to read the network packages resulting 100% CPU usage. To fix this I simply increased the amount file descriptors available for the shadowsocks process and CPU usage went down.
sudo ulimit -n 4096For this code to work, I needed to stop and restart all my shadowsocks-libev process. This was made easy as I can just read the files from /var/run:
kill -SIGTERM $(cat /var/run/ss-server1.pid)Then I just do htop to see if there's any lingering shadowsocks processes left before restarting each one
Generating QR Codes
If you have multiple config.json files, it gets a bit tedious to generate QR codes for each of them. I wrote bash script that would allow me to instantly generate QR codes for all the config.json files which can be used to scan and connect directly.
The prerequisites to run this is to have qrencode and jq libraries installed.
sudo apt-get install qrencode
sudo apt-get install jqAfter installing create a text file with your favorite text editor and paste this script.
#!/bin/bash
# Fixed server IP
server_ip="your_server_ip"
# Folder containing configuration files
CONFIG_DIR=$(pwd)
# Folder to store QR codes
OUTPUT_DIR="${CONFIG_DIR}/qrcodes"
# Create output directory if it doesn't exist
mkdir -p "$OUTPUT_DIR"
# Function to generate ss:// link and QR code for a single config file
generate_qr() {
local config_file=$1
local output_file="${OUTPUT_DIR}/$(basename "$config_file" .json).png"
# Parse the configuration file for method, password, and port
method=$(jq -r '.method // empty' "$config_file")
password=$(jq -r '.password // empty' "$config_file")
port=$(jq -r '.server_port // empty' "$config_file")
# Check if any required value is missing
if [[ -z "$method" || -z "$password" || -z "$port" ]]; then
echo "Error: Missing required values in $config_file. Skipping QR generation."
return
fi
# Create the ss:// link using the fixed server IP
ss_link="ss://$(echo -n "${method}:${password}@${server_ip}:${port}" | base64)"
# Generate the QR code from the ss:// link
qrencode -o "$output_file" -t PNG "$ss_link"
echo "Generated QR code for $config_file at $output_file"
}
# Check the argument passed to the script
if [[ $1 == "all" ]]; then
# Generate QR codes for all config*.json files
for config_file in "$CONFIG_DIR"/config*.json; do
generate_qr "$config_file"
done
elif [[ -f "$1" ]]; then
# Generate QR code for the specified config file
generate_qr "$1"
else
echo "Usage: $0 all | <config.json>"
fiMake sure the script is in the same folder where you store your config files. Add your server ip between the quotation marks for server_ip variable. You can change were the qr_cdoes are stored by specifying a different folder in OUTPUT_DIR
Then make the script file executable
chmod +x generate_qrcodes.shNow you can run it to generate all QR codes at once or by specifying a config
./generate_qrcodes.sh all
./generate_qrcodes.sh config1.jsonLast updated