Skip to content

Commit

Permalink
Support $DHCP and $BOOTSTRAP keywords in forwarding rules
Browse files Browse the repository at this point in the history
Ideally, that should also be supported by the captive portal
handler.

Great work by @lifenjoiner

Fixes #2460
  • Loading branch information
jedisct1 committed Jan 10, 2025
1 parent cd3cb2e commit eda26b4
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 40 deletions.
16 changes: 13 additions & 3 deletions dnscrypt-proxy/example-forwarding-rules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,31 @@
## <domain> <server address>[:port] [, <server address>[:port]...]
## IPv6 addresses can be specified by enclosing the address in square brackets.

## The following keywords can also be used instead of a server address:
## $BOOTSTRAP to use the default bootstrap resolvers
## $DHCP to use the default DNS resolvers provided by the DHCP server

## In order to enable this feature, the "forwarding_rules" property needs to
## be set to this file name inside the main configuration file.

## Blocking IPv6 may prevent local devices from being discovered.
## If this happens, set `block_ipv6` to `false` in the main config file.

## Forward *.lan, *.local, *.home, *.home.arpa, *.internal and *.localdomain to 192.168.1.1
## Forward *.lan, *.home, *.home.arpa, and *.localdomain to 192.168.1.1
# lan 192.168.1.1
# local 192.168.1.1
# home 192.168.1.1
# home.arpa 192.168.1.1
# internal 192.168.1.1
# localdomain 192.168.1.1
# 192.in-addr.arpa 192.168.1.1

## Forward *.local to the resolvers provided by the DHCP server
# local $DHCP

## Forward *.internal to 192.168.1.1, and if it doesn't work, to the
## DNS from the local DHCP server, and if it still doesn't work, to the
## bootstrap resolvers
# internal 192.168.1.1,$DHCP,$BOOTSTRAP

## Forward queries for example.com and *.example.com to 9.9.9.9 and 8.8.8.8
# example.com 9.9.9.9,8.8.8.8

Expand Down
190 changes: 153 additions & 37 deletions dnscrypt-proxy/plugin_forward.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,32 @@ import (
"strings"

"github.com/jedisct1/dlog"
"github.com/lifenjoiner/dhcpdns"
"github.com/miekg/dns"
)

