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]...]
|
## <domain> <server address>[:port] [, <server address>[:port]...]
|
||||||
## IPv6 addresses can be specified by enclosing the address in square brackets.
|
## 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
|
## In order to enable this feature, the "forwarding_rules" property needs to
|
||||||
## be set to this file name inside the main configuration file.
|
## be set to this file name inside the main configuration file.
|
||||||
|
|
||||||
## Blocking IPv6 may prevent local devices from being discovered.
|
## Blocking IPv6 may prevent local devices from being discovered.
|
||||||
## If this happens, set `block_ipv6` to `false` in the main config file.
|
## 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
|
# lan 192.168.1.1
|
||||||
# local 192.168.1.1
|
|
||||||
# home 192.168.1.1
|
# home 192.168.1.1
|
||||||
# home.arpa 192.168.1.1
|
# home.arpa 192.168.1.1
|
||||||
# internal 192.168.1.1
|
|
||||||
# localdomain 192.168.1.1
|
# localdomain 192.168.1.1
|
||||||
# 192.in-addr.arpa 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
|
## 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
|
# example.com 9.9.9.9,8.8.8.8
|
||||||
|
|
||||||
|
|
|
@ -7,16 +7,32 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jedisct1/dlog"
|
"github.com/jedisct1/dlog"
|
||||||
|
"github.com/lifenjoiner/dhcpdns"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SearchSequenceItemType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Explicit SearchSequenceItemType = iota
|
||||||
|
Bootstrap
|
||||||
|
DHCP
|
||||||
|
)
|
||||||
|
|
||||||
|
type SearchSequenceItem struct {
|
||||||
|
typ SearchSequenceItemType
|
||||||
|
servers []string
|
||||||
|
}
|
||||||
|
|
||||||
type PluginForwardEntry struct {
|
type PluginForwardEntry struct {
|
||||||
domain string
|
domain string
|
||||||
servers []string
|
sequence []SearchSequenceItem
|
||||||
}
|
}
|
||||||
|
|
||||||
type PluginForward struct {
|
type PluginForward struct {
|
||||||
forwardMap []PluginForwardEntry
|
forwardMap []PluginForwardEntry
|
||||||
|
bootstrapResolvers []string
|
||||||
|
dhcpdns []*dhcpdns.Detector
|
||||||
}
|
}
|
||||||
|
|
||||||
func (plugin *PluginForward) Name() string {
|
func (plugin *PluginForward) Name() string {
|
||||||
|
@ -29,6 +45,11 @@ func (plugin *PluginForward) Description() string {
|
||||||
|
|
||||||
func (plugin *PluginForward) Init(proxy *Proxy) error {
|
func (plugin *PluginForward) Init(proxy *Proxy) error {
|
||||||
dlog.Noticef("Loading the set of forwarding rules from [%s]", proxy.forwardFile)
|
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)
|
lines, err := ReadTextFile(proxy.forwardFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -46,9 +67,37 @@ func (plugin *PluginForward) Init(proxy *Proxy) error {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
domain = strings.ToLower(domain)
|
domain = strings.ToLower(domain)
|
||||||
var servers []string
|
requiresDHCP := false
|
||||||
|
var sequence []SearchSequenceItem
|
||||||
for _, server := range strings.Split(serversStr, ",") {
|
for _, server := range strings.Split(serversStr, ",") {
|
||||||
server = strings.TrimSpace(server)
|
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.TrimPrefix(server, "[")
|
||||||
server = strings.TrimSuffix(server, "]")
|
server = strings.TrimSuffix(server, "]")
|
||||||
if ip := net.ParseIP(server); ip != nil {
|
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)
|
server = fmt.Sprintf("[%s]:%d", server, 53)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dlog.Infof("Forwarding [%s] to %s", domain, server)
|
idxServers := -1
|
||||||
servers = append(servers, server)
|
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{
|
plugin.forwardMap = append(plugin.forwardMap, PluginForwardEntry{
|
||||||
domain: domain,
|
domain: domain,
|
||||||
servers: servers,
|
sequence: sequence,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -83,7 +154,7 @@ func (plugin *PluginForward) Reload() error {
|
||||||
func (plugin *PluginForward) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
|
func (plugin *PluginForward) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
|
||||||
qName := pluginsState.qName
|
qName := pluginsState.qName
|
||||||
qNameLen := len(qName)
|
qNameLen := len(qName)
|
||||||
var servers []string
|
var sequence []SearchSequenceItem
|
||||||
for _, candidate := range plugin.forwardMap {
|
for _, candidate := range plugin.forwardMap {
|
||||||
candidateLen := len(candidate.domain)
|
candidateLen := len(candidate.domain)
|
||||||
if candidateLen > qNameLen {
|
if candidateLen > qNameLen {
|
||||||
|
@ -92,25 +163,68 @@ func (plugin *PluginForward) Eval(pluginsState *PluginsState, msg *dns.Msg) erro
|
||||||
if (qName[qNameLen-candidateLen:] == candidate.domain &&
|
if (qName[qNameLen-candidateLen:] == candidate.domain &&
|
||||||
(candidateLen == qNameLen || (qName[qNameLen-candidateLen-1] == '.'))) ||
|
(candidateLen == qNameLen || (qName[qNameLen-candidateLen-1] == '.'))) ||
|
||||||
(candidate.domain == ".") {
|
(candidate.domain == ".") {
|
||||||
servers = candidate.servers
|
sequence = candidate.sequence
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(servers) == 0 {
|
if len(sequence) == 0 {
|
||||||
return nil
|
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
|
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}
|
client := dns.Client{Net: pluginsState.serverProto, Timeout: pluginsState.timeout}
|
||||||
respMsg, _, err := client.Exchange(msg, server)
|
respMsg, _, err = client.Exchange(msg, server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
continue
|
||||||
}
|
}
|
||||||
if respMsg.Truncated {
|
if respMsg.Truncated {
|
||||||
client.Net = "tcp"
|
client.Net = "tcp"
|
||||||
respMsg, _, err = client.Exchange(msg, server)
|
respMsg, _, err = client.Exchange(msg, server)
|
||||||
if err != nil {
|
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() {
|
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
|
pluginsState.returnCode = PluginsReturnCodeForward
|
||||||
return nil
|
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/jedisct1/xsecretbox v0.0.0-20241212092125-3afc4917ac41
|
||||||
github.com/k-sone/critbitgo v1.4.0
|
github.com/k-sone/critbitgo v1.4.0
|
||||||
github.com/kardianos/service v1.2.2
|
github.com/kardianos/service v1.2.2
|
||||||
|
github.com/lifenjoiner/dhcpdns v0.0.6
|
||||||
github.com/miekg/dns v1.1.62
|
github.com/miekg/dns v1.1.62
|
||||||
github.com/opencoff/go-sieve v0.2.1
|
github.com/opencoff/go-sieve v0.2.1
|
||||||
github.com/powerman/check v1.8.0
|
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/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 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
|
||||||
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
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 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||||
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
|
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
|
# github.com/kardianos/service v1.2.2
|
||||||
## explicit; go 1.12
|
## explicit; go 1.12
|
||||||
github.com/kardianos/service
|
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
|
# github.com/miekg/dns v1.1.62
|
||||||
## explicit; go 1.19
|
## explicit; go 1.19
|
||||||
github.com/miekg/dns
|
github.com/miekg/dns
|
||||||
|
|
Loading…
Add table
Reference in a new issue