Add DNS filtering with dnsmasq address=/ directives for wildcard blocking
Adds Pi-hole-style DNS filtering using dnsmasq's native address=/ directives, which block domains and all subdomains. Users subscribe to blocklists by URL or upload files, with suggested lists from Hagezi, Steven Black, and OISD. Background runner refreshes lists on a configurable interval (default 24h).
This commit is contained in:
@@ -26,7 +26,8 @@ type DNSEntry struct {
|
||||
|
||||
// ConfigGenerator handles dnsmasq configuration generation
|
||||
type ConfigGenerator struct {
|
||||
configPath string
|
||||
configPath string
|
||||
filterConfPath string // path to addn-hosts file for DNS filtering
|
||||
}
|
||||
|
||||
// NewConfigGenerator creates a new dnsmasq config generator
|
||||
@@ -44,6 +45,12 @@ func (g *ConfigGenerator) GetConfigPath() string {
|
||||
return g.configPath
|
||||
}
|
||||
|
||||
// SetFilterConfPath sets the path to an additional hosts file for DNS filtering.
|
||||
// When set, the generated config includes an addn-hosts= directive.
|
||||
func (g *ConfigGenerator) SetFilterConfPath(path string) {
|
||||
g.filterConfPath = path
|
||||
}
|
||||
|
||||
// Generate creates a dnsmasq configuration from registered domain entries.
|
||||
// It auto-detects the Wild Central server's IP address for the listen directive.
|
||||
func (g *ConfigGenerator) Generate(cfg *config.State, entries []DNSEntry) string {
|
||||
@@ -94,10 +101,16 @@ log-queries
|
||||
log-dhcp
|
||||
`
|
||||
|
||||
return fmt.Sprintf(template,
|
||||
result := fmt.Sprintf(template,
|
||||
dnsIP,
|
||||
resolution_section,
|
||||
)
|
||||
|
||||
if g.filterConfPath != "" {
|
||||
result += fmt.Sprintf("\n# DNS Filtering\nconf-file=%s\n", g.filterConfPath)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// RestartService reloads the dnsmasq service (SIGHUP — no downtime).
|
||||
@@ -270,6 +283,10 @@ log-dhcp
|
||||
}
|
||||
}
|
||||
|
||||
if g.filterConfPath != "" {
|
||||
fmt.Fprintf(&sb, "\n# DNS Filtering\nconf-file=%s\n", g.filterConfPath)
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
|
||||
@@ -313,6 +313,43 @@ func TestGenerate_MultipleEntries(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_ConfFile(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
g.SetFilterConfPath("/var/lib/wild-central/dns-filter/hosts.blocked")
|
||||
|
||||
out := g.Generate(nil, nil)
|
||||
|
||||
if !strings.Contains(out, "conf-file=/var/lib/wild-central/dns-filter/hosts.blocked") {
|
||||
t.Errorf("expected addn-hosts directive, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "# DNS Filtering") {
|
||||
t.Errorf("expected DNS Filtering comment, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_NoConfFileWhenEmpty(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
// addnHostsPath is empty by default
|
||||
|
||||
out := g.Generate(nil, nil)
|
||||
|
||||
if strings.Contains(out, "addn-hosts") {
|
||||
t.Errorf("should not contain addn-hosts when path is empty, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateMainConfig_ConfFile(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
g.SetFilterConfPath("/tmp/hosts.blocked")
|
||||
|
||||
cfg := &config.State{}
|
||||
out := g.GenerateMainConfig(cfg)
|
||||
|
||||
if !strings.Contains(out, "conf-file=/tmp/hosts.blocked") {
|
||||
t.Errorf("expected addn-hosts in main config, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// Test: nil config doesn't panic, uses auto-detect for IP
|
||||
func TestGenerate_NilConfig(t *testing.T) {
|
||||
g := NewConfigGenerator("/tmp/test-dnsmasq.conf")
|
||||
|
||||
Reference in New Issue
Block a user