Skip to content

Commit d9b5625

Browse files
committed
IP blocking
1 parent 1c80e80 commit d9b5625

7 files changed

Lines changed: 223 additions & 5 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# ![dnscrypt-proxy 2](https://raw.github.com/jedisct1/dnscrypt-proxy/master/logo.png?2)
44

5-
A modern client implementation of the [DNSCrypt](https://github.com/DNSCrypt/dnscrypt-protocol/blob/master/DNSCRYPT-V2-PROTOCOL.txt) protocol.
5+
A flexible DNS proxy, with support for encrypted DNS protocols such as [DNSCrypt](https://github.com/DNSCrypt/dnscrypt-protocol/blob/master/DNSCRYPT-V2-PROTOCOL.txt).
66

77
## [dnscrypt-proxy 2.0.0beta6 is available for download!](https://github.com/jedisct1/dnscrypt-proxy/releases/latest)
88

dnscrypt-proxy/config.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type Config struct {
3131
QueryLog QueryLogConfig `toml:"query_log"`
3232
NxLog NxLogConfig `toml:"nx_log"`
3333
BlockName BlockNameConfig `toml:"blacklist"`
34+
BlockIP BlockIPConfig `toml:"ip_blacklist"`
3435
ForwardFile string `toml:"forwarding_rules"`
3536
ServersConfig map[string]ServerConfig `toml:"servers"`
3637
SourcesConfig map[string]SourceConfig `toml:"sources"`
@@ -94,6 +95,12 @@ type BlockNameConfig struct {
9495
Format string `toml:"log_format"`
9596
}
9697

98+
type BlockIPConfig struct {
99+
File string `toml:"blacklist_file"`
100+
LogFile string `toml:"log_file"`
101+
Format string `toml:"log_format"`
102+
}
103+
97104
func ConfigLoad(proxy *Proxy, svcFlag *string, config_file string) error {
98105
version := flag.Bool("version", false, "prints current proxy version")
99106
configFile := flag.String("config", "dnscrypt-proxy.toml", "path to the configuration file")
@@ -174,6 +181,18 @@ func ConfigLoad(proxy *Proxy, svcFlag *string, config_file string) error {
174181
proxy.blockNameFormat = config.BlockName.Format
175182
proxy.blockNameLogFile = config.BlockName.LogFile
176183

184+
if len(config.BlockIP.Format) == 0 {
185+
config.BlockIP.Format = "tsv"
186+
} else {
187+
config.BlockIP.Format = strings.ToLower(config.BlockIP.Format)
188+
}
189+
if config.BlockIP.Format != "tsv" && config.BlockIP.Format != "ltsv" {
190+
return errors.New("Unsupported IP block log format")
191+
}
192+
proxy.blockIPFile = config.BlockIP.File
193+
proxy.blockIPFormat = config.BlockIP.Format
194+
proxy.blockIPLogFile = config.BlockIP.LogFile
195+
177196
proxy.forwardFile = config.ForwardFile
178197

179198
requiredProps := ServerInformalProperties(0)

dnscrypt-proxy/dnscrypt-proxy.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,34 @@ format = 'tsv'
205205

206206

207207

208+
###########################################################
209+
# Pattern-based IP blocking (IP blacklists) #
210+
###########################################################
211+
212+
## IP blacklists are made of one pattern per line. Example of valid patterns:
213+
##
214+
## 127.*
215+
## fe80:abcd:*
216+
## 192.168.1.4
217+
218+
[ip_blacklist]
219+
220+
## Path to the file of blocking rules (absolute, or relative to the same directory as the executable file)
221+
222+
# blacklist_file = 'ip-blacklist.txt'
223+
224+
225+
## Optional path to a file logging blocked queries
226+
227+
# log_file = 'ip-blocked.log'
228+
229+
230+
## Optional log format: tsv or ltsv (default: tsv)
231+
232+
# log_format = 'tsv'
233+
234+
235+
208236
#########################
209237
# Servers #
210238
#########################

dnscrypt-proxy/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ type Proxy struct {
4444
blockNameFile string
4545
blockNameLogFile string
4646
blockNameFormat string
47+
blockIPFile string
48+
blockIPLogFile string
49+
blockIPFormat string
4750
forwardFile string
4851
pluginsGlobals PluginsGlobals
4952
urlsToPrefetch []URLToPrefetch

dnscrypt-proxy/plugin_block_ip.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io/ioutil"
7+
"net"
8+
"os"
9+
"strings"
10+
"sync"
11+
"time"
12+
"unicode"
13+
14+
"github.com/hashicorp/go-immutable-radix"
15+
"github.com/jedisct1/dlog"
16+
"github.com/miekg/dns"
17+
)
18+
19+
type PluginBlockIP struct {
20+
sync.Mutex
21+
blockedPrefixes *iradix.Tree
22+
blockedIPs map[string]interface{}
23+
outFd *os.File
24+
format string
25+
}
26+
27+
func (plugin *PluginBlockIP) Name() string {
28+
return "block_ip"
29+
}
30+
31+
func (plugin *PluginBlockIP) Description() string {
32+
return "Block responses containing specific IP addresses"
33+
}
34+
35+
func (plugin *PluginBlockIP) Init(proxy *Proxy) error {
36+
dlog.Noticef("Loading the set of IP blocking rules from [%s]", proxy.blockIPFile)
37+
bin, err := ioutil.ReadFile(proxy.blockIPFile)
38+
if err != nil {
39+
return err
40+
}
41+
plugin.blockedPrefixes = iradix.New()
42+
plugin.blockedIPs = make(map[string]interface{})
43+
for lineNo, line := range strings.Split(string(bin), "\n") {
44+
line = strings.TrimFunc(line, unicode.IsSpace)
45+
if len(line) == 0 || strings.HasPrefix(line, "#") {
46+
continue
47+
}
48+
ip := net.ParseIP(line)
49+
trailingStar := strings.HasSuffix(line, "*")
50+
if len(line) < 2 || (ip != nil && trailingStar) {
51+
dlog.Errorf("Suspicious IP blocking rule [%s] at line %d", line, lineNo)
52+
continue
53+
}
54+
if trailingStar {
55+
line = line[:len(line)-1]
56+
}
57+
if strings.HasSuffix(line, ":") || strings.HasSuffix(line, ".") {
58+
line = line[:len(line)-1]
59+
}
60+
if len(line) == 0 {
61+
dlog.Errorf("Empty IP blocking rule at line %d", lineNo)
62+
continue
63+
}
64+
if strings.Contains(line, "*") {
65+
dlog.Errorf("Invalid rule: [%s] - wildcards can only be used as a suffix at line %d", line, lineNo)
66+
continue
67+
}
68+
line = strings.ToLower(line)
69+
if trailingStar {
70+
plugin.blockedPrefixes, _, _ = plugin.blockedPrefixes.Insert([]byte(line), 0)
71+
} else {
72+
plugin.blockedIPs[line] = true
73+
}
74+
}
75+
if len(proxy.blockIPLogFile) == 0 {
76+
return nil
77+
}
78+
plugin.Lock()
79+
defer plugin.Unlock()
80+
outFd, err := os.OpenFile(proxy.blockIPLogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
81+
if err != nil {
82+
return err
83+
}
84+
plugin.outFd = outFd
85+
plugin.format = proxy.blockIPFormat
86+
87+
return nil
88+
}
89+
90+
func (plugin *PluginBlockIP) Drop() error {
91+
return nil
92+
}
93+
94+
func (plugin *PluginBlockIP) Reload() error {
95+
return nil
96+
}
97+
98+
func (plugin *PluginBlockIP) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
99+
answers := msg.Answer
100+
if len(answers) == 0 {
101+
return nil
102+
}
103+
reject, reason, ipStr := false, "", ""
104+
for _, answer := range answers {
105+
header := answer.Header()
106+
Rrtype := header.Rrtype
107+
if header.Class != dns.ClassINET || (Rrtype != dns.TypeA && Rrtype != dns.TypeAAAA) {
108+
continue
109+
}
110+
if Rrtype == dns.TypeA {
111+
ipStr = answer.(*dns.A).A.String()
112+
} else if Rrtype == dns.TypeAAAA {
113+
ipStr = answer.(*dns.AAAA).AAAA.String() // IPv4-mapped IPv6 addresses are converted to IPv4
114+
}
115+
if _, found := plugin.blockedIPs[ipStr]; found {
116+
reject, reason = true, ipStr
117+
break
118+
}
119+
match, _, found := plugin.blockedPrefixes.Root().LongestPrefix([]byte(ipStr))
120+
if found {
121+
if len(match) == len(ipStr) || (ipStr[len(match)] == '.' || ipStr[len(match)] == ':') {
122+
reject, reason = true, string(match)+"*"
123+
break
124+
}
125+
}
126+
}
127+
if reject {
128+
pluginsState.action = PluginsActionReject
129+
if plugin.outFd != nil {
130+
questions := msg.Question
131+
if len(questions) != 1 {
132+
return nil
133+
}
134+
qName := strings.ToLower(StripTrailingDot(questions[0].Name))
135+
if len(qName) < 2 {
136+
return nil
137+
}
138+
var clientIPStr string
139+
if pluginsState.clientProto == "udp" {
140+
clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
141+
} else {
142+
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
143+
}
144+
var line string
145+
if plugin.format == "tsv" {
146+
now := time.Now()
147+
year, month, day := now.Date()
148+
hour, minute, second := now.Clock()
149+
tsStr := fmt.Sprintf("[%d-%02d-%02d %02d:%02d:%02d]", year, int(month), day, hour, minute, second)
150+
line = fmt.Sprintf("%s\t%s\t%s\t%s\t%s\n", tsStr, clientIPStr, StringQuote(qName), StringQuote(ipStr), StringQuote(reason))
151+
} else if plugin.format == "ltsv" {
152+
line = fmt.Sprintf("time:%d\thost:%s\tqname:%s\tip:%s\tmessage:%s\n", time.Now().Unix(), clientIPStr, StringQuote(qName), StringQuote(ipStr), StringQuote(reason))
153+
} else {
154+
dlog.Fatalf("Unexpected log format: [%s]", plugin.format)
155+
}
156+
plugin.Lock()
157+
if plugin.outFd == nil {
158+
return errors.New("Log file not initialized")
159+
}
160+
plugin.outFd.WriteString(line)
161+
defer plugin.Unlock()
162+
}
163+
}
164+
return nil
165+
}

dnscrypt-proxy/plugin_block_name.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ func (plugin *PluginBlockName) Init(proxy *Proxy) error {
118118
}
119119
plugin.outFd = outFd
120120
plugin.format = proxy.blockNameFormat
121+
121122
return nil
122123
}
123124

dnscrypt-proxy/plugins.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,15 @@ func InitPluginsGlobals(pluginsGlobals *PluginsGlobals, proxy *Proxy) error {
6060
}
6161

6262
responsePlugins := &[]Plugin{}
63-
if proxy.cache {
64-
*responsePlugins = append(*responsePlugins, Plugin(new(PluginCacheResponse)))
65-
}
6663
if len(proxy.nxLogFile) != 0 {
6764
*responsePlugins = append(*responsePlugins, Plugin(new(PluginNxLog)))
6865
}
69-
66+
if len(proxy.blockIPFile) != 0 {
67+
*responsePlugins = append(*responsePlugins, Plugin(new(PluginBlockIP)))
68+
}
69+
if proxy.cache {
70+
*responsePlugins = append(*responsePlugins, Plugin(new(PluginCacheResponse)))
71+
}
7072
for _, plugin := range *queryPlugins {
7173
if err := plugin.Init(proxy); err != nil {
7274
return err

0 commit comments

Comments
 (0)