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