mirror of
https://github.com/DNSCrypt/dnscrypt-proxy.git
synced 2025-03-04 02:14:40 +01:00
Support $DHCP and $BOOTSTRAP keywords in forwarding rules
Ideally, that should also be supported by the captive portal handler. Great work by @lifenjoiner Fixes #2460
This commit is contained in:
parent
cd3cb2e98b
commit
eda26b4a79
5 changed files with 173 additions and 41 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -7,16 +7,32 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/jedisct1/dlog"
|
||||
"github.com/lifenjoiner/dhcpdns"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type SearchSequenceItemType int
|
||||
|
||||
const (
|
||||
Explicit SearchSequenceItemType = iota
|
||||
Bootstrap
|
||||
DHCP
|
||||
)
|
||||
|
||||
type SearchSequenceItem struct {
|
||||
typ SearchSequenceItemType
|
||||
servers []string
|
||||
}
|
||||
|
||||
type PluginForwardEntry struct {
|
||||
domain string
|
||||
servers []string
|
||||
sequence []SearchSequenceItem
|
||||
}
|
||||
|
||||
type PluginForward struct {
|
||||
forwardMap []PluginForwardEntry
|
||||
bootstrapResolvers []string
|
||||
dhcpdns []*dhcpdns.Detector
|
||||
}
|
||||
|
||||
func (plugin *PluginForward) Name() string {
|
||||
|
@ -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
|
||||
|
@ -46,9 +67,37 @@ 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)
|
||||
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 {
|
||||
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 {
|
||||
|
@ -58,15 +107,37 @@ func (plugin *PluginForward) Init(proxy *Proxy) error {
|
|||
server = fmt.Sprintf("[%s]:%d", server, 53)
|
||||
}
|
||||
}
|
||||
dlog.Infof("Forwarding [%s] to %s", domain, server)
|
||||
servers = append(servers, server)
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
if len(servers) == 0 {
|
||||
continue
|
||||
}
|
||||
plugin.forwardMap = append(plugin.forwardMap, PluginForwardEntry{
|
||||
domain: domain,
|
||||
servers: servers,
|
||||
sequence: sequence,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
|
@ -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 {
|
||||
|
@ -92,25 +163,68 @@ 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))]
|
||||
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)
|
||||
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 {
|
||||
return err
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(sequence) > 0 {
|
||||
switch respMsg.Rcode {
|
||||
case dns.RcodeNameError, dns.RcodeRefused, dns.RcodeNotAuth:
|
||||
continue
|
||||
}
|
||||
}
|
||||
if edns0 := respMsg.IsEdns0(); edns0 == nil || !edns0.Do() {
|
||||
|
@ -122,3 +236,5 @@ func (plugin *PluginForward) Eval(pluginsState *PluginsState, msg *dns.Msg) erro
|
|||
pluginsState.returnCode = PluginsReturnCodeForward
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -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
|
||||
|
|
2
go.sum
2
go.sum
|
@ -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=
|
||||
|
|
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue