Skip to main content

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