type PluginForwardEntry struct {
domain string
type SearchSequenceItemType int

const (
Explicit SearchSequenceItemType = iota
Bootstrap
DHCP
)

type SearchSequenceItem struct {
typ SearchSequenceItemType
servers []string
}

type PluginForwardEntry struct {
domain string
sequence []SearchSequenceItem
}

type PluginForward struct {
forwardMap []PluginForwardEntry
forwardMap []PluginForwardEntry
bootstrapResolvers []string
dhcpdns []*dhcpdns.Detector
}

func (plugin *PluginForward) Name() string {
Expand All @@ -29,6 +45,11 @@ func (plugin *PluginForward) Description() string {

func (plugin *PluginForward) Init(proxy *Proxy) error {
dlog.Noticef("Loading the set of forwarding rules from [%s]", proxy.forwardFile)

if proxy.xTransport != nil {
plugin.bootstrapResolvers = proxy.xTransport.bootstrapResolvers
}

lines, err := ReadTextFile(proxy.forwardFile)
if err != nil {
return err
Expand All @@ -46,27 +67,77 @@ func (plugin *PluginForward) Init(proxy *Proxy) error {
)
}
domain = strings.ToLower(domain)
var servers []string
requiresDHCP := false
var sequence []SearchSequenceItem
for _, server := range strings.Split(serversStr, ",") {
server = strings.TrimSpace(server)
server = strings.TrimPrefix(server, "[")
server = strings.TrimSuffix(server, "]")
if ip := net.ParseIP(server); ip != nil {
if ip.To4() != nil {
server = fmt.Sprintf("%s:%d", server, 53)
switch server {
case "$BOOTSTRAP":
if len(plugin.bootstrapResolvers) == 0 {
return fmt.Errorf(
"Syntax error for a forwarding rule at line %d. No bootstrap resolvers available",
1+lineNo,
)
}
if len(sequence) > 0 && sequence[len(sequence)-1].typ == Bootstrap {
// Ignore repetitions
} else {
server = fmt.Sprintf("[%s]:%d", server, 53)
sequence = append(sequence, SearchSequenceItem{typ: Bootstrap})
dlog.Infof("Forwarding [%s] to the bootstrap servers", domain)
}
case "$DHCP":
if len(sequence) > 0 && sequence[len(sequence)-1].typ == DHCP {
// Ignore repetitions
} else {
sequence = append(sequence, SearchSequenceItem{typ: Bootstrap})
dlog.Infof("Forwarding [%s] to the DHCP servers", domain)
}
requiresDHCP = true
default:
if strings.HasPrefix(server, "$") {
dlog.Criticalf("Unknown keyword [%s] at line %d", server, 1+lineNo)
continue
}
server = strings.TrimPrefix(server, "[")
server = strings.TrimSuffix(server, "]")
if ip := net.ParseIP(server); ip != nil {
if ip.To4() != nil {
server = fmt.Sprintf("%s:%d", server, 53)
} else {
server = fmt.Sprintf("[%s]:%d", server, 53)
}
}
idxServers := -1
for i, item := range sequence {
if item.typ == Explicit {
idxServers = i
}
}
if idxServers == -1 {
sequence = append(sequence, SearchSequenceItem{typ: Explicit, servers: []string{server}})
} else {
sequence[idxServers].servers = append(sequence[idxServers].servers, server)
}
dlog.Infof("Forwarding [%s] to [%s]", domain, server)
}
dlog.Infof("Forwarding [%s] to %s", domain, server)
servers = append(servers, server)
}
if len(servers) == 0 {
continue
if requiresDHCP {
if proxy.SourceIPv6 {
dlog.Info("Starting a DHCP/DNS detector for IPv6")
d6 := &dhcpdns.Detector{RemoteIPPort: "[2001:DB8::53]:80"}
go d6.Serve(9, 10)
plugin.dhcpdns = append(plugin.dhcpdns, d6)
}
if proxy.SourceIPv4 {
dlog.Info("Starting a DHCP/DNS detector for IPv4")
d4 := &dhcpdns.Detector{RemoteIPPort: "192.0.2.53:80"}
go d4.Serve(9, 10)
plugin.dhcpdns = append(plugin.dhcpdns, d4)
}
}
plugin.forwardMap = append(plugin.forwardMap, PluginForwardEntry{
domain: domain,
servers: servers,
domain: domain,
sequence: sequence,
})
}
return nil
Expand All @@ -83,7 +154,7 @@ func (plugin *PluginForward) Reload() error {
func (plugin *PluginForward) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
qName := pluginsState.qName
qNameLen := len(qName)
var servers []string
var sequence []SearchSequenceItem
for _, candidate := range plugin.forwardMap {
candidateLen := len(candidate.domain)
if candidateLen > qNameLen {
Expand All @@ -92,33 +163,78 @@ func (plugin *PluginForward) Eval(pluginsState *PluginsState, msg *dns.Msg) erro
if (qName[qNameLen-candidateLen:] == candidate.domain &&
(candidateLen == qNameLen || (qName[qNameLen-candidateLen-1] == '.'))) ||
(candidate.domain == ".") {
servers = candidate.servers
sequence = candidate.sequence
break
}
}
if len(servers) == 0 {
if len(sequence) == 0 {
return nil
}
server := servers[rand.Intn(len(servers))]
pluginsState.serverName = server
client := dns.Client{Net: pluginsState.serverProto, Timeout: pluginsState.timeout}
respMsg, _, err := client.Exchange(msg, server)
if err != nil {
return err
}
if respMsg.Truncated {
client.Net = "tcp"
var err error
var respMsg *dns.Msg
var tries = 4
for _, item := range sequence {
var server string
switch item.typ {
case Explicit:
server = item.servers[rand.Intn(len(item.servers))]
pluginsState.serverName = server
case Bootstrap:
server = plugin.bootstrapResolvers[rand.Intn(len(plugin.bootstrapResolvers))]
pluginsState.serverName = "[BOOTSTRAP]"
case DHCP:
const maxInconsistency = 9
for _, dhcpdns := range plugin.dhcpdns {
inconsistency, ip, dhcpDNS, err := dhcpdns.Status()
if err != nil && ip != "" && inconsistency > maxInconsistency {
dhcpDNS = nil
}
if len(dhcpDNS) > 0 {
server = net.JoinHostPort(dhcpDNS[rand.Intn(len(dhcpDNS))].String(), "53")
break
}
}
if len(server) == 0 {
dlog.Warn("DHCP didn't provide any DNS server")
continue
}
pluginsState.serverName = "[DHCP]"
}
if len(server) == 0 {
continue
}

if tries == 0 {
break
}
tries--
dlog.Debugf("Forwarding [%s] to [%s]", qName, server)
client := dns.Client{Net: pluginsState.serverProto, Timeout: pluginsState.timeout}
respMsg, _, err = client.Exchange(msg, server)
if err != nil {
return err
continue
}
if respMsg.Truncated {
client.Net = "tcp"
respMsg, _, err = client.Exchange(msg, server)
if err != nil {
continue
}
}
if len(sequence) > 0 {
switch respMsg.Rcode {
case dns.RcodeNameError, dns.RcodeRefused, dns.RcodeNotAuth:
continue
}
}
if edns0 := respMsg.IsEdns0(); edns0 == nil || !edns0.Do() {
respMsg.AuthenticatedData = false
}
respMsg.Id = msg.Id
pluginsState.synthResponse = respMsg
pluginsState.action = PluginsActionSynth
pluginsState.returnCode = PluginsReturnCodeForward
return nil
}
if edns0 := respMsg.IsEdns0(); edns0 == nil || !edns0.Do() {
respMsg.AuthenticatedData = false
}
respMsg.Id = msg.Id
pluginsState.synthResponse = respMsg
pluginsState.action = PluginsActionSynth
pluginsState.returnCode = PluginsReturnCodeForward
return nil
return err
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/jedisct1/xsecretbox v0.0.0-20241212092125-3afc4917ac41
github.com/k-sone/critbitgo v1.4.0
github.com/kardianos/service v1.2.2
github.com/lifenjoiner/dhcpdns v0.0.6
github.com/miekg/dns v1.1.62
github.com/opencoff/go-sieve v0.2.1
github.com/powerman/check v1.8.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ github.com/k-sone/critbitgo v1.4.0 h1:l71cTyBGeh6X5ATh6Fibgw3+rtNT80BA0uNNWgkPrb
github.com/k-sone/critbitgo v1.4.0/go.mod h1:7E6pyoyADnFxlUBEKcnfS49b7SUAQGMK+OAp/UQvo0s=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/lifenjoiner/dhcpdns v0.0.6 h1:rn4Y5RRR5sgQ6RjWenwhA7i/uHzHW9hbZpCobA4CAJs=
github.com/lifenjoiner/dhcpdns v0.0.6/go.mod h1:BixeaGeafYzDIuDCYIUbSOdi4m+TScpzI9cZGYgzgSk=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
Expand Down
3 changes: 3 additions & 0 deletions vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ github.com/k-sone/critbitgo
# github.com/kardianos/service v1.2.2
## explicit; go 1.12
github.com/kardianos/service
# github.com/lifenjoiner/dhcpdns v0.0.6
## explicit; go 1.20
github.com/lifenjoiner/dhcpdns
# github.com/miekg/dns v1.1.62
## explicit; go 1.19
github.com/miekg/dns
Expand Down

0 comments on commit eda26b4

Please # to comment.