mirror of
https://github.com/DNSCrypt/dnscrypt-proxy.git
synced 2025-04-06 22:57:37 +03:00
Forwarding plugin
This commit is contained in:
parent
1b38364e48
commit
96dadc7aca
8 changed files with 170 additions and 53 deletions
49
README.md
49
README.md
|
@ -8,30 +8,31 @@ A modern client implementation of the [DNSCrypt](https://github.com/DNSCrypt/dns
|
|||
|
||||
## Current status/features
|
||||
|
||||
| Features | dnscrypt-proxy 1.x | dnscrypt-proxy 2.x |
|
||||
| ---------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| Status | Old PoC, barely maintained any more | Very new, but quickly evolving |
|
||||
| Code quality | Big ugly mess | Readable, easy to work on |
|
||||
| Reliability | Poor, due to completely broken handling of edge cases | Excellent |
|
||||
| Security | Written in C, bundles patched versions from old branches of system libraries | Written in standard and portable Go |
|
||||
| Dependencies | Specific versions of dnscrypt-proxy, libldns and libtool | None |
|
||||
| Upstream connections using TCP | Catastrophic, requires client retries | Implemented as anyone would expect, works well with TOR |
|
||||
| XChaCha20 support | Only if compiled with recent versions of libsodium | Yes, always available |
|
||||
| Support of links with small MTU | Unreliable due to completely broken padding | Reliable, properly implemented |
|
||||
| Support for multiple servers | Nonexistent | Yes, with automatic failover and load-balancing |
|
||||
| Custom additions | C API, requires libldns for sanity | Simple Go structures using miekg/dns |
|
||||
| AAAA blocking for IPv4-only networks | Yes | Yes |
|
||||
| DNS caching | Yes, with ugly hacks for DNSSEC support | Yes, without ugly hacks |
|
||||
| EDNS support | Broken with custom records | Yes |
|
||||
| Asynchronous filters | Lol, no, filters block everything | Of course, thanks to Go |
|
||||
| Session-local storage for extensions | Impossible | Yes |
|
||||
| Multicore support | Nonexistent | Yes, thanks to Go |
|
||||
| Efficient padding of queries | Couldn't be any worse | Yes |
|
||||
| Multiple local sockets | Impossible | Of course. IPv4, IPv6, as many as you like |
|
||||
| Automatically picks the fastest servers | Lol, it supports only one at a time, anyway | Yes, out of the box |
|
||||
| Official, always up-to-date pre-built libraries | None | Yes, for many platforms. See below. |
|
||||
| Automatically downloads and verifies servers lists | No. Requires custom scripts, cron jobs and dependencies (minisign) | Yes, built-in, including signature verification |
|
||||
| Advanced expresions in blacklists (ads*.example[0-9]*.com) | No | Yes |
|
||||
| Features | dnscrypt-proxy 1.x | dnscrypt-proxy 2.x |
|
||||
| ----------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| Status | Old PoC, barely maintained any more | Very new, but quickly evolving |
|
||||
| Code quality | Big ugly mess | Readable, easy to work on |
|
||||
| Reliability | Poor, due to completely broken handling of edge cases | Excellent |
|
||||
| Security | Written in C, bundles patched versions from old branches of system libraries | Written in standard and portable Go |
|
||||
| Dependencies | Specific versions of dnscrypt-proxy, libldns and libtool | None |
|
||||
| Upstream connections using TCP | Catastrophic, requires client retries | Implemented as anyone would expect, works well with TOR |
|
||||
| XChaCha20 support | Only if compiled with recent versions of libsodium | Yes, always available |
|
||||
| Support of links with small MTU | Unreliable due to completely broken padding | Reliable, properly implemented |
|
||||
| Support for multiple servers | Nonexistent | Yes, with automatic failover and load-balancing |
|
||||
| Custom additions | C API, requires libldns for sanity | Simple Go structures using miekg/dns |
|
||||
| AAAA blocking for IPv4-only networks | Yes | Yes |
|
||||
| DNS caching | Yes, with ugly hacks for DNSSEC support | Yes, without ugly hacks |
|
||||
| EDNS support | Broken with custom records | Yes |
|
||||
| Asynchronous filters | Lol, no, filters block everything | Of course, thanks to Go |
|
||||
| Session-local storage for extensions | Impossible | Yes |
|
||||
| Multicore support | Nonexistent | Yes, thanks to Go |
|
||||
| Efficient padding of queries | Couldn't be any worse | Yes |
|
||||
| Multiple local sockets | Impossible | Of course. IPv4, IPv6, as many as you like |
|
||||
| Automatically picks the fastest servers | Lol, it supports only one at a time, anyway | Yes, out of the box |
|
||||
| Official, always up-to-date pre-built libraries | None | Yes, for many platforms. See below. |
|
||||
| Automatically downloads and verifies servers lists | No. Requires custom scripts, cron jobs and dependencies (minisign) | Yes, built-in, including signature verification |
|
||||
| Advanced expressions in blacklists (ads*.example[0-9]*.com) | No | Yes |
|
||||
| Forwarding with load balancing | No | Yes |
|
||||
|
||||
## Planned features
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ type Config struct {
|
|||
CacheMaxTTL uint32 `toml:"cache_max_ttl"`
|
||||
QueryLog QueryLogConfig `toml:"query_log"`
|
||||
BlockName BlockNameConfig `toml:"block_name"`
|
||||
ForwardFile string `toml:"forwarding_rules"`
|
||||
ServersConfig map[string]ServerConfig `toml:"servers"`
|
||||
SourcesConfig map[string]SourceConfig `toml:"sources"`
|
||||
}
|
||||
|
@ -104,6 +105,7 @@ func ConfigLoad(proxy *Proxy, config_file string) error {
|
|||
proxy.queryLogFile = config.QueryLog.File
|
||||
proxy.queryLogFormat = config.QueryLog.Format
|
||||
proxy.blockNameFile = config.BlockName.File
|
||||
proxy.forwardFile = config.ForwardFile
|
||||
if len(config.ServerNames) == 0 {
|
||||
for serverName := range config.ServersConfig {
|
||||
config.ServerNames = append(config.ServerNames, serverName)
|
||||
|
|
|
@ -48,28 +48,12 @@ cert_refresh_delay = 30
|
|||
block_ipv6 = false
|
||||
|
||||
|
||||
############## Query logging ##############
|
||||
############## Route queries for specific domains to a dedicated set of servers ##############
|
||||
# Example map entries (one entry per line):
|
||||
# example.com: 9.9.9.9
|
||||
# example.net: 9.9.9.9,8.8.8.8
|
||||
|
||||
## Log client queries to a file
|
||||
|
||||
[query_log]
|
||||
### Full path to the query log file
|
||||
# file = "query.log"
|
||||
|
||||
### Query log format (currently supported: tsv and ltsv)
|
||||
format = "tsv"
|
||||
|
||||
|
||||
############## Pattern-based blocking (blacklists) ##############
|
||||
# Blacklists are made of one pattern per line. Example of valid patterns:
|
||||
# example.com
|
||||
# *sex*
|
||||
# ads.*
|
||||
# ads*.example.*
|
||||
# ads*.example[0-9]*.com
|
||||
[block_name]
|
||||
## Full path to the file of blocking rules
|
||||
file = "blacklist.txt"
|
||||
forwarding_rules = "forwarding-rules.txt"
|
||||
|
||||
|
||||
############## DNS Cache ##############
|
||||
|
@ -99,6 +83,30 @@ cache_max_ttl = 86400
|
|||
cache_neg_ttl = 60
|
||||
|
||||
|
||||
############## Query logging ##############
|
||||
# Log client queries to a file
|
||||
|
||||
[query_log]
|
||||
### Full path to the query log file
|
||||
# file = "query.log"
|
||||
|
||||
### Query log format (currently supported: tsv and ltsv)
|
||||
format = "tsv"
|
||||
|
||||
|
||||
############## Pattern-based blocking (blacklists) ##############
|
||||
# Blacklists are made of one pattern per line. Example of valid patterns:
|
||||
# example.com
|
||||
# *sex*
|
||||
# ads.*
|
||||
# ads*.example.*
|
||||
# ads*.example[0-9]*.com
|
||||
|
||||
[block_name]
|
||||
## Full path to the file of blocking rules
|
||||
file = "blacklist.txt"
|
||||
|
||||
|
||||
############## Servers ##############
|
||||
|
||||
## Remote lists of available servers
|
||||
|
|
|
@ -31,6 +31,7 @@ type Proxy struct {
|
|||
queryLogFile string
|
||||
queryLogFormat string
|
||||
blockNameFile string
|
||||
forwardFile string
|
||||
pluginsGlobals PluginsGlobals
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -54,23 +53,22 @@ func (plugin *PluginBlockName) Init(proxy *Proxy) error {
|
|||
blockType := PluginBlockTypeNone
|
||||
if isGlobCandidate(line) {
|
||||
blockType = PluginBlockTypePattern
|
||||
fmt.Println(line)
|
||||
_, err := filepath.Match(line, "example.com")
|
||||
if len(line) < 2 || err != nil {
|
||||
dlog.Errorf("Syntax error in block rules at line %d", lineNo)
|
||||
dlog.Errorf("Syntax error in block rules at line %d", 1+lineNo)
|
||||
continue
|
||||
}
|
||||
} else if leadingStar && trailingStar {
|
||||
blockType = PluginBlockTypeSubstring
|
||||
if len(line) < 3 {
|
||||
dlog.Errorf("Syntax error in block rules at line %d", lineNo)
|
||||
dlog.Errorf("Syntax error in block rules at line %d", 1+lineNo)
|
||||
continue
|
||||
}
|
||||
line = line[1 : len(line)-1]
|
||||
} else if trailingStar {
|
||||
blockType = PluginBlockTypePrefix
|
||||
if len(line) < 2 {
|
||||
dlog.Errorf("Syntax error in block rules at line %d", lineNo)
|
||||
dlog.Errorf("Syntax error in block rules at line %d", 1+lineNo)
|
||||
continue
|
||||
}
|
||||
line = line[:len(line)-1]
|
||||
|
@ -84,7 +82,7 @@ func (plugin *PluginBlockName) Init(proxy *Proxy) error {
|
|||
}
|
||||
}
|
||||
if len(line) == 0 {
|
||||
dlog.Errorf("Syntax error in block rule at line %d", lineNo)
|
||||
dlog.Errorf("Syntax error in block rule at line %d", 1+lineNo)
|
||||
continue
|
||||
}
|
||||
line = strings.ToLower(line)
|
||||
|
|
105
dnscrypt-proxy/plugin_forward.go
Normal file
105
dnscrypt-proxy/plugin_forward.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/jedisct1/dlog"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type PluginForwardEntry struct {
|
||||
domain string
|
||||
servers []string
|
||||
}
|
||||
|
||||
type PluginForward struct {
|
||||
forwardMap []PluginForwardEntry
|
||||
}
|
||||
|
||||
func (plugin *PluginForward) Name() string {
|
||||
return "forward"
|
||||
}
|
||||
|
||||
func (plugin *PluginForward) Description() string {
|
||||
return "Route queries matching specific domains to a dedicated set of servers"
|
||||
}
|
||||
|
||||
func (plugin *PluginForward) Init(proxy *Proxy) error {
|
||||
dlog.Noticef("Loading the set of forwarding rules from [%s]", proxy.forwardFile)
|
||||
bin, err := ioutil.ReadFile(proxy.forwardFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for lineNo, line := range strings.Split(string(bin), "\n") {
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) == 0 || len(strings.Trim(parts[0], " \t\r")) == 0 {
|
||||
continue
|
||||
}
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("Syntax error for a forwarding rule at line %d. Expected syntax: example.com: 9.9.9.9,8.8.8.8", 1+lineNo)
|
||||
}
|
||||
domain := strings.ToLower(strings.Trim(parts[0], " \t\r"))
|
||||
serversStr := strings.Trim(parts[1], " \t\r")
|
||||
if len(domain) == 0 || len(serversStr) == 0 {
|
||||
continue
|
||||
}
|
||||
var servers []string
|
||||
for _, server := range strings.Split(serversStr, ",") {
|
||||
server = strings.Trim(server, " \t\r")
|
||||
if net.ParseIP(server) != nil {
|
||||
server = fmt.Sprintf("%s:%d", server, 53)
|
||||
}
|
||||
servers = append(servers, server)
|
||||
}
|
||||
if len(servers) == 0 {
|
||||
continue
|
||||
}
|
||||
plugin.forwardMap = append(plugin.forwardMap, PluginForwardEntry{
|
||||
domain: domain, servers: servers,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *PluginForward) Drop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *PluginForward) Reload() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *PluginForward) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
|
||||
questions := msg.Question
|
||||
if len(questions) != 1 {
|
||||
return nil
|
||||
}
|
||||
question := strings.ToLower(StripTrailingDot(questions[0].Name))
|
||||
questionLen := len(question)
|
||||
var servers []string
|
||||
for _, candidate := range plugin.forwardMap {
|
||||
candidateLen := len(candidate.domain)
|
||||
if candidateLen > questionLen {
|
||||
continue
|
||||
}
|
||||
if question[questionLen-candidateLen:] == candidate.domain && (candidateLen == questionLen || (question[questionLen-candidateLen] == '.')) {
|
||||
servers = candidate.servers
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(servers) == 0 {
|
||||
return nil
|
||||
}
|
||||
server := servers[rand.Intn(len(servers))]
|
||||
respMsg, err := dns.Exchange(msg, server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pluginsState.synthResponse = respMsg
|
||||
pluginsState.action = PluginsActionSynth
|
||||
return nil
|
||||
}
|
|
@ -55,7 +55,9 @@ func InitPluginsGlobals(pluginsGlobals *PluginsGlobals, proxy *Proxy) error {
|
|||
if proxy.cache {
|
||||
*queryPlugins = append(*queryPlugins, Plugin(new(PluginCache)))
|
||||
}
|
||||
|
||||
if len(proxy.forwardFile) != 0 {
|
||||
*queryPlugins = append(*queryPlugins, Plugin(new(PluginForward)))
|
||||
}
|
||||
responsePlugins := &[]Plugin{}
|
||||
if proxy.cache {
|
||||
*responsePlugins = append(*responsePlugins, Plugin(new(PluginCacheResponse)))
|
||||
|
|
|
@ -136,7 +136,7 @@ func (source *Source) Parse() ([]RegisteredServer, error) {
|
|||
continue
|
||||
}
|
||||
if len(record) < 14 {
|
||||
return registeredServers, fmt.Errorf("Parse error at line %d", lineNo)
|
||||
return registeredServers, fmt.Errorf("Parse error at line %d", 1+lineNo)
|
||||
}
|
||||
if lineNo == 0 {
|
||||
continue
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue