implements a lightweight, plugin-based approach for automatically downloading and updating DNS rule files. This approach combines the Downloader Plugin with the Cron Plugin to provide scheduled file updates without requiring an HTTP server or Admin API.
┌─────────────────────────────────────────────────────────────────┐
│ Schedule: Daily at 02:05 UTC │
│ (Cron Expression: "0 5 2 * * *") │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────┐
│ CronPlugin │
│ (Scheduler) │
└────────┬─────────┘
│
invoke_plugin action
│
┌────────▼─────────┐
│ DownloaderPlugin │
│ (Download Files) │
└────────┬─────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
gfw.txt reject-list.txt hosts.txt
│ │ │
└────────────────────┼────────────────────┘
│
┌────────▼────────┐
│ File Watcher │
│ (auto_reload) │
└────────┬────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
DomainSet IPSet Hosts
(auto reload) (auto reload) (auto reload)
Handles the actual file downloads with robust error handling and atomic updates.
Configuration Options:
- tag: file_downloader
type: downloader
args:
files:
- url: "https://example.com/gfw.txt"
path: "gfw.txt"
- url: "https://example.com/reject.txt"
path: "reject.txt"
# Download timeout per file (seconds)
timeout_secs: 30
# Concurrent download strategy
concurrent: false
Features:
Triggers the downloader on a specified schedule.
Configuration Options:
- tag: auto_update_scheduler
type: cron
args:
# Cron expression: second minute hour day month day-of-week
cron: "0 5 2 * * *" # Daily at 02:05 UTC
# Action type
action: invoke_plugin
# Target plugin
args:
plugin: "file_downloader"
Cron Expression Examples:
0 5 2 * * * - Every day at 02:05:00 UTC0 0 */6 * * * - Every 6 hours at 00:00 minutes0 0 0 1 * * - First day of every month at 00:00 UTC0 0 0 * * 0 - Every Sunday at 00:00 UTC (weekly)DomainSet, IPSet, and Hosts plugins automatically reload files when they change:
- tag: domain_gfw
type: domain_set
args:
files:
- "gfw.txt"
auto_reload: true # Key: enables automatic reload on file change
invoke_plugin actionRules from reloaded files are immediately available for DNS queries:
DNS Query (e.g., facebook.com)
│
▼
Check domain_gfw matcher (uses updated gfw.txt)
│
├─ Found → Forward to upstream_proxy
│
└─ Not found → Forward to upstream_direct
plugins:
# Download files
- tag: file_downloader
type: downloader
args:
files:
- url: "https://example.com/gfw.txt"
path: "gfw.txt"
timeout_secs: 30
concurrent: false
# Schedule the download
- tag: scheduler
type: cron
args:
cron: "0 5 2 * * *"
action: invoke_plugin
args:
plugin: "file_downloader"
# Load with auto-reload
- tag: domain_list
type: domain_set
args:
files: ["gfw.txt"]
auto_reload: true
- tag: file_downloader
type: downloader
args:
files:
- url: "https://example.com/gfw.txt"
path: "gfw.txt"
- url: "https://example.com/reject.txt"
path: "reject.txt"
- url: "https://example.com/hosts.txt"
path: "hosts.txt"
timeout_secs: 60
concurrent: true # Download 3 files simultaneously
- tag: scheduler
type: cron
args:
cron: "0 0 3 * * 0" # 0 = Sunday
action: invoke_plugin
args:
plugin: "file_downloader"
# Fast daily update (lightweight lists)
- tag: daily_downloader
type: downloader
args:
files:
- url: "https://example.com/gfw.txt"
path: "gfw.txt"
timeout_secs: 20
concurrent: false
- tag: daily_scheduler
type: cron
args:
cron: "0 2 2 * * *"
action: invoke_plugin
args:
plugin: "daily_downloader"
# Weekly comprehensive update (all lists)
- tag: weekly_downloader
type: downloader
args:
files:
- url: "https://example.com/gfw.txt"
path: "gfw.txt"
- url: "https://example.com/reject.txt"
path: "reject.txt"
- url: "https://example.com/hosts.txt"
path: "hosts.txt"
timeout_secs: 60
concurrent: true
- tag: weekly_scheduler
type: cron
args:
cron: "0 0 3 * * 0" # Weekly
action: invoke_plugin
args:
plugin: "weekly_downloader"
Sequential Mode (concurrent: false):
Concurrent Mode (concurrent: true):
File Replace + Reload:
# Plugin initialization
INFO downloader: Downloader plugin initialized
# Cron trigger
INFO cron: Cron scheduler started
# Download start
INFO downloader: Starting file downloads count=3 concurrent=false
# File downloaded
INFO downloader: File downloaded url="https://..." path="gfw.txt" size_bytes=524288 duration_ms=2500
# All downloads complete
INFO downloader: All files downloaded successfully count=3 duration_ms=5200
# File reload
INFO domain_set: Reloading file "gfw.txt" from file system
# Reload complete
INFO domain_set: Domain set loaded: 10234 entries
# Watch for failed downloads
tail -f logs/lazydns.log | grep "download failed"
# Monitor file sizes after update
ls -lh *.txt
# Check last update time
stat gfw.txt
# Verify log rotation
ls -la logs/lazydns.log*
Check 1: Cron scheduler running
grep "Cron scheduler started" logs/lazydns.log
Check 2: Cron expression correct
# Test cron expression with cronexpr tool or online calculator
# "0 5 2 * * *" should trigger at 02:05:00
Check 3: Plugin registered
grep "Downloader plugin initialized" logs/lazydns.log
Issue: Files too large or network too slow
Solution: Increase timeout_secs
- tag: file_downloader
type: downloader
args:
files: [...]
timeout_secs: 60 # Increased from 30
Issue: auto_reload not enabled
Solution: Enable auto_reload on data plugins
- tag: domain_list
type: domain_set
args:
files: ["gfw.txt"]
auto_reload: true # Must be explicitly true
Issue: Network bottleneck or overloaded disk
Solution: Use sequential mode
- tag: file_downloader
type: downloader
args:
files: [...]
concurrent: false # Download one at a time
// 1. Download to temporary file
let temp_path = format!("{}.tmp", spec.path);
write_to_file(&temp_path, &content)?;
// 2. Atomic rename (OS-level guarantee)
fs::rename(&temp_path, &spec.path)?;
// 3. File watcher detects change
// 4. auto_reload triggers reload
Benefits:
invoke_plugin Action Flow:
plugin: "file_downloader"execute() method// Pseudo-code
if let Some(plugin_tag) = args.get("plugin") {
if let Some(plugin) = plugin_registry.get(plugin_tag) {
// Spawn async task
tokio::spawn(async move {
let _ = plugin.execute(&mut ctx).await;
});
}
}
For formats requiring transformation (e.g., dnsmasq → POSIX domain list):
# Download raw dnsmasq format
- tag: downloader
type: downloader
args:
files:
- url: "https://example.com/dnsmasq.conf"
path: "dnsmasq-raw.conf"
timeout_secs: 30
# Post-process with external command (requires Plan A elements)
# TODO: Add exec_plugin to run conversion script
Ensure service availability if downloads fail:
- tag: domain_gfw
type: domain_set
args:
files:
- "gfw.txt" # Updated by downloader
- "gfw-fallback.txt" # Static fallback
auto_reload: true