A comprehensive RBL (Real-time Blackhole List) lookup tool with web interface, DNS server, and command-line capabilities.
- Features
- Project Structure
- Installation
- Custom HTML Header and Footer
- Usage
- Custom RBL Management
- API Endpoints
- Rate Limiting
- Request Logging
- RBL Servers Included
- How the DNS Server Works
- Testing Tools
- Configuration
- Development
- Running as a Daemon
- Scripts
- License
- DNS Server: RFC-compliant DNS server with intelligent two-tier caching
- Custom RBL: Self-managed blocklist with CIDR range support (IPv4/IPv6)
- Multi-RBL Lookup: Query all RBLs at once via DNS with 250ms timeout
- Multi-Zone Support: Configure multiple DNS zones with different RBL sets for targeted checks
- Web Interface: Modern, responsive web UI with real-time updates
- CLI Tool: PHP command-line tool with formatted table output and custom RBL management
- Two-Tier Caching: Optional memcache (L1 ~0.1ms) + PostgreSQL (L2 ~1-5ms)
- API Key Authentication: Secure admin API for custom RBL management
- Concurrent Queries: Check 40+ RBL servers simultaneously
- Fast Performance: Sub-millisecond cache hits, concurrent DNS lookups, efficient CIDR matching
- Color-Coded Results: Easy-to-read status indicators
- Filterable Results: View all, listed only, clean only, or errors only
multirbl-lookup/
├── src/
│ ├── rbl-lookup.js # Core RBL lookup logic
│ ├── rbl-lookup-cached.js # Cached RBL lookups with TTL
│ ├── cache-db.js # Two-tier cache manager (memcache + PostgreSQL)
│ ├── memcache.js # Memcache client wrapper (L1 cache)
│ ├── db-postgres.js # PostgreSQL connection pool
│ ├── custom-rbl-lookup.js # Custom RBL CIDR matching
│ ├── auth-middleware.js # API key authentication
│ ├── ip-network-utils.js # IP/CIDR validation and matching utilities
│ ├── dns-server.js # DNS server implementation
│ ├── start-dns-server.js # DNS server CLI
│ ├── server.js # Express API server
│ └── logger.js # Request logging utility
├── database/
│ ├── schema.sql # PostgreSQL schema
│ └── migrate.js # Database migration script
├── docs/
│ └── CUSTOM-RBL.md # Custom RBL documentation
├── public_html/
│ ├── index.html # Web interface
│ ├── styles.css # Styling
│ └── app.js # Frontend JavaScript
├── etc/
│ ├── rbl-servers.json.example # 40+ RBL server configurations (example)
│ ├── multi-rbl-zones.json.example # Multi-RBL zone configurations (example)
│ └── dns-access-control.json.example # DNS access control (example)
├── logs/
│ └── requests.log # Request logs (auto-created)
├── rbl-cli.php # PHP CLI with custom RBL commands
├── .env.example # Environment configuration template
└── package.json # Node.js dependencies
- Node.js (v14 or higher)
- PostgreSQL (v12 or higher)
- PHP (v7.4 or higher) with curl extension (for CLI tool)
- Install PostgreSQL (if not already installed):
Ubuntu/Debian:
sudo apt-get update
sudo apt-get install postgresql postgresql-contribmacOS:
brew install postgresql
brew services start postgresqlWindows: Download from postgresql.org
- Create PostgreSQL database and user:
# Connect to PostgreSQL
sudo -u postgres psql
# Run these commands in psql:
CREATE DATABASE multirbl;
CREATE USER multirbl WITH ENCRYPTED PASSWORD 'changeme';
GRANT ALL PRIVILEGES ON DATABASE multirbl TO multirbl;
# Exit psql
\q- Install Node.js dependencies:
npm install- Copy configuration files:
# Copy RBL servers configuration
cp etc/rbl-servers.json.example etc/rbl-servers.json
# Copy multi-RBL zones configuration (optional)
cp etc/multi-rbl-zones.json.example etc/multi-rbl-zones.json
# Copy DNS access control configuration (optional)
cp etc/dns-access-control.json.example etc/dns-access-control.jsonNote: These configuration files are excluded from git so you can customize them for your environment.
- Configure environment variables:
# Copy the example environment file
cp .env.example .env
# Edit .env with your database credentials
nano .envUpdate the following in .env:
DB_HOST=localhost
DB_PORT=5432
DB_NAME=multirbl
DB_USER=multirbl
DB_PASSWORD=changeme- Run database migration:
node database/migrate.jsYou should see:
✓ Connected successfully
✓ Schema created successfully
✓ Migration completed successfully
- Start the API server:
npm startThe server will start on http://localhost:3000
If you have an existing installation using SQLite (data/rbl-cache.db), follow these steps to migrate:
- Backup your existing SQLite database (optional, for reference):
cp data/rbl-cache.db data/rbl-cache.db.backup-
Install PostgreSQL (see Setup section above)
-
Update dependencies:
npm install pg bcrypt dotenv-
Configure PostgreSQL (see Setup steps 2-5 above)
-
Run the migration script:
node database/migrate.js- Test the server:
npm start- Verify caching is working:
# Make a test request
curl -X POST http://localhost:3000/api/lookup \
-H "Content-Type: application/json" \
-d '{"ip": "8.8.8.8"}'
# Check cache stats
curl http://localhost:3000/api/cache/statsDatabase:
- SQLite → PostgreSQL
- File-based storage → Client-server database
- TEXT columns → Native INET/CIDR types
- Simple indexes → GiST indexes for IP matching
Performance:
- Similar or better cache performance
- Better concurrent query handling
- Native IPv6 support
- Efficient CIDR range matching
New Features:
- Custom RBL with CIDR support
- API key authentication
- Admin management endpoints
- IPv6 ready
- The old SQLite database file (
data/rbl-cache.db) is no longer used and can be deleted - All cache data starts fresh in PostgreSQL (no automatic data migration)
- API endpoints remain compatible (no client changes needed)
- DNS server functionality unchanged
For high-traffic deployments, you can add memcache as an L1 cache layer for even faster lookups.
The Multi-RBL Lookup tool implements a two-tier caching architecture:
- L1 Cache (Memcache): Sub-millisecond lookups (~0.1ms) for hot data
- L2 Cache (PostgreSQL): Persistent storage with reasonable performance (~1-5ms)
Without memcache, all cache lookups go directly to PostgreSQL. With memcache enabled, frequently accessed results are served from RAM, significantly reducing database load and improving response times.
Ubuntu/Debian:
sudo apt-get update
sudo apt-get install memcached
sudo systemctl enable memcached
sudo systemctl start memcachedmacOS:
brew install memcached
brew services start memcachedWindows: Download from memcached.org or use Docker:
docker run -d -p 11211:11211 --name memcached memcached- Enable memcache in
.env:
MEMCACHE_ENABLED=true
MEMCACHE_SERVERS=localhost:11211
MEMCACHE_DEBUG=false- Multiple servers (optional):
For high availability, you can use multiple memcache servers:
MEMCACHE_SERVERS=cache1.example.com:11211,cache2.example.com:11211,cache3.example.com:11211- Restart the services:
# If using PM2
pm2 restart multirbl-web
pm2 restart multirbl-dns
# If using systemd
sudo systemctl restart multirbl-web
sudo systemctl restart multirbl-dns
# If running directly
npm startThe two-tier caching system works as follows:
-
Cache Read (getCached):
- Check memcache first (L1) - ~0.1ms
- If found, return result immediately
- If not found, check PostgreSQL (L2) - ~1-5ms
- If found in PostgreSQL, backfill memcache for future requests
- Return result
-
Cache Write (cache):
- Write to memcache (L1) - fire and forget, non-blocking
- Write to PostgreSQL (L2) - persistent storage
-
Cache Invalidation:
- Memcache entries expire based on TTL
- PostgreSQL serves as authoritative source
- Cache clear operations flush both layers
With memcache enabled, you can expect:
- Cached lookups: ~0.1ms (99.99% faster than DNS)
- Database load: Reduced by 80-95% for hot data
- Throughput: 10-100x improvement for repeated queries
- Scalability: Better handling of traffic spikes
Check if memcache is enabled:
# View startup logs
pm2 logs multirbl-web | grep Memcache
# Expected output when enabled:
# Memcache enabled: localhost:11211
# Expected output when disabled:
# Memcache disabled (set MEMCACHE_ENABLED=true to enable)View cache statistics:
curl http://localhost:3000/api/cache/statsResponse will show cache hits from both layers:
{
"success": true,
"stats": {
"total": 1500,
"valid": 1450,
"expired": 50
}
}Memcache not connecting:
# Check if memcached is running
sudo systemctl status memcached # Linux
brew services list # macOS
docker ps | grep memcached # Docker
# Test connection manually
telnet localhost 11211
stats
quitVerify memcache is being used:
Set MEMCACHE_DEBUG=true in .env to see memcache operations in the logs:
pm2 logs multirbl-webYou should see messages like:
[Memcache] Connected to localhost:11211
[Memcache] GET rbl:8.8.8.8:zen.spamhaus.org - HIT
[Memcache] SET rbl:1.1.1.1:zen.spamhaus.org - OK
Clearing memcache:
# Via API (clears both memcache and PostgreSQL)
curl -X POST http://localhost:3000/api/cache/clear
# Manually flush memcache
echo 'flush_all' | nc localhost 11211- Memcache is optional - the system works fine without it
- Memcache does not persist data across restarts (by design)
- PostgreSQL always serves as the persistent L2 cache
- Both DNS server and web interface use the same memcache instance
- All cache operations gracefully degrade if memcache is unavailable
The server is configured using environment variables in .env file (copy from .env.example):
DB_HOST=localhost # PostgreSQL host
DB_PORT=5432 # PostgreSQL port
DB_NAME=multirbl # Database name
DB_USER=multirbl # Database user
DB_PASSWORD=changeme # Database password
DB_POOL_MAX=20 # Max connections in pool
DB_IDLE_TIMEOUT=30000 # Idle timeout (ms)
DB_CONNECT_TIMEOUT=2000 # Connection timeout (ms)MEMCACHE_ENABLED=false # Enable memcache (default: false)
MEMCACHE_SERVERS=localhost:11211 # Memcache server(s)
MEMCACHE_DEBUG=false # Enable debug logging (default: false)PORT=3000 # Server port (default: 3000)
RATE_LIMIT_MAX=15 # Max requests per window (default: 15)
RATE_LIMIT_WINDOW_HOURS=1 # Rate limit window in hours (default: 1)
HEADER_HTML_FILE=./public_html/header.html # Custom header HTML
FOOTER_HTML_FILE=./public_html/footer.html # Custom footer HTMLDNS_SERVER_PORT=8053 # DNS server port
DNS_SERVER_HOST=0.0.0.0 # DNS bind address
DNS_UPSTREAM=8.8.8.8 # Upstream DNS server
DNS_MULTI_RBL_DOMAIN=multi-rbl.example.com # Multi-RBL domainCUSTOM_RBL_ZONE=myrbl.example.com # Custom RBL zone name (informational)You can also set environment variables directly:
PORT=8080 npm startMultiple environment variables:
# Linux/macOS
PORT=8080 RATE_LIMIT_MAX=20 RATE_LIMIT_WINDOW_HOURS=2 npm start
# Windows (PowerShell)
$env:PORT=8080; $env:RATE_LIMIT_MAX=20; $env:RATE_LIMIT_WINDOW_HOURS=2; npm startOr set them permanently in your environment:
# Linux/macOS
export PORT=8080
export RATE_LIMIT_MAX=20
export RATE_LIMIT_WINDOW_HOURS=2
npm start
# Windows (Command Prompt)
set PORT=8080
set RATE_LIMIT_MAX=20
set RATE_LIMIT_WINDOW_HOURS=2
npm start
# Windows (PowerShell)
$env:PORT=8080
$env:RATE_LIMIT_MAX=20
$env:RATE_LIMIT_WINDOW_HOURS=2
npm startYou can inject custom HTML into the web interface to add branding, analytics, banners, or custom styling.
- Header HTML is inserted immediately after the
</head>tag - Footer HTML is inserted immediately before the
</body>tag - Changes are cached for 1 minute for performance
- Files are optional - if they don't exist, nothing is injected
- Copy the example files:
cp public_html/header.html.example public_html/header.html
cp public_html/footer.html.example public_html/footer.html- Edit the files with your custom HTML:
# Edit header.html
nano public_html/header.html
# Edit footer.html
nano public_html/footer.html- Restart the server (or wait up to 1 minute for cache refresh)
Custom banner (header.html):
<div style="background: #f0f0f0; padding: 10px; text-align: center;">
<strong>Notice:</strong> This is a demonstration server
</div>Analytics tracking (footer.html):
<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
</script>Custom footer (footer.html):
<div style="text-align: center; padding: 20px; color: #666;">
<p>© 2025 Your Organization | <a href="/privacy">Privacy Policy</a></p>
</div>Custom CSS (header.html):
<style>
body {
font-family: 'Your Custom Font', sans-serif;
}
.container {
max-width: 1400px;
}
</style>You can specify custom file paths using environment variables:
HEADER_HTML_FILE=/etc/multirbl/custom-header.html npm start
FOOTER_HTML_FILE=/etc/multirbl/custom-footer.html npm start- Header and footer files are in
.gitignore(your customizations won't be committed) - Example files (
.exampleextension) are tracked in git for reference - HTML is injected server-side on every request
- No restart needed - changes take effect within 1 minute (cache TTL)
- Both files are optional - the server works fine if they don't exist
Start the DNS server with caching (supports both UDP and TCP):
npm run dns-serverOptions:
node src/start-dns-server.js [options]
Options:
--port=<port> DNS server port (default: 8053)
--host=<host> Bind address (default: 0.0.0.0)
--upstream=<dns> Upstream DNS for non-RBL queries (default: 8.8.8.8)
--multi-rbl-domain=<dom> Domain for multi-RBL lookups (default: multi-rbl.example.com)
--stats Show cache statistics
--clear-cache Clear all cached entriesTesting Single RBL Lookups:
Using dig (UDP):
dig @localhost -p 8053 2.0.0.127.zen.spamhaus.orgUsing dig (TCP):
dig @localhost -p 8053 +tcp 2.0.0.127.zen.spamhaus.orgUsing nslookup:
nslookup 2.0.0.127.zen.spamhaus.org localhost -port=8053DNS Server Multi-RBL Lookups :
The DNS server supports querying an IP against all configured RBLs at once. This feature:
- Checks the IP against all 40+ RBL servers concurrently
- Returns results within 250ms (hard timeout)
- Provides aggregate results via DNS A and TXT records
- Uses the same caching system for instant responses
Query an IP across all RBLs:
# A record - returns 127.0.0.2 if listed on any RBL
dig @localhost -p 8053 2.0.0.127.multi-rbl.example.com
# TXT records - shows detailed results
dig @localhost -p 8053 2.0.0.127.multi-rbl.example.com TXT
# Example TXT output:
# "Listed on 3/45 RBLs (45/50 checked in 180ms)"
# "Spamhaus ZEN: LISTED"
# "Barracuda: LISTED"
# "SpamCop: LISTED"Using nslookup:
nslookup 2.0.0.127.multi-rbl.example.com localhost -port=8053Custom multi-RBL domain:
node src/start-dns-server.js --multi-rbl-domain=check.example.org
dig @localhost -p 8053 8.8.8.8.check.example.org TXTConfiguring Multiple Multi-RBL Zones:
The DNS server supports multiple custom multi-RBL zones, each checking different sets of RBLs. This is configured via etc/multi-rbl-zones.json:
{
"zones": [
{
"domain": "multi-rbl.example.com",
"description": "All RBLs (comprehensive check)",
"rbls": "*"
},
{
"domain": "major-rbls.example.com",
"description": "Major/most reliable RBLs only",
"rbls": [
"zen.spamhaus.org",
"sbl.spamhaus.org",
"xbl.spamhaus.org",
"pbl.spamhaus.org",
"cbl.abuseat.org",
"bl.spamcop.net",
"psbl.surriel.com",
"dnsbl.sorbs.net",
"b.barracudacentral.org"
]
},
{
"domain": "spamhaus.example.com",
"description": "Spamhaus lists only",
"rbls": [
"zen.spamhaus.org",
"sbl.spamhaus.org",
"xbl.spamhaus.org",
"pbl.spamhaus.org"
]
}
]
}Each zone can:
- Use
"rbls": "*"to check against all RBL servers - Specify an array of specific RBL hosts to check
- Have its own domain name for querying
Query different zones:
# Check against all RBLs (slower, comprehensive)
dig @localhost -p 8053 2.0.0.127.multi-rbl.example.com TXT
# Check against major RBLs only (faster)
dig @localhost -p 8053 2.0.0.127.major-rbls.example.com TXT
# Check Spamhaus only (fastest, most authoritative)
dig @localhost -p 8053 2.0.0.127.spamhaus.example.com TXTNotes:
- If
etc/multi-rbl-zones.jsondoesn't exist, the server falls back to the single domain specified byDNS_MULTI_RBL_DOMAINin.env - Zone configurations are loaded at server startup
- Each zone's queries are cached independently
- Using targeted zones can significantly improve response times for specific use cases
Cache Management:
View statistics:
npm run dns-statsClear cache:
npm run dns-clear-cacheFor production environments, you'll want to run the DNS server as a background daemon that starts automatically on system boot.
PM2 is a production-grade process manager for Node.js applications that works on Linux, macOS, and Windows.
Install PM2 globally:
npm install -g pm2Start the DNS server:
pm2 start src/start-dns-server.js --name multirbl-dnsWith custom options:
pm2 start src/start-dns-server.js --name multirbl-dns -- --port=53 --host=0.0.0.0Useful PM2 commands:
pm2 list # View all running processes
pm2 logs multirbl-dns # View logs for DNS server
pm2 restart multirbl-dns # Restart DNS server
pm2 stop multirbl-dns # Stop DNS server
pm2 delete multirbl-dns # Remove from PM2
pm2 monit # Monitor CPU and memory usageAuto-start on system boot:
pm2 startup # Follow the displayed instructions
pm2 save # Save current process listCreate a systemd service file for the DNS server:
1. Create service file:
sudo nano /etc/systemd/system/multirbl-dns.service2. Add the following content:
[Unit]
Description=Multi-RBL Lookup DNS Server
After=network.target
[Service]
Type=simple
User=your-username
WorkingDirectory=/path/to/multirbl-lookup
ExecStart=/usr/bin/node src/start-dns-server.js --port=8053 --host=0.0.0.0
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.targetNote: To bind to port 53 (privileged port), either run as root (not recommended) or use setcap:
sudo setcap 'cap_net_bind_service=+ep' $(which node)3. Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable multirbl-dns
sudo systemctl start multirbl-dns4. Manage the service:
sudo systemctl status multirbl-dns # Check status
sudo systemctl restart multirbl-dns # Restart
sudo systemctl stop multirbl-dns # Stop
sudo journalctl -u multirbl-dns -f # View logsInstall node-windows to run as a Windows service:
1. Install node-windows:
npm install -g node-windows2. Create install script (install-dns-service.js):
const Service = require('node-windows').Service;
const svc = new Service({
name: 'Multi-RBL Lookup DNS',
description: 'Multi-RBL Lookup DNS Server',
script: require('path').join(__dirname, 'src', 'start-dns-server.js'),
scriptOptions: '--port=8053 --host=0.0.0.0',
nodeOptions: ['--max_old_space_size=4096']
});
svc.on('install', function() {
svc.start();
console.log('DNS service installed and started');
});
svc.install();3. Run the install script as Administrator:
node install-dns-service.js4. Manage via Windows Services:
- Open
services.msc - Find "Multi-RBL Lookup DNS"
- Start/Stop/Restart as needed
Create a Dockerfile for the DNS server:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 8053/udp
CMD ["node", "src/start-dns-server.js", "--port=8053", "--host=0.0.0.0"]Build and run:
docker build -t multirbl-dns -f Dockerfile.dns .
docker run -d -p 8053:8053/udp --name multirbl-dns multirbl-dnsFor port 53 (standard DNS):
docker run -d -p 53:53/udp --name multirbl-dns multirbl-dns node src/start-dns-server.js --port=53Using docker-compose (docker-compose.yml):
version: '3.8'
services:
multirbl-dns:
build:
context: .
dockerfile: Dockerfile.dns
ports:
- "8053:8053/udp"
volumes:
- ./data:/app/data
- ./etc:/app/etc
restart: unless-stopped
command: ["node", "src/start-dns-server.js", "--port=8053", "--host=0.0.0.0"]Start with:
docker-compose up -dFor simple deployments, you can use cron to ensure the DNS server stays running.
1. Create a startup/monitor script (scripts/ensure-dns-running.sh):
#!/bin/bash
# Configuration
PROJECT_DIR="/path/to/multirbl-lookup"
PID_FILE="$PROJECT_DIR/dns-server.pid"
LOG_FILE="$PROJECT_DIR/logs/dns-server.log"
NODE_BIN="/usr/bin/node"
DNS_PORT="8053"
DNS_HOST="0.0.0.0"
cd "$PROJECT_DIR"
# Create logs directory if it doesn't exist
mkdir -p logs
# Function to check if DNS server is running
is_running() {
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p "$PID" > /dev/null 2>&1; then
return 0
fi
fi
return 1
}
# Function to start DNS server
start_server() {
echo "[$(date)] Starting Multi-RBL DNS server on port $DNS_PORT..." >> "$LOG_FILE"
nohup "$NODE_BIN" src/start-dns-server.js --port="$DNS_PORT" --host="$DNS_HOST" >> "$LOG_FILE" 2>&1 &
echo $! > "$PID_FILE"
echo "[$(date)] DNS server started with PID $(cat $PID_FILE)" >> "$LOG_FILE"
}
# Check if server is running, start if not
if is_running; then
echo "[$(date)] DNS server is running with PID $(cat $PID_FILE)" >> "$LOG_FILE"
else
echo "[$(date)] DNS server is not running, starting..." >> "$LOG_FILE"
start_server
fi2. Make the script executable:
chmod +x scripts/ensure-dns-running.sh3. Add to crontab:
crontab -eAdd one of these lines:
Check every minute:
* * * * * /path/to/multirbl-lookup/scripts/ensure-dns-running.shCheck every 5 minutes:
*/5 * * * * /path/to/multirbl-lookup/scripts/ensure-dns-running.shStart at system reboot:
@reboot /path/to/multirbl-lookup/scripts/ensure-dns-running.shCombined (check every 5 minutes + start at reboot):
@reboot /path/to/multirbl-lookup/scripts/ensure-dns-running.sh
*/5 * * * * /path/to/multirbl-lookup/scripts/ensure-dns-running.sh4. Create manual control scripts:
Stop script (scripts/stop-dns-server.sh):
#!/bin/bash
PROJECT_DIR="/path/to/multirbl-lookup"
PID_FILE="$PROJECT_DIR/dns-server.pid"
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p "$PID" > /dev/null 2>&1; then
kill "$PID"
echo "DNS server stopped (PID $PID)"
rm "$PID_FILE"
else
echo "PID file exists but process not running"
rm "$PID_FILE"
fi
else
echo "DNS server is not running (no PID file)"
fiStatus script (scripts/status-dns-server.sh):
#!/bin/bash
PROJECT_DIR="/path/to/multirbl-lookup"
PID_FILE="$PROJECT_DIR/dns-server.pid"
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p "$PID" > /dev/null 2>&1; then
echo "DNS server is running (PID $PID)"
ps -p "$PID" -o pid,ppid,cmd,%mem,%cpu,etime
else
echo "PID file exists but process not running"
fi
else
echo "DNS server is not running (no PID file)"
fiMake them executable:
chmod +x scripts/stop-dns-server.sh scripts/status-dns-server.sh5. View logs:
tail -f logs/dns-server.logNote: This cron-based approach is simpler but less robust than PM2 or systemd. The server won't restart immediately on crashes (only when cron runs), and there's no built-in log rotation or advanced process management.
The web interface uses the same caching system as the DNS server for improved performance.
- Open your browser to http://localhost:3000
- Enter an IPv4 address (e.g., 8.8.8.8)
- Click "Lookup" to check the IP against all RBL servers
- View results in a color-coded table with filtering options
- Results show cache hit/miss statistics
New Cache API Endpoints:
GET /api/cache/stats- View cache statisticsPOST /api/cache/clear- Clear all cache or specific IPPOST /api/cache/clean- Clean expired entries
The PHP CLI tool queries the API server and displays results in a formatted text table.
Linux/macOS/Unix:
php rbl-cli.php <ip-address>Windows:
rbl-cli.cmd <ip-address>Or use PHP directly on any platform:
php rbl-cli.php <ip-address>Example:
php rbl-cli.php 8.8.8.8
# Windows: rbl-cli.cmd 8.8.8.8--host=<host> API server host (default: localhost)
--port=<port> API server port (default: 3000)
--filter=<type> Filter results: all, listed, clean, error (default: all)
--tls Use HTTPS instead of HTTP
--no-verify-ssl Disable SSL certificate verification (for self-signed certs)
--no-color Disable colored output
--json Output raw JSON instead of table
--help, -h Show help message
Settings can be stored in ~/.rbl-cli.rc (INI format). Command-line options override config file settings.
Create config file:
cat > ~/.rbl-cli.rc << 'EOF'
; RBL CLI Configuration
host = localhost
port = 3000
filter = all
no-color = false
json = false
EOFExample config file (.rbl-cli.rc.example):
; RBL CLI Configuration File
; Command-line options will override these settings
; API server host (default: localhost)
host = localhost
; API server port (default: 3000)
port = 3000
; Filter results: all, listed, clean, error (default: all)
filter = all
; Use HTTPS instead of HTTP (default: false)
tls = false
; Verify SSL certificates (default: true)
; Set to false for self-signed certificates (INSECURE - testing only!)
verify-ssl = true
; Disable colored output (default: false)
no-color = false
; Output JSON instead of table (default: false)
json = falseSSL Certificate Issues (Windows):
If you encounter SSL certificate errors on Windows, you have two options:
-
Recommended: Disable SSL verification for self-signed certificates:
php rbl-cli.php 8.8.8.8 --tls --no-verify-ssl
Or in config file:
verify-ssl = false -
Production: Use proper SSL certificates from a trusted CA
Note: --no-verify-ssl should only be used for testing with self-signed certificates. Never use this in production!
Show only blacklisted results:
php rbl-cli.php 8.8.8.8 --filter=listedQuery a remote API server:
php rbl-cli.php 192.168.1.1 --host=example.com --port=8080Get raw JSON output:
php rbl-cli.php 8.8.8.8 --jsonDisable colors (useful for piping or logging):
php rbl-cli.php 8.8.8.8 --no-color > results.txtThe Multi-RBL Lookup tool includes support for self-managed custom RBL lists with CIDR range support for both IPv4 and IPv6.
- Generate an API key:
First, you need an API key for authentication. There are three ways to generate one:
Option A: Bootstrap Script (Recommended for first-time setup):
node database/bootstrap-apikey.js "Initial admin key"This will:
- Generate a cryptographically secure random API key
- Store it in the database with bcrypt hashing
- Display the key (save it immediately - you won't see it again!)
Save the key to ~/.rbl-cli.rc:
api-key = YOUR_GENERATED_KEY_HEREOption B: Manual database insert (alternative):
psql -U multirbl -d multirbl -c "
INSERT INTO api_keys (key_hash, key_prefix, description)
SELECT crypt('your-secure-key-here', gen_salt('bf')),
'your-sec',
'Initial admin key';"Option C: Generate via CLI (requires existing API key):
php rbl-cli.php custom apikey generate --desc="Production key"- Add entries to your custom RBL:
# Add a single IP (stored as /32)
php rbl-cli.php custom add 192.168.1.100/32 "Known spammer"
# Add an entire subnet
php rbl-cli.php custom add 10.0.0.0/24 "Spam network"
# Add IPv6 range
php rbl-cli.php custom add 2001:db8::/32 "Blocked IPv6 range"- List entries:
php rbl-cli.php custom list- Remove entries:
php rbl-cli.php custom remove 192.168.1.100/32- View/update configuration:
# View current config
php rbl-cli.php custom config
# Update zone name
php rbl-cli.php custom config --zone=blocklist.example.comOnce you have entries in your custom RBL, test via DNS server:
# Start DNS server
node src/start-dns-server.js
# Test query (IP 192.168.1.100 becomes 100.1.168.192 reversed)
dig @localhost -p 8053 100.1.168.192.myrbl.example.com
# Get reason via TXT record
dig @localhost -p 8053 100.1.168.192.myrbl.example.com TXTAll admin endpoints require X-API-Key header:
# List entries
curl -H "X-API-Key: YOUR_KEY" http://localhost:3000/api/admin/custom-rbl/entries
# Add entry
curl -X POST http://localhost:3000/api/admin/custom-rbl/entries \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_KEY" \
-d '{"network": "203.0.113.0/24", "reason": "Spam source"}'
# Check IP (public endpoint, no auth required)
curl -X POST http://localhost:3000/api/custom-rbl/check \
-H "Content-Type: application/json" \
-d '{"ip": "203.0.113.50"}'| Command | Description | Example |
|---|---|---|
custom add <cidr> [reason] |
Add IP/CIDR to blocklist | custom add 10.0.0.1/32 "Spammer" |
custom remove <cidr> |
Remove entry | custom remove 10.0.0.1/32 |
custom list [--limit=N] |
List all entries | custom list --limit=50 |
custom config [--zone=name] |
View/update config | custom config --zone=my.rbl.com |
custom apikey generate |
Generate API key | custom apikey generate --desc="Key" |
- CIDR Matching: Efficient subnet matching using PostgreSQL's native INET/CIDR types
- IPv4 and IPv6: Full support for both IP versions
- GiST Indexes: Fast lookups even with millions of entries
- API Key Authentication: Secure access control via X-API-Key header
- DNS Integration: Custom RBL queries work seamlessly with DNS server
- CLI Management: Full-featured command-line interface
- Reason Tracking: Store why an IP/range is blocked
For complete documentation including architecture, API reference, and examples, see:
- docs/CUSTOM-RBL.md - Complete Custom RBL guide
Query an IP address against all RBL servers (with caching).
Request:
{
"ip": "8.8.8.8"
}Response:
{
"success": true,
"data": {
"ip": "8.8.8.8",
"timestamp": "2025-01-01T12:00:00.000Z",
"totalChecked": 40,
"listedCount": 0,
"notListedCount": 38,
"errorCount": 2,
"cacheHits": 35,
"cacheMisses": 5,
"cacheHitRate": "87.5%",
"results": [
{
"name": "Spamhaus ZEN",
"host": "zen.spamhaus.org",
"description": "Combined list including SBL, XBL, and PBL",
"listed": false,
"response": null,
"responseTime": 0,
"error": null,
"ttl": 3600,
"fromCache": true
}
]
}
}Get cache statistics.
Response:
{
"success": true,
"stats": {
"total": 150,
"valid": 145,
"expired": 5,
"listed": 12,
"notListed": 130,
"errors": 3
}
}Clear cache entries.
Request (clear all):
{}Request (clear specific IP):
{
"ip": "8.8.8.8"
}Response:
{
"success": true,
"message": "Cleared 40 entries",
"deleted": 40
}Clean expired cache entries.
Response:
{
"success": true,
"message": "Cleaned 5 expired entries",
"deleted": 5
}Get the list of configured RBL servers.
Health check endpoint.
To prevent server overload and abuse, the web server implements rate limiting on RBL lookup endpoints (/api/lookup and /api/lookup-stream).
- 15 requests per hour per client IP address
- Configurable via environment variables (see Configuration section)
- Rate limiting is applied per client IP address
- Works correctly behind proxies and load balancers (uses
X-Forwarded-Forheader) - When limit is exceeded, HTTP 429 (Too Many Requests) is returned
- Response includes
RateLimit-*headers showing current usage - Rate limit violations are logged automatically
The server returns these headers with each lookup request:
RateLimit-Limit: 15 # Maximum requests allowed
RateLimit-Remaining: 12 # Requests remaining in window
RateLimit-Reset: 1698765432 # Unix timestamp when limit resets
When the rate limit is exceeded, the API returns:
{
"success": false,
"error": "Too many lookup requests. Maximum 15 requests per 1 hour(s). Please try again later.",
"retryAfter": 3600
}See the Configuration section for environment variables to adjust rate limits:
RATE_LIMIT_MAX- Maximum requests (default: 15)RATE_LIMIT_WINDOW_HOURS- Time window in hours (default: 1)
All RBL lookup requests made through the web server are automatically logged with the following information:
- Timestamp: ISO 8601 format
- Client IP Address: Automatically detects the real client IP, even behind proxies
- Target IP: The IP address being checked against RBLs
- User Agent: Browser or client information
Logs are stored in: logs/requests.log
[2025-10-28T02:26:56.814Z] [INFO] RBL lookup request {"clientIp":"203.0.113.45","targetIp":"8.8.8.8","userAgent":"curl/8.14.1"}
The logging system automatically detects the real client IP address when the server is behind a proxy or load balancer by checking these headers in order:
X-Forwarded-For(takes the first IP in the chain)X-Real-IP- Direct connection IP (fallback)
- Log files automatically rotate when they reach 10MB
- Rotated files are timestamped:
requests-YYYY-MM-DDTHH-MM-SS.log - Old logs are preserved and can be archived or deleted manually
View recent requests:
tail -f logs/requests.logView last 100 requests:
tail -n 100 logs/requests.logSearch for specific IP:
grep "203.0.113.45" logs/requests.logThe tool checks against 40+ RBL servers including:
- Spamhaus: ZEN, SBL, XBL, PBL, DBL
- SpamCop: Blocking List
- SORBS: Multiple lists (DNSBL, SPAM, WEB, SMTP, SOCKS, etc.)
- Barracuda: Reputation Block List
- UCEPROTECT: Levels 1, 2, 3
- PSBL: Passive Spam Block List
- CBL: Composite Blocking List
- DroneBL: IRC spam drones
- SpamRats: Dynamic, NoPtr, Spam
- And many more...
┌─────────────────┐
│ DNS Client │
└────────┬────────┘
│
▼
┌─────────────────┐
│ DNS Server │ (Port 8053)
│ (native-dns) │
└────────┬────────┘
│
├─────────► Custom RBL Query?
│ └─ Yes: PostgreSQL CIDR lookup → Response
│
├─────────► Multi-RBL Query?
│ ├─ Yes: Check cache for ALL RBLs (two-tier)
│ ├─ Cache hits return instantly
│ ├─ Cache misses: Query concurrently (250ms timeout)
│ └─ Cache new results → Aggregate response
│
├─────────► Two-Tier Cache Check
│ ├─ L1 (Memcache): Hit? Return result (~0.1ms)
│ ├─ L2 (PostgreSQL): Hit? Return result + backfill L1 (~1-5ms)
│ └─ Miss: Continue to lookup
│
├─────────► RBL Lookup (DNS query)
│
├─────────► Cache Result (L1 + L2 with TTL)
│
└─────────► DNS Response (A + TXT records)
When a query matches any configured multi-RBL zone domain (e.g., 2.0.0.127.multi-rbl.example.com or 2.0.0.127.major-rbls.example.com):
- Match Zone: Identify which multi-RBL zone the query is for
- Parse IP: Extract IP address from query (e.g.,
127.0.0.2) - Filter RBLs: Select RBL servers based on zone configuration:
"rbls": "*"= all 40+ RBL servers"rbls": [...]= specific RBL servers listed in the zone
- Concurrent Lookups: Query selected RBL servers simultaneously using cached lookups
- 250ms Timeout: Hard timeout ensures response within 250ms
- Collect Results: Gather all completed responses (cache hits return instantly)
- Build Response:
- A Record: Returns
127.0.0.2if listed on any RBL, or NXDOMAIN if clean - TXT Records:
- Summary:
"Listed on 3/9 RBLs (9/9 checked in 180ms)"(example for major-rbls zone) - Details:
"Spamhaus ZEN: LISTED","Barracuda: LISTED", etc.
- Summary:
- A Record: Returns
- Cache Everything: All individual RBL results are cached for future queries
Multiple Zone Example:
multi-rbl.example.comchecks all 40+ RBLs (comprehensive but slower)major-rbls.example.comchecks only 9 major RBLs (faster, most important lists)spamhaus.example.comchecks only 4 Spamhaus lists (fastest, single provider)
CREATE TABLE rbl_cache (
ip INET NOT NULL, -- Native PostgreSQL INET type
rbl_host VARCHAR(255) NOT NULL,
listed BOOLEAN NOT NULL, -- Native boolean
response INET, -- Native INET type
error TEXT,
ttl INTEGER NOT NULL,
cached_at BIGINT NOT NULL,
expires_at BIGINT NOT NULL,
UNIQUE(ip, rbl_host)
);
-- GiST index for efficient IP lookups
CREATE INDEX idx_rbl_cache_ip ON rbl_cache USING GIST(ip inet_ops);- Listed IPs: Uses TTL from DNS response (typically 300-3600 seconds)
- Not Listed: Cached for 1 hour (3600 seconds)
- Errors: Cached for 5 minutes (300 seconds)
- Auto-cleanup: Expired entries cleaned every 5 minutes
- L1 Cache Hit (Memcache): ~0.1ms response time (99.99% faster than DNS lookup)
- L2 Cache Hit (PostgreSQL): ~1-5ms response time (99.7% faster than DNS lookup)
- Cache Miss: 100-5000ms (depends on RBL server)
- Concurrent Lookups: All RBL servers queried in parallel
- Storage: Two-tier caching with optional memcache + PostgreSQL with connection pooling
- Custom RBL CIDR Lookup: < 10ms using GiST indexes
Both the DNS server and web interface use the same two-tier cache system (optional memcache + PostgreSQL). This means:
- Queries via DNS server are cached for web interface
- Queries via web interface are cached for DNS server
- Custom RBL lookups integrated seamlessly
- Cache is shared across all services (memcache + PostgreSQL with connection pooling)
- Performance benefits apply to all query methods
- Single source of truth for all RBL lookups
- Hot data served from memcache (L1) for sub-millisecond performance
Example workflow:
- User queries DNS server:
dig @localhost -p 8053 2.0.0.127.zen.spamhaus.org - Result is cached in both layers (memcache L1 + PostgreSQL L2)
- Web interface query for
127.0.0.2returns cached result from memcache instantly (~0.1ms) - Multi-RBL query uses all cached results:
dig @localhost -p 8053 2.0.0.127.multi-rbl.example.com TXT - All subsequent queries (DNS, HTTP, or multi-RBL) benefit from two-tier cache
- If memcache restarts, PostgreSQL (L2) serves as backup and backfills memcache
A PHP-based DNS testing tool that sends raw DNS queries and displays results including TXT records.
php test-dns.phpThe script tests multiple query types:
- Single RBL lookups (e.g.,
2.0.0.127.zen.spamhaus.org) - Multi-RBL lookups (e.g.,
2.0.0.127.multi-rbl.example.com) - Regular domain lookups (forwarded to upstream DNS)
Output includes:
- DNS response codes (NOERROR, NXDOMAIN, etc.)
- A record IP addresses
- TXT record contents (useful for multi-RBL results)
- Response times and transaction details
Edit the $test_queries array in the script to customize test cases.
RBL servers are configured in etc/rbl-servers.json. Each entry contains:
{
"name": "Display Name",
"host": "dnsbl.example.org",
"description": "Description of this RBL"
}You can add or remove RBL servers by editing this file.
Multi-RBL zones are optionally configured in etc/multi-rbl-zones.json. Each zone defines a domain and the set of RBLs to check:
{
"zones": [
{
"domain": "multi-rbl.example.com",
"description": "All RBLs (comprehensive check)",
"rbls": "*"
},
{
"domain": "major-rbls.example.com",
"description": "Major RBLs only",
"rbls": ["zen.spamhaus.org", "cbl.abuseat.org", "bl.spamcop.net"]
}
]
}Zone Configuration Options:
domain: The DNS domain for this zone (e.g.,major-rbls.example.com)description: Human-readable description of the zone's purposerbls: Either"*"for all RBL servers, or an array of specific RBL hosts
Notes:
- If this file doesn't exist, the server uses the single domain from
DNS_MULTI_RBL_DOMAINin.env - The RBL hosts in the
rblsarray must match thehostvalues inetc/rbl-servers.json - Multiple zones allow you to create fast, targeted checks (e.g., Spamhaus-only) alongside comprehensive checks
The DNS server supports optional IP-based access control to restrict which networks can query the server. This is configured in etc/dns-access-control.json.
To enable access control:
- Copy the example configuration:
cp etc/dns-access-control.json.example etc/dns-access-control.json- Edit
etc/dns-access-control.json:
{
"enabled": true,
"description": "DNS server access control - restrict queries to specific IP networks",
"allowedNetworks": [
"192.168.0.0/16",
"10.0.0.0/8",
"172.16.0.0/12",
"127.0.0.1/32",
"2001:db8::/32",
"::1/128"
]
}- Restart the DNS server
Configuration Options:
enabled: Set totrueto activate access control,falseto allow all IPs (default)allowedNetworks: Array of CIDR network ranges (supports both IPv4 and IPv6)
Behavior:
- When
enabledisfalseor the file doesn't exist: All IPs can query (default behavior) - When
enabledistrue: Only IPs matching theallowedNetworkscan query - Denied queries receive a DNS REFUSED response
CIDR Examples:
192.168.1.0/24- Subnet 192.168.1.0 through 192.168.1.25510.0.0.0/8- Entire 10.x.x.x network203.0.113.5/32- Single IPv4 address2001:db8::/32- IPv6 network::1/128- Single IPv6 address (localhost)
Notes:
- The configuration file is excluded from git (only the example is tracked)
- Invalid CIDR entries are logged but won't crash the server
- Access control status is shown when the DNS server starts
Web server:
npm run devDNS server:
npm run dns-server:devEdit src/rbl-lookup.js and change the timeout parameter in lookupSingleRbl() (default: 5000ms).
For production environments, you'll want to run the web server as a background daemon that starts automatically on system boot.
PM2 is a production-grade process manager for Node.js applications that works on Linux, macOS, and Windows.
Install PM2 globally:
npm install -g pm2Start the web server:
pm2 start src/server.js --name multirbl-webStart the DNS server:
pm2 start src/start-dns-server.js --name multirbl-dnsUseful PM2 commands:
pm2 list # View all running processes
pm2 logs multirbl-web # View logs for web server
pm2 logs multirbl-dns # View logs for DNS server
pm2 restart multirbl-web # Restart web server
pm2 stop multirbl-web # Stop web server
pm2 delete multirbl-web # Remove from PM2
pm2 monit # Monitor CPU and memory usageAuto-start on system boot:
pm2 startup # Follow the displayed instructions
pm2 save # Save current process listCreate a systemd service file for the web server:
1. Create service file:
sudo nano /etc/systemd/system/multirbl-web.service2. Add the following content:
[Unit]
Description=Multi-RBL Lookup Web Server
After=network.target
[Service]
Type=simple
User=your-username
WorkingDirectory=/path/to/multirbl-lookup
ExecStart=/usr/bin/node src/server.js
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target3. Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable multirbl-web
sudo systemctl start multirbl-web4. Manage the service:
sudo systemctl status multirbl-web # Check status
sudo systemctl restart multirbl-web # Restart
sudo systemctl stop multirbl-web # Stop
sudo journalctl -u multirbl-web -f # View logsFor the DNS server, create a similar file at /etc/systemd/system/multirbl-dns.service:
[Unit]
Description=Multi-RBL Lookup DNS Server
After=network.target
[Service]
Type=simple
User=your-username
WorkingDirectory=/path/to/multirbl-lookup
ExecStart=/usr/bin/node src/start-dns-server.js --port=8053
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.targetInstall node-windows to run as a Windows service:
1. Install node-windows:
npm install -g node-windows2. Create install script (install-service.js):
const Service = require('node-windows').Service;
const svc = new Service({
name: 'Multi-RBL Lookup Web',
description: 'Multi-RBL Lookup Web Server',
script: require('path').join(__dirname, 'src', 'server.js'),
nodeOptions: ['--max_old_space_size=4096']
});
svc.on('install', function() {
svc.start();
console.log('Service installed and started');
});
svc.install();3. Run the install script as Administrator:
node install-service.js4. Manage via Windows Services:
- Open
services.msc - Find "Multi-RBL Lookup Web"
- Start/Stop/Restart as needed
Create a Dockerfile:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000 8053/udp
CMD ["node", "src/server.js"]Build and run:
docker build -t multirbl-lookup .
docker run -d -p 3000:3000 -p 8053:8053/udp --name multirbl multirbl-lookupUsing docker-compose (docker-compose.yml):
version: '3.8'
services:
multirbl:
build: .
ports:
- "3000:3000"
- "8053:8053/udp"
volumes:
- ./data:/app/data
restart: unless-stoppedStart with:
docker-compose up -dFor simple deployments, you can use cron to ensure the server stays running by checking and restarting it periodically.
1. Create a startup/monitor script (scripts/ensure-running.sh):
#!/bin/bash
# Configuration
PROJECT_DIR="/path/to/multirbl-lookup"
PID_FILE="$PROJECT_DIR/server.pid"
LOG_FILE="$PROJECT_DIR/logs/server.log"
NODE_BIN="/usr/bin/node"
cd "$PROJECT_DIR"
# Create logs directory if it doesn't exist
mkdir -p logs
# Function to check if server is running
is_running() {
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p "$PID" > /dev/null 2>&1; then
return 0
fi
fi
return 1
}
# Function to start server
start_server() {
echo "[$(date)] Starting Multi-RBL Lookup server..." >> "$LOG_FILE"
nohup "$NODE_BIN" src/server.js >> "$LOG_FILE" 2>&1 &
echo $! > "$PID_FILE"
echo "[$(date)] Server started with PID $(cat $PID_FILE)" >> "$LOG_FILE"
}
# Check if server is running, start if not
if is_running; then
echo "[$(date)] Server is running with PID $(cat $PID_FILE)" >> "$LOG_FILE"
else
echo "[$(date)] Server is not running, starting..." >> "$LOG_FILE"
start_server
fi2. Make the script executable:
chmod +x scripts/ensure-running.sh3. Add to crontab:
crontab -eAdd one of these lines:
Check every minute:
* * * * * /path/to/multirbl-lookup/scripts/ensure-running.shCheck every 5 minutes:
*/5 * * * * /path/to/multirbl-lookup/scripts/ensure-running.shStart at system reboot:
@reboot /path/to/multirbl-lookup/scripts/ensure-running.shCombined (check every 5 minutes + start at reboot):
@reboot /path/to/multirbl-lookup/scripts/ensure-running.sh
*/5 * * * * /path/to/multirbl-lookup/scripts/ensure-running.sh4. Create manual control scripts:
Stop script (scripts/stop-server.sh):
#!/bin/bash
PROJECT_DIR="/path/to/multirbl-lookup"
PID_FILE="$PROJECT_DIR/server.pid"
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p "$PID" > /dev/null 2>&1; then
kill "$PID"
echo "Server stopped (PID $PID)"
rm "$PID_FILE"
else
echo "PID file exists but process not running"
rm "$PID_FILE"
fi
else
echo "Server is not running (no PID file)"
fiStatus script (scripts/status-server.sh):
#!/bin/bash
PROJECT_DIR="/path/to/multirbl-lookup"
PID_FILE="$PROJECT_DIR/server.pid"
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p "$PID" > /dev/null 2>&1; then
echo "Server is running (PID $PID)"
ps -p "$PID" -o pid,ppid,cmd,%mem,%cpu,etime
else
echo "PID file exists but process not running"
fi
else
echo "Server is not running (no PID file)"
fiMake them executable:
chmod +x scripts/stop-server.sh scripts/status-server.sh5. View logs:
tail -f logs/server.logNote: This cron-based approach is simpler but less robust than PM2 or systemd. The server won't restart immediately on crashes (only when cron runs), and there's no built-in log rotation or advanced process management.
npm start- Start web server (with caching)npm run dev- Start web server with auto-reloadnpm run dns-server- Start DNS servernpm run dns-server:dev- Start DNS server with auto-reloadnpm run dns-stats- Show cache statisticsnpm run dns-clear-cache- Clear all cached entriesnpm run test-cache- Test cache functionality and performance
Affero GPL (see LICENSE)