Testing Guide
This guide shows how to manually test and validate Moat's features.
Prerequisites
Install required tools:
apt-get update
apt-get install -y curl tcpdump tshark netcat-openbsd bind9-dnsutils jq
Start Moat with Logging
Enable Debug Logging
# Option 1: Environment variable
export RUST_LOG=debug
./target/release/moat --config config.yaml
# Option 2: Specific module logging
export RUST_LOG=moat::tcp_fingerprint=debug,moat::ja4_plus=debug,moat::access_log=debug
./target/release/moat --config config.yaml
# Option 3: Log to file
./target/release/moat --config config.yaml 2>&1 | tee moat.log
Watch Logs in Real-Time
# In a separate terminal
tail -f moat.log | grep -E "TCP_FP|JA4|Fingerprint"
Testing TCP Fingerprinting (JA4T)
Test IPv4 TCP Fingerprinting
# Simple HTTPS request (generates TCP SYN)
curl -v https://localhost:8443/
# Expected BPF log output:
# TCP_FP: New fingerprint from 127.0.0.1:xxxxx - TTL:64 MSS:1460 WS:7 Window:65535
# View the fingerprint in access logs
tail -f moat.log | grep "ja4t"
Test IPv6 TCP Fingerprinting
# Test with IPv6 localhost
curl -v -6 https://[::1]:8443/
# Test with global IPv6 address (if available)
curl -v -6 https://[2001:db8::1]:8443/
# Expected BPF log output:
# TCP_FP: New IPv6 fingerprint from ::1:xxxxx - TTL:64 MSS:1440 WS:7 Window:65535
Capture TCP Options with tcpdump
# Capture and display TCP SYN packets with options
sudo tcpdump -i any -nn -vv 'tcp[tcpflags] & tcp-syn != 0' port 8443
# Output shows:
# - TTL (from IP header)
# - Window size
# - MSS option (kind=2)
# - Window scale option (kind=3)
# - SACK permitted (kind=4)
# - Timestamps (kind=8)
JA4T Format
The JA4T format is: {window_size}_{tcp_options}_{mss}_{window_scale}
Example: 65535_2-4-8-1-3_1460_7
- Window size: 65535
- TCP options: MSS(2), SACK(4), Timestamps(8), NOP(1), Window Scale(3)
- MSS value: 1460
- Window scale: 7
Testing HTTP Fingerprinting (JA4H)
Basic HTTP Request
# Simple GET request
curl -v https://localhost:8443/test
# Expected JA4H format: {method}{version}{cookie}{referer}{count}{lang}_{headers_hash}_{cookie_names_hash}_{cookie_values_hash}
# Example: ge11nr05n_a1b2c3d4e5f6_000000000000_000000000000
Test with Custom Headers
# With cookies
curl -v -H "Cookie: session=abc123; user=john" \
https://localhost:8443/test
# With referer
curl -v -H "Referer: https://example.com" \
https://localhost:8443/test
# With language
curl -v -H "Accept-Language: en-US,en;q=0.9" \
https://localhost:8443/test
# With multiple headers
curl -v \
-H "Cookie: session=abc123" \
-H "Referer: https://example.com" \
-H "Accept-Language: en-US" \
-H "User-Agent: CustomAgent/1.0" \
-H "Accept: application/json" \
https://localhost:8443/test
# Expected JA4H: ge11cr08enus_<hash>_<cookie_names_hash>_<cookie_values_hash>
Compare Different Browsers
# Simulate Chrome
curl -v -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
-H "Accept: text/html,application/xhtml+xml" \
-H "Accept-Language: en-US,en;q=0.9" \
-H "Accept-Encoding: gzip, deflate, br" \
https://localhost:8443/
# Simulate Firefox
curl -v -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0" \
-H "Accept: text/html,application/xhtml+xml" \
-H "Accept-Language: en-US,en;q=0.5" \
-H "Accept-Encoding: gzip, deflate, br" \
https://localhost:8443/
# Compare JA4H hashes - they should be different
Testing TLS Client Fingerprinting (JA4)
Test Default curl
# Default TLS settings
curl -v https://localhost:8443/
# Check curl's TLS library
curl --version | grep -E "OpenSSL|GnuTLS|NSS"
Test with Different TLS Versions
# Force TLS 1.2
curl -v --tlsv1.2 --tls-max 1.2 https://localhost:8443/
# Force TLS 1.3
curl -v --tlsv1.3 https://localhost:8443/
# Different fingerprints should be generated
Capture TLS ClientHello with tshark
# Capture TLS handshake
sudo tshark -i any -f "port 8443" -Y "tls.handshake.type == 1" -V
# Look for:
# - TLS version
# - Cipher suites
# - Extensions
# - ALPN values
Viewing Fingerprints in Access Logs
JSON Log Format
# Watch access logs
tail -f moat.log | jq -r 'select(.http) | {
timestamp: .timestamp,
client_ip: .client_ip,
ja4: .tls.ja4,
ja4h: .http.ja4h,
ja4t: .tls.ja4t,
method: .http.method,
path: .http.path
}'
Example output:
{
"timestamp": "2025-10-29T12:34:56.789Z",
"client_ip": "192.168.1.100",
"ja4": "t13d1516h2_8daaf6152771_b186095e22b6",
"ja4h": "ge11cr08enus_a1b2c3d4e5f6_123456789abc_def012345678",
"ja4t": "65535_2-4-8-1-3_1460_7",
"method": "GET",
"path": "/test"
}
Extract Specific Fingerprints
# Get unique JA4 fingerprints
tail -n 1000 moat.log | jq -r '.tls.ja4' | sort | uniq -c
# Get unique JA4T fingerprints
tail -n 1000 moat.log | jq -r '.tls.ja4t' | sort | uniq -c
# Get unique JA4H fingerprints
tail -n 1000 moat.log | jq -r '.http.ja4h' | sort | uniq -c
# Correlate client IP with fingerprints
tail -n 1000 moat.log | jq -r '[.client_ip, .tls.ja4t, .http.ja4h] | @tsv'
Testing Health Endpoints
Health Check
# Check health endpoint
curl http://localhost:8080/health
# Expected response:
{
"status": "healthy",
"timestamp": "2025-10-29T12:00:00Z",
"service": "moat"
}
# HEAD request (for load balancers)
curl -I http://localhost:8080/health
TCP Fingerprint Statistics
# Get TCP fingerprint statistics
curl -s http://localhost:8080/health/tcp_fingerprint_stats | jq .
# Sample output:
{
"timestamp": "2025-10-29T12:34:56.789Z",
"syn_stats": {
"total_syns": 150,
"unique_fingerprints": 12
},
"fingerprints": [
{
"key": {
"src_ip": "192.168.1.100",
"src_port": 54321,
"fingerprint": "064:1460:65535:007"
},
"data": {
"packet_count": 25,
"ttl": 64,
"mss": 1460,
"window_size": 65535,
"window_scale": 7
}
}
]
}
Filter by IP
# IPv4 example
curl -s http://localhost:8080/health/tcp_fingerprint_stats | \
jq '.fingerprints[] | select(.key.src_ip == "192.168.1.100")'
# IPv6 example
curl -s http://localhost:8080/health/tcp_fingerprint_stats | \
jq '.fingerprints[] | select(.key.src_ip | startswith("2001:"))'
Testing with Multiple Clients
Generate Load
# Script to test with multiple clients
for i in {1..10}; do
curl -v https://localhost:8443/test?client=$i &
done
wait
# Check collected fingerprints
curl -s http://localhost:8080/health/tcp_fingerprint_stats | jq '.total_unique_fingerprints'
Test IPv4 and IPv6 Simultaneously
# Terminal 1: IPv4 requests
while true; do
curl -4 -s https://localhost:8443/ > /dev/null
sleep 1
done
# Terminal 2: IPv6 requests
while true; do
curl -6 -s https://[::1]:8443/ > /dev/null
sleep 1
done
# Terminal 3: Monitor fingerprints
watch -n 2 'curl -s http://localhost:8080/health/tcp_fingerprint_stats | jq ".fingerprints | length"'
Debugging Tools
Check BPF Maps
# List BPF maps (requires root)
sudo bpftool map list | grep -E "tcp_fingerprint|banned"
# Dump IPv4 fingerprint map
sudo bpftool map dump name tcp_fingerprints
# Dump IPv6 fingerprint map
sudo bpftool map dump name tcp_fingerprints_v6
# Check map usage
sudo bpftool map show name tcp_fingerprints
Monitor Kernel Logs
# View BPF program output
sudo cat /sys/kernel/debug/tracing/trace_pipe | grep TCP_FP
# Or use dmesg
sudo dmesg -T | grep -i "xdp\|bpf\|tcp_fp"
Check XDP Attachment
# Should show XDP program on your interface
sudo bpftool prog show | grep xdp
# Check which interface
ip link show | grep -B 1 "prog/xdp"
Performance Testing
# Load test with ab (ApacheBench)
ab -n 1000 -c 10 https://localhost:8443/
# Check fingerprint collection overhead
time curl -s http://localhost:8080/health/tcp_fingerprint_stats > /dev/null
# Monitor CPU usage during collection
pidstat -p $(pgrep moat) 1 10
Quick Test Script
#!/bin/bash
# quick_test.sh - Comprehensive test script
echo "=== Testing TCP Fingerprinting (JA4T) ==="
echo "IPv4 test..."
curl -4 -s https://localhost:8443/ > /dev/null && echo "✓ IPv4 request sent"
echo "IPv6 test..."
curl -6 -s https://[::1]:8443/ > /dev/null && echo "✓ IPv6 request sent"
echo ""
echo "=== Testing HTTP Fingerprinting (JA4H) ==="
curl -s -H "Cookie: test=value" -H "Referer: https://test.com" https://localhost:8443/ > /dev/null
echo "✓ Request with headers sent"
echo ""
echo "=== Checking Statistics ==="
STATS=$(curl -s http://localhost:8080/health/tcp_fingerprint_stats)
COUNT=$(echo $STATS | jq -r '.fingerprints | length')
echo "✓ Total unique fingerprints: $COUNT"
echo ""
echo "=== Recent Fingerprints ==="
echo $STATS | jq -r '.fingerprints[0:3]'
echo ""
echo "=== Access Log Sample ==="
tail -5 moat.log | jq -r 'select(.http) | {ja4t: .tls.ja4t, ja4h: .http.ja4h, client: .client_ip}'
Make executable and run:
chmod +x quick_test.sh
./quick_test.sh
Validation Checklist
After testing, verify:
- JA4T fingerprints appear in access logs
- JA4H fingerprints change with different headers
- JA4 fingerprints change with different TLS settings
- IPv4 and IPv6 both generate fingerprints
- BPF maps contain entries
- No errors in moat logs
- Statistics endpoint returns data
- Health endpoint responds correctly
- Different clients generate different fingerprints
- Same client generates consistent fingerprints
Common Issues
No fingerprints appearing
# Check XDP is enabled
./target/release/moat --config config.yaml 2>&1 | grep -i xdp
# Verify interface is correct
ip link show
# Check BPF program is loaded
sudo bpftool prog list | grep xdp
IPv6 not working
# Enable IPv6
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=0
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=0
# Test connectivity
ping6 -c 3 ::1
Empty statistics
# Lower threshold for testing
# Edit config.yaml:
tcp_fingerprinting:
min_packet_count: 1
# Restart moat
Next Steps
- JA4+ Fingerprinting - Learn about fingerprint analysis
- Configuration Reference - Tune your settings