Censored Lab
A page with instructions on how to set up a censorship research and testing lab in Virtualbox, as well as results of simulations and tests we’ve run.
General Information
For general information about how censorship works, and information about IP Addresses, DNS, Deep Packet Inspection, and other subjects, see:
Censorship and Surveillance in the Digital Age: Part One
Censorship and Surveillance in the Digital Age: Part Two
Golden Shield: The Inner Workings of China's Great Firewall
Behind the Blackout: The Mechanisms, Monitoring, and Impact of Internet Shutdowns
See also the Resources Page of this blog for links to anti-censorship tools and other informational sites.
Lab Setup
For simulating online censorship, a lab can be set up using Virtualbox installed on a host computer. Virtualbox can be downloaded for Windows, MacOS, and Linux from HTTPS://www.virtualbox.org. Be sure to download and install the extension pack as well. For Ubuntu and other Linux distros, be sure to download the correct edition (i.e. Ubuntu 24.02) because unmet dependencies can be a problem. I recommend four different virtual machines:
IPFire as a firewall. Will be used for simulating the actions of a government censor. Download here.
Windows 10 as one of the censored computers. Should have Psiphon, Lantern, Tor, and a VPN installed. Use the Microsoft Media Creation Tool.
Kali Linux for running Wireshark. Can also be used as a censorship platform with iptables and dnsmasq (discussed later). Download here.
Ubuntu 24.04 as another censored machine. Download Here.
Note: I have had different firewalls work better with certain hosts. IPFire works well on Windows, but other choices include OPNSense (complicated), PFSense (a little less complicated), ClearOS (hard to install) among others. IPFire is easy to install and configure.
The initial setup will look like this:
The IPFire virtual machine (VM) acts as our firewall, hands out IP Addresses to the other VMs via DHCP, and acts as a DNS server. When IPFire is set up on Virtualbox, there is a GREEN interface and a RED interface. In our case, the green interface is set to the internal network and the red interface to NAT which provides internet access via the host computer. Instructions for installing IPFire on Virtualbox can be found HERE, and instructions for IPFire networking HERE.
The subnet for the internal network is 192.168.1.0/24, and the IPFire VM has an IP Address of 192.168.1.1. Windows 10, Kali, and Ubuntu are all set to internal networking only. This means they can only access the internet via the firewall. So, network settings for all three need the following settings:
Receive IP Address automatically (DHCP from IPFire)
DNS server to IPFire IP Address (192.168.1.1)
I disabled IPV6 for simplicity, but in real life IPV6 would also have to be blocked to censor access to DNA, etc.
For Kali, install Wireshark as follows (if not already installed);
sudo apt update
sudo apt install wireshark
sudo wireshark
Section One:
DNS Blocking and Poisoning
Blocking DNS can be challenging. Modern browsers will fallback to any available DNS servers unless they are all blocked. Blocking DNS (port 53) only works if the browsers don’t have HTTPS over DNS (Port 443). Blocking all traffic to 443 blocks all websites, not just censored sites. Windows caches DNS, so the cache has to be cleared for previously visited sites.
With port 53 and port 443 NOT blocked and DNS servers not blocked, an nslookup (Windows) or dig (Linux) for bbc.com is run and works. Accessing BBC on firefox while Wireshark is running shows the DNS requests:
Blocking all common DNS servers but not blocking all traffic to port 443 on IPFire and doing the same thing shows a different result. This involved adding rules blocking port 443 for
Cloudflare 1.1.1.1, 1.0.0.1 2606:4700:4700::1111 (IPv6)
Google 8.8.8.8, 8.8.4.4 2001:4860:4860::8888 (IPv6)
Quad9 9.9.9.9, 149.112.112.112
NextDNS 45.90.28.0/24, 45.90.30.0/24
OpenDNS 208.67.222.222, 208.67.220.220
CleanBrowsing 185.228.168.168, 185.228.169.168
AdGuard 94.140.14.14, 94.140.15.15
Comodo Secure DNS 8.26.56.26, 8.20.247.20
Neustar UltraDNS 156.154.70.1, 156.154.71.1
Cloudflare for Families 1.1.1.2, 1.0.0.2
When this is done, ping 8.8.8.8 works because it doesn’t require DNS, but dig google.com does not work (DNS is blocked). Lantern also does NOT work with DNS blocked. Tor does not access BBC.com but will connect. Wireshark with filters for port 53 or 443 doesn’t show any captures. This mimics real-world censorship in a country like China.
So, how does a person surfing the web in China get to a website since all foreign DNS serves are blocked? China has servers run by the government. This gives the PRC tight control over what sites can be reached (combined with other methods of censorship). The state controlled DNS servers can be configured to allow access to approved sites or to block or poison requests for censored sites.
DNS poisoning can be demonstrated using dnsmasq on Kali. This requires a reconfiguration of the Virtualbox network so traffic from Windows or Ubuntu goes through Kali instead of IPFire. The new setup will look like this:
Kali has dnsmasq installed and also has the Linux firewall iptables. The dnsmasq program will be used to mimic DNS poisoning and can be installed with the commands:
sudo apt update
sudo apt install dnsmasq
DNS on Ubuntu is set to Kali’s IP. To setup dnsmasq on Kali to block or poison DNS to certain sites, the config file needs to be edited:
sudo nano /etc/dnsmasq.conf
Then edit the file:
# Basic DNS settings
port=53
domain-needed
bogus-priv
no-resolv
# Use public DNS servers upstream
server=1.1.1.1
server=8.8.8.8
# Spoofed/poisoned domains
address=/facebook.com/0.0.0.0
address=/youtube.com/0.0.0.0
address=/bbc.com/0.0.0.0
address=/twitter.com/0.0.0.0
address=/instagram.com/0.0.0.0
address=/wikipedia.org/0.0.0.0
Save this then exit. Now when Ubuntu is forced to use Kali’s DNS, dig facebook.com does not work, but dig google.com does. The DNS is poisoned and redirects to 0.0.0.0. This shows how DNS could be blocked by cenosrs. The problem with this method is that the censored computer (Ubuntu) has to have to many configuration changes to make it work. (I had to edit two other files to really block DNS). With Windows 10 instead of Ubuntu, though, DNS was blocked with just dnsmasq changes (the browser could not reach any blocked sites, but could reach any other site).
If Wireshark is run for the requests and filtered for DNS and DNS over HTTPS, surfing to google.com shows the normal DNS request:
bbc.com on the other hand, shows:
What’s really interesting, this was done with DNS over HTTPS off in Firefox. If Privacy and Security settings are changed to use DNS over HTTPS instead, the sites can be accessed. To block this too, DNS over port 443 for all or most common DNS servers has to be blocked as above.
Another interesting thing we can do is set up dnsmasq on Kali to log all attempts to reach blocked sites. We have to modify the config dnsmasq config file again and add
log-queries
log-facility=/var/log/dnsmasq.log
the restart dnsmasq:
sudo systemctl restart dnsmasq
and start logging:
sudo tail -f /var/log/dnsmasq.log
If the user on Windows tries to browse to facebook.com, the censor will see
Conclusion of This Section
The goal of all of this is to simulate how a repressive government like China could block and poison DNS. One thing to add: since I am not an expert in networking (although I have a Network+ certification), I LIBERALLY relied on ChatGPT and Perplexity to help resolve problems and develop testing ideas. All the writing is mine, but much of the technical stuff would have been difficult without AI help.
Section Two:
IP Blocking
Blocking IP Addresses is more straightforward than DNS poisoning. We can just reconfigure our lab setup back to the first one above. Change the DNS on Windows to 192.168.1.1 and add some rules to our firewall. Picking on bbc.com again, we can do nslookup for bbc.com, and we get:
C:\Users\drnaa>nslookup bbc.com
Server: cdns01.comcast.net
Address: 75.75.75.75
Non-authoritative answer:
Name: bbc.com
Addresses: 2a04:4e42:400::81
2a04:4e42:600::81
2a04:4e42::81
2a04:4e42:200::81
151.101.0.81
151.101.64.81
151.101.128.81
151.101.192.81
The we add the rules to IPFire (remember we disabled IPV6 so we don’t have to add those right now):
A rule is made for each IP Address for bbc.com. Now, when the user tries to browse to the BBC, the browser will not connect. I we start Wireshark in Kali while trying to browse to one of the bbc.com IP Adresses, we get:
The request is dropped and the connection is not made.
In reality, a country like Iran might have a block list more like:
facebook.com 157.240.20.35
instagram.com 157.240.20.174
telegram.org 149.154.167.99
whatsapp.com 157.240.1.60
signal.org 35.231.145.151
youtube.com 142.250.72.238
vimeo.com1 51.101.64.217
bbc.com/persian 151.101.64.81
radiofarda.com 23.12.147.186
voanews.com 23.4.139.29
dw.com 194.55.30.46
amnesty.org 104.17.13.86
hrw.org 128.241.42.108
psiphon.ca 18.211.195.224
lantern.io 104.21.2.135
getoutline.org 35.202.130.166
torproject.org 116.202.120.166
dnscrypt.info 185.19.105.118
opennic.org 185.121.177.177
protonvpn.com 185.159.159.140
protonmail.com 185.70.40.101
cloudflare.com 104.16.124.96
archive.org 207.241.224.2
github.com 20.27.177.113
gitlab.com 172.65.251.78
ooni.org 89.234.186.112
accessnow.org 104.20.36.123
eff.org 104.20.22.46
zeronet.io 104.21.71.123
duckduckgo.com 40.89.244.232
and so forth up to 70+ sites.
Introduction: Implementing SNI-Based Censorship Using Suricata and iptables
In this lab, I implemented a basic deep packet inspection (DPI) censorship mechanism that mirrors techniques used by real-world network censors. The goal was to identify and block access to a specific website—in this case, www.torproject.org
—based on the Server Name Indication (SNI) field of encrypted TLS traffic.
To accomplish this, I configured Suricata, a powerful open-source intrusion detection system, to inspect network traffic on the gateway (Kali Linux) and generate an alert whenever an outbound TLS handshake contained the SNI torproject.org
. A custom rule was created in Suricata to match on this domain name, and its alerts were monitored in real time using a Bash script.
Upon detecting the SNI match, the script extracted the source IP address from the Suricata alert and dynamically inserted a DROP
rule into the system’s iptables
FORWARD chain. This blocked further traffic from the offending client. Additional safeguards were implemented to ensure IPv6 traffic—commonly used to bypass IPv4-based filtering—was also disabled or explicitly blocked.
This configuration demonstrates a simple but effective censorship model that can:
Detect access to targeted encrypted services via SNI
Dynamically respond by blocking client traffic at the firewall level
Be expanded to include multiple domains, timed bans, or automated logging/reporting
The result is a functioning prototype of content-based network control that highlights both the feasibility and limitations of SNI-based censorship in modern network environments.
Suricata Setup
sudo apt update
sudo apt install suricata
Confirm it is working
sudo suricata -i eth0 -v
Be sure config file is set for local.rules
sudo nano /etc/suricata/suricata.yaml
and be sure says
rule-files:
- local.rules
(It will likely say suricata.rules so change it)
Edit rules
sudo nano /var/lib/suricata/rules/local.rules
Then
sudo suricata-update
sudo systemctl restart suricata
Now, if you go to te Windows VM and surf to torproject.org, you will get and alert if you check
sudo tail -f /var/log/suricata/fast.log
Suricata alert for torproject.org
{"timestamp":"2025-06-14T13:41:14.161249-0400","flow_id":609456373043862,"in_iface":"eth0","event_type":"alert","src_ip":"192.168.1.101","src_port":50580,"dest_ip":"204.8.99.146","dest_port":443,"proto":"TCP","pkt_src":"wire/pcap","tx_id":0,"alert":{"action":"allowed","gid":1,"signature_id":100001,"rev":1,"signature":"Blocked SNI contains torproject.org","category":"","severity":3},"tls":{"sni":"www.torproject.org","version":"TLS 1.3"},"app_proto":"tls","direction":"to_server","flow":{"pkts_toserver":5,"pkts_toclient":5,"bytes_toserver":2197,"bytes_toclient":3208,"start":"2025-06-14T13:41:14.076364-0400","src_ip":"192.168.1.101","dest_ip":"204.8.99.146","src_port":50580,"dest_port":443}}
Now, we can go back to our IP and DNS blocking and add rules to block Tor. We could also set Suricata to trigger a rule blocking torproject.org.
This is a little more complicated.
First, Kali has to be set as the gateway (IPFire is off)
On Virtualbox, Kali's first interface is NAT, the second in the internal network.
Go to
sudo nano /etc/network/interfaces
and add
auto eth0
iface eth0 inet dhcp
auto eth1
iface eth1 inet static
address 192.168.56.1
netmask 255.255.255.0
Then enable NAT and FORWARD rules on Kali
# Enable IP forwarding
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
sudo sysctl -w net.ipv4.ip_forward=1
# Make it permanent
sudo sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/' /etc/sysctl.conf
Then add the Forwarding Rules
# NAT for outbound traffic
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# Allow forwarding
sudo iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
Add Suricata TLS rule
sudo nano /var/lib/suricata/rules/local.rules
Add the rule (all in one line)
alert tls any any -> any any (msg:"Blocked SNI contains torproject.org"; tls_sni; content:"torproject.org"; nocase; sid:100001; rev:1;)
Run Suricata
sudo suricata -c /etc/suricata/suricata.yaml -i eth1
Add the IP-Blocking Script to block torproject.org in real time
sudo nano /usr/local/bin/suri-block.sh
#!/bin/bash
LOGFILE="/var/log/suri-blocked.log"
EVE_FILE="/var/log/suricata/eve.json"
# Log the user running the script
echo "Running as user: $(whoami)" >> /tmp/suri-script.log
tail -Fn0 "$EVE_FILE" | jq -c 'select(.event_type=="alert" and .alert.signature_id==100001)' | while read -r line; do
SRC_IP=$(echo "$line" | jq -r '.src_ip')
MSG=$(echo "$line" | jq -r '.alert.signature')
DATE=$(date +'%Y-%m-%d %H:%M:%S')
# Debug log
echo "[DEBUG] Attempting to block $SRC_IP at $DATE" >> /tmp/suri-debug.log
# Block source IP on FORWARD chain if not already blocked
if ! iptables -C FORWARD -s "$SRC_IP" -j DROP 2>/dev/null; then
iptables -A FORWARD -s "$SRC_IP" -j DROP 2>> /var/log/suri-iptables-errors.log
if [ $? -eq 0 ]; then
echo "$DATE - Blocked $SRC_IP for: $MSG" | tee -a "$LOGFILE"
else
echo "$DATE - Failed to block $SRC_IP" >> /var/log/suri-iptables-errors.log
fi
fi
done
Make it executable
sudo chmod +x /usr/local/bin/suri-block.sh
Then run it
sudo /usr/local/bin/suri-block.sh
When I tried to go to www.torproject.org on Windows, I get the following on Suricata
2025-06-14 20:46:05 - Blocked 192.168.56.101 for: Blocked SNI contains torproject.org
Note, to make this work, I disabled ipv6 on Kali
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
Firefox does not reach torproject.org, and If I check
sudo iptables -L FORWARD -n --line-numbers
I get
Chain FORWARD (policy ACCEPT)
num target prot opt source destination
1 DROP all -- 192.168.56.101 0.0.0.0/0
So, browsing to torproject.org triggers an alert as seen in the first part, then blocks access to the website by triggering an iptables rule!
If we start Tor Browser and go to www.torproject.org, it does connect and the rule is not triggered. I ran
sudo tcpdump -i eth1 host 192.168.56.101
and got
14:16:16.434242 ARP, Request who-has 192.168.56.101 tell 192.168.56.1, length 28
14:16:16.435977 ARP, Reply 192.168.56.101 is-at 08:00:27:7f:00:56 (oui Unknown), length 46
14:16:17.388364 IP static.166.233.108.65.clients.your-server.de.9001 > 192.168.56.101.49191: Flags [P.], seq 387115844:387116380, ack 150732934, win 65535, length 536
14:16:17.452908 IP 192.168.56.101.49191 > static.166.233.108.65.clients.your-server.de.9001: Flags [.], ack 536, win 64240, length 0
14:16:19.382150 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [P.], seq 386754002:386754538, ack 948302314, win 65535, length 536
14:16:19.468551 IP 192.168.56.101.49192 > v2202504171896332841.powersrv.de.45785: Flags [.], ack 536, win 63704, length 0
14:16:25.431883 IP static.166.233.108.65.clients.your-server.de.9001 > 192.168.56.101.49191: Flags [P.], seq 536:1072, ack 1, win 65535, length 536
14:16:25.480870 IP 192.168.56.101.49191 > static.166.233.108.65.clients.your-server.de.9001: Flags [.], ack 1072, win 63704, length 0
14:16:26.672183 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [P.], seq 536:1072, ack 1, win 65535, length 536
14:16:26.717379 IP 192.168.56.101.49192 > v2202504171896332841.powersrv.de.45785: Flags [.], ack 1072, win 63168, length 0
14:16:29.469297 IP 192.168.56.101.49192 > v2202504171896332841.powersrv.de.45785: Flags [P.], seq 1:537, ack 1072, win 63168, length 536
14:16:29.469907 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [.], ack 537, win 65535, length 0
14:16:29.707100 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [P.], seq 1072:1608, ack 537, win 65535, length 536
14:16:29.710099 IP 192.168.56.101.49192 > v2202504171896332841.powersrv.de.45785: Flags [P.], seq 537:1587, ack 1608, win 64240, length 1050
14:16:29.710976 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [.], ack 1587, win 65535, length 0
14:16:29.932352 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [.], seq 1608:3068, ack 1587, win 65535, length 1460
14:16:29.932408 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [.], seq 3068:4528, ack 1587, win 65535, length 1460
14:16:29.932410 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [P.], seq 4528:5228, ack 1587, win 65535, length 700
14:16:29.933267 IP 192.168.56.101.49192 > v2202504171896332841.powersrv.de.45785: Flags [.], ack 5228, win 64240, length 0
14:16:30.061351 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [P.], seq 5228:5764, ack 1587, win 65535, length 536
14:16:30.063834 IP 192.168.56.101.49192 > v2202504171896332841.powersrv.de.45785: Flags [P.], seq 1587:2637, ack 5764, win 63704, length 1050
14:16:30.064440 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [.], ack 2637, win 65535, length 0
14:16:30.074614 IP static.166.233.108.65.clients.your-server.de.9001 > 192.168.56.101.49191: Flags [P.], seq 1072:1608, ack 1, win 65535, length 536
14:16:30.148596 IP 192.168.56.101.49191 > static.166.233.108.65.clients.your-server.de.9001: Flags [.], ack 1608, win 63168, length 0
14:16:30.205620 IP 192.168.56.101.49191 > static.166.233.108.65.clients.your-server.de.9001: Flags [.], seq 1:1461, ack 1608, win 63168, length 1460
14:16:30.205635 IP 192.168.56.101.49191 > static.166.233.108.65.clients.your-server.de.9001: Flags [P.], seq 1461:1565, ack 1608, win 63168, length 104
14:16:30.205636 IP 192.168.56.101.49192 > v2202504171896332841.powersrv.de.45785: Flags [P.], seq 2637:3173, ack 5764, win 63704, length 536
14:16:30.206004 IP static.166.233.108.65.clients.your-server.de.9001 > 192.168.56.101.49191: Flags [.], ack 1461, win 65535, length 0
14:16:30.206061 IP static.166.233.108.65.clients.your-server.de.9001 > 192.168.56.101.49191: Flags [.], ack 1565, win 65535, length 0
14:16:30.206379 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [.], ack 3173, win 65535, length 0
14:16:30.269164 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [P.], seq 5764:6300, ack 3173, win 65535, length 536
14:16:30.273408 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [.], seq 6300:7760, ack 3173, win 65535, length 1460
14:16:30.273481 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [P.], seq 7760:9220, ack 3173, win 65535, length 1460
14:16:30.274311 IP 192.168.56.101.49192 > v2202504171896332841.powersrv.de.45785: Flags [.], ack 9220, win 64240, length 0
14:16:30.312728 IP v2202504171896332841.powersrv.de.45785 > 192.168.56.101.49192: Flags [.], seq 9220:10680, ack 3173, win 65535, length 1460
So we captured Windows communicating on non standard ports:
9001 — commonly used by Tor relays
45785 — likely another Tor relay port
We could add a rule to Suricata to trigger an alert on port 9001
alert tcp any any -> any 9001 (msg:"Tor ORPort Access Detected"; sid:100005; rev:1;)
And we could look up know Tor relay IPs and block them
https://check.torproject.org/torbulkexitlist
https://metrics.torproject.org/collector.html
Then we could try to access sites using Tor pluggable transports like
obfs4
Snowflake
Meek