Skip to content

Iodine

Iodine is a DNS tunneling tool that encapsulates IP traffic within DNS queries and responses, allowing you to tunnel network traffic through restrictive firewalls and captive portals that only permit DNS traffic. It establishes a PPP tunnel interface (dns0) over DNS, enabling full network access when direct connections are blocked.

sudo apt update
sudo apt install iodine
brew install iodine
git clone https://github.com/yarrick/iodine.git
cd iodine
make
sudo make install

Verify installation:

iodine --version
iodined --version
  1. Delegated subdomain: Use a domain you control (e.g., tunnel.example.com)
  2. DNS nameserver: The delegated domain must point to your iodined server

Create NS record delegation at your domain registrar:

tunnel.example.com NS ns1.tunnel.example.com
ns1.tunnel.example.com A <your-server-ip>

This routes all DNS queries for *.tunnel.example.com to your iodined server listening on port 53.

Verification:

nslookup test.tunnel.example.com
# Should resolve to your server IP

Start the DNS tunneling server:

sudo iodined -f -c -P "SecurePassword123" 10.0.0.1 tunnel.example.com
OptionDescription
-fRun in foreground (don’t daemonize)
-cDisable IP check of incoming packets (enables NAT clients)
-P passwordSet tunnel authentication password
-n auto|IPListen address (auto or specific IP, default: 0.0.0.0)
-l 0.0.0.0Explicitly bind to all interfaces
-p 53Listen port (default 53, requires root)
-u usernameDrop privileges to user after bind
-t /var/emptyChroot to directory
-d deviceUse specific TUN device
-m mtuSet tunnel MTU (default 1024)
-MUse lazy mode for faster transfers
-w downstreamFragment size downstream (default 3333)
sudo iodined -u iodine -t /var/empty \
  -c -P "ComplexPassword!" \
  -n auto \
  -m 1024 \
  10.0.0.1 tunnel.example.com

Verify server is running:

sudo netstat -tulnp | grep 53
ps aux | grep iodined
sudo iodine -f -P "SecurePassword123" tunnel.example.com
OptionDescription
-fRun in foreground
-P passwordAuthentication password (must match server)
-rForce specific DNS resolver IP
-RBind to specific DNS port on client
-T record_typeDNS record type: NULL (default), TXT, SRV, MX, CNAME, A
-m mtuSet tunnel MTU (should match server)
-MEnable lazy mode (faster transfers)
-I intervalPing interval in seconds (default 4)
-L lazy_modeSet lazy mode strength (0-3)
-O encodingSet encoding: base32 (default), base64, base128, raw
-W window_sizeSet window size for flow control
sudo iodine -f -r 8.8.8.8 -P "password" tunnel.example.com
sudo iodine -f -T TXT -P "password" tunnel.example.com
TypeBandwidthNotes
NULL200-300 KB/sBest (most nameservers allow)
TXT150-200 KB/sGood fallback when NULL blocked
A50-80 KB/sLimited, some networks block
CNAME50-80 KB/sCan trigger warnings, avoid
MX100-150 KB/sReasonable fallback
SRV100-150 KB/sPossible but uncommon
  1. Try NULL first (fastest, widely supported)
  2. Fall back to TXT if NULL blocked
  3. Use A records as last resort (slowest)
# Try NULL (default)
sudo iodine -f tunnel.example.com

# Fall back to TXT
sudo iodine -f -T TXT tunnel.example.com

# Try A record
sudo iodine -f -T A tunnel.example.com

Once connected, you’ll see:

ifconfig dns0
# Shows: 10.0.0.x/255.255.255.0 (tun interface)
# Test tunnel is up
ping 10.0.0.1

# Test external connectivity
traceroute 8.8.8.8

Configure which traffic flows through the tunnel:

# Route all traffic through tunnel
sudo route add default gw 10.0.0.1

# Route specific subnet
sudo route add 192.168.1.0/24 gw 10.0.0.1

# Route to specific host
sudo route add 10.20.30.40 gw 10.0.0.1

Create a SOCKS proxy via SSH tunneling:

# Terminal 1: Establish iodine tunnel
sudo iodine -f -P "password" tunnel.example.com

# Terminal 2: SSH SOCKS proxy (once tunnel is up)
ssh -D 1080 user@10.0.0.1

# Terminal 3: Use SOCKS proxy
curl --socks5 127.0.0.1:1080 https://example.com

Set DNS for tunnel traffic:

# Temporary (single session)
echo "nameserver 10.0.0.1" | sudo tee -a /etc/resolv.conf

# Or use client option
sudo iodine -f -P "password" tunnel.example.com
# DNS automatically configured

Start conservative, then increase:

# Server (smaller MTU = more reliable)
sudo iodined -m 1024 10.0.0.1 tunnel.example.com

# Client (must match or be smaller than server)
sudo iodine -m 1024 tunnel.example.com

# Increase for faster speeds (if stable)
sudo iodined -m 2048 10.0.0.1 tunnel.example.com
sudo iodine -m 2048 tunnel.example.com
# Server: reduce downstream fragment size for stability
sudo iodined -w 2000 10.0.0.1 tunnel.example.com

Reduces ping overhead, increases speed:

# Server
sudo iodined -M 10.0.0.1 tunnel.example.com

# Client
sudo iodine -M tunnel.example.com

Choose encoding to optimize for network conditions:

# base32 (default, most compatible)
sudo iodine -O base32 tunnel.example.com

# base64 (faster, may be filtered)
sudo iodine -O base64 tunnel.example.com

# base128 (fastest, least compatible)
sudo iodine -O base128 tunnel.example.com
# 1. Connect to WiFi (will show captive portal)
# Don't authenticate if you want pure DNS tunnel

# 2. Start iodine tunnel
sudo iodine -f -P "password" tunnel.example.com

# 3. Once connected, traffic routes through DNS tunnel
# You have full network access regardless of captive portal

# 4. Verify connectivity
ping 8.8.8.8
curl https://example.com

Some networks block DNS on non-standard ports or filter record types:

# Try different DNS resolvers
sudo iodine -r 1.1.1.1 tunnel.example.com
sudo iodine -r 8.8.8.8 tunnel.example.com

# Try different record types
sudo iodine -T TXT tunnel.example.com
sudo iodine -T A tunnel.example.com

# Combine strategies
sudo iodine -r 1.1.1.1 -T TXT tunnel.example.com

Route only specific traffic through tunnel:

# Assume tunnel is up: 10.0.0.1

# Route corporate network through tunnel
sudo route add 10.20.0.0/16 gw 10.0.0.1

# Route specific server
sudo route add 10.30.40.50 gw 10.0.0.1

# All other traffic uses normal route (local ISP)

Route all traffic through tunnel (careful with latency):

# Save default route first
ip route show > /tmp/routes.bak

# Replace default route
sudo route del default
sudo route add default gw 10.0.0.1

# Restore later
sudo route del default
cat /tmp/routes.bak | while read line; do sudo route add $line; done
route -n
ip route show
netstat -r
# 1. Verify DNS resolution
nslookup test.tunnel.example.com
dig @ns1.example.com test.tunnel.example.com

# 2. Check server is listening
sudo netstat -tulnp | grep 53
sudo ss -tulnp | grep 53

# 3. Check firewall
sudo iptables -L -n | grep 53
sudo firewall-cmd --list-all

# 4. Enable firewall port
sudo ufw allow 53/udp
sudo firewall-cmd --permanent --add-port=53/udp
# 1. Test with lazy mode
sudo iodine -M -f tunnel.example.com

# 2. Increase MTU
sudo iodine -m 2048 tunnel.example.com

# 3. Try different encoding
sudo iodine -O base64 tunnel.example.com

# 4. Check latency
ping -c 5 10.0.0.1
# Verify server password
sudo ps aux | grep iodined
# Look for -P flag

# Ensure exact match on client
sudo iodine -P "ExactSamePassword" tunnel.example.com

# Check for special characters
# Use quotes to preserve spaces/special chars
# Check device exists
ls -la /dev/net/tun

# Create if missing
sudo mkdir -p /dev/net
sudo mknod /dev/net/tun c 10 200
sudo chmod 600 /dev/net/tun
# Manually set DNS
echo "nameserver 10.0.0.1" | sudo tee /etc/resolv.conf

# Or use resolvconf
echo "nameserver 10.0.0.1" | sudo resolvconf -a tun0

# Verify
cat /etc/resolv.conf
  • Use strong passwords: At least 16 characters, mix of complexity
  • Restrict server access: Firewall port 53 to authorized IPs only
  • Run as unprivileged user: Use -u iodine -t /var/empty
  • Use HTTPS tunneling: Encrypt data within the tunnel
  • Rotate credentials: Change password regularly in production
  • Start conservative: Begin with default MTU (1024), increase carefully
  • Monitor packet loss: Check ping output for drops
  • Use lazy mode judiciously: Faster but less reliable on poor connections
  • Match MTU on client and server: Prevents fragmentation
  • Test in staging: Verify before production deployment
  • Monitor DNS logs: Track tunnel usage at the DNS level
  • Set up alerts: Monitor connection drops and errors
  • Document configuration: Keep record of MTU, encoding, record type settings
  • Have fallbacks: Prepare alternative tunneling methods (dnscat2, dns2tcp)
  • Test regularly: Verify tunnel works from different networks
ToolPurposeComparison
dnscat2DNS tunneling with C&C capabilitiesFull shell over DNS, more features, complex setup
dns2tcpTCP tunneling over DNSLighter weight, simpler than iodine, slower
ChiselHTTP/HTTPS reverse proxy tunnelingModern, SSH-like syntax, easier setup, not DNS-based
NSTXDNS tunneling via SSHOlder, less maintained, similar to iodine
DNSExfilDNS exfiltration onlyOne-way data extraction, not bidirectional
  • Firewall bypass: When only DNS port 53 is open
  • Captive portal evasion: Hotel/airport WiFi restrictions
  • Low-bandwidth pivoting: Need minimal bandwidth for command execution
  • Reliable covert channel: More stable than ICMP or other methods
  • Full shell needed: Use dnscat2 (more features)
  • Modern infrastructure: Use Chisel (simpler, faster)
  • Quick lightweight tunnel: Use dns2tcp (minimal overhead)