1
|
|
package config
|
2
|
|
|
3
|
|
import (
|
4
|
|
"crypto/rand"
|
5
|
|
"encoding/binary"
|
6
|
|
"fmt"
|
7
|
|
"io"
|
8
|
|
"os"
|
9
|
|
"path"
|
10
|
|
"path/filepath"
|
11
|
|
"strings"
|
12
|
|
"time"
|
13
|
|
|
14
|
|
"github.com/BurntSushi/toml"
|
15
|
|
"github.com/semihalev/log"
|
16
|
|
)
|
17
|
|
|
18
|
|
const configver = "1.1.0"
|
19
|
|
|
20
|
|
// Config type
|
21
|
|
type Config struct {
|
22
|
|
Version string
|
23
|
|
BlockLists []string
|
24
|
|
BlockListDir string
|
25
|
|
RootServers []string
|
26
|
|
Root6Servers []string
|
27
|
|
RootKeys []string
|
28
|
|
FallbackServers []string
|
29
|
|
AccessList []string
|
30
|
|
LogLevel string
|
31
|
|
AccessLog string
|
32
|
|
Bind string
|
33
|
|
BindTLS string
|
34
|
|
BindDOH string
|
35
|
|
TLSCertificate string
|
36
|
|
TLSPrivateKey string
|
37
|
|
API string
|
38
|
|
Nullroute string
|
39
|
|
Nullroutev6 string
|
40
|
|
Hostsfile string
|
41
|
|
OutboundIPs []string
|
42
|
|
OutboundIP6s []string
|
43
|
|
Timeout Duration
|
44
|
|
Expire uint32
|
45
|
|
CacheSize int
|
46
|
|
Maxdepth int
|
47
|
|
RateLimit int
|
48
|
|
ClientRateLimit int
|
49
|
|
CookieSecret string
|
50
|
|
NSID string
|
51
|
|
Blocklist []string
|
52
|
|
Whitelist []string
|
53
|
|
Chaos bool
|
54
|
|
QnameMinLevel int `toml:"qname_min_level"`
|
55
|
|
EmptyZones []string
|
56
|
|
|
57
|
|
Plugins map[string]Plugin
|
58
|
|
|
59
|
|
sVersion string
|
60
|
|
}
|
61
|
|
|
62
|
|
// Plugin type
|
63
|
|
type Plugin struct {
|
64
|
|
Path string
|
65
|
|
Config map[string]interface{}
|
66
|
|
}
|
67
|
|
|
68
|
|
// ServerVersion return current server version
|
69
|
|
func (c *Config) ServerVersion() string {
|
70
|
0
|
return c.sVersion
|
71
|
|
}
|
72
|
|
|
73
|
|
// Duration type
|
74
|
|
type Duration struct {
|
75
|
|
time.Duration
|
76
|
|
}
|
77
|
|
|
78
|
|
// UnmarshalText for duration type
|
79
|
|
func (d *Duration) UnmarshalText(text []byte) error {
|
80
|
2
|
var err error
|
81
|
2
|
d.Duration, err = time.ParseDuration(string(text))
|
82
|
2
|
return err
|
83
|
|
}
|
84
|
|
|
85
|
|
var defaultConfig = `
|
86
|
|
# Config version, config and build versions can be different.
|
87
|
|
version = "%s"
|
88
|
|
|
89
|
|
# Address to bind to for the DNS server
|
90
|
|
bind = ":53"
|
91
|
|
|
92
|
|
# Address to bind to for the DNS-over-TLS server
|
93
|
|
# bindtls = ":853"
|
94
|
|
|
95
|
|
# Address to bind to for the DNS-over-HTTPS server
|
96
|
|
# binddoh = ":8053"
|
97
|
|
|
98
|
|
# TLS certificate file
|
99
|
|
# tlscertificate = "server.crt"
|
100
|
|
|
101
|
|
# TLS private key file
|
102
|
|
# tlsprivatekey = "server.key"
|
103
|
|
|
104
|
|
# Outbound ipv4 addresses, if you set multiple, sdns can use random outbound ipv4 address by request based
|
105
|
|
outboundips = [
|
106
|
|
]
|
107
|
|
|
108
|
|
# Outbound ipv6 addresses, if you set multiple, sdns can use random outbound ipv6 address by request based
|
109
|
|
outboundip6s = [
|
110
|
|
]
|
111
|
|
|
112
|
|
# Root zone ipv4 servers
|
113
|
|
rootservers = [
|
114
|
|
"192.5.5.241:53",
|
115
|
|
"198.41.0.4:53",
|
116
|
|
"192.228.79.201:53",
|
117
|
|
"192.33.4.12:53",
|
118
|
|
"199.7.91.13:53",
|
119
|
|
"192.203.230.10:53",
|
120
|
|
"192.112.36.4:53",
|
121
|
|
"128.63.2.53:53",
|
122
|
|
"192.36.148.17:53",
|
123
|
|
"192.58.128.30:53",
|
124
|
|
"193.0.14.129:53",
|
125
|
|
"199.7.83.42:53",
|
126
|
|
"202.12.27.33:53"
|
127
|
|
]
|
128
|
|
|
129
|
|
# Root zone ipv6 servers
|
130
|
|
root6servers = [
|
131
|
|
"[2001:500:2f::f]:53",
|
132
|
|
"[2001:503:ba3e::2:30]:53",
|
133
|
|
"[2001:500:200::b]:53",
|
134
|
|
"[2001:500:2::c]:53",
|
135
|
|
"[2001:500:2d::d]:53",
|
136
|
|
"[2001:500:a8::e]:53",
|
137
|
|
"[2001:500:12::d0d]:53",
|
138
|
|
"[2001:500:1::53]:53",
|
139
|
|
"[2001:7fe::53]:53",
|
140
|
|
"[2001:503:c27::2:30]:53",
|
141
|
|
"[2001:7fd::1]:53",
|
142
|
|
"[2001:500:9f::42]:53",
|
143
|
|
"[2001:dc3::35]:53"
|
144
|
|
]
|
145
|
|
|
146
|
|
# Trusted anchors for dnssec
|
147
|
|
rootkeys = [
|
148
|
|
". 172800 IN DNSKEY 257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU="
|
149
|
|
]
|
150
|
|
|
151
|
|
# Failover resolver ipv4 or ipv6 addresses with port, left blank for disabled. Example: "1.1.1.1:53"
|
152
|
|
# fallbackservers = [
|
153
|
|
# "8.8.8.8:53",
|
154
|
|
# "8.8.4.4:53"
|
155
|
|
# ]
|
156
|
|
fallbackservers = [
|
157
|
|
]
|
158
|
|
|
159
|
|
# Address to bind to for the http API server, left blank for disabled
|
160
|
|
api = "127.0.0.1:8080"
|
161
|
|
|
162
|
|
# What kind of information should be logged, Log verbosity level [crit,error,warn,info,debug]
|
163
|
|
loglevel = "info"
|
164
|
|
|
165
|
|
# The location of access log file, left blank for disabled. SDNS uses Common Log Format by default.
|
166
|
|
# accesslog = ""
|
167
|
|
|
168
|
|
# List of remote blocklists address list. All lists will be download to blocklist folder.
|
169
|
|
# blocklists = [
|
170
|
|
# "http://mirror1.malwaredomains.com/files/justdomains",
|
171
|
|
# "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
|
172
|
|
# "http://sysctl.org/cameleon/hosts",
|
173
|
|
# "https://zeustracker.abuse.ch/blocklist.php?download=domainblocklist",
|
174
|
|
# "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
|
175
|
|
# "https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
|
176
|
|
# "https://raw.githubusercontent.com/quidsup/notrack/master/trackers.txt"
|
177
|
|
# ]
|
178
|
|
blocklists = [
|
179
|
|
]
|
180
|
|
|
181
|
|
# List of locations to recursively read blocklists from (warning, every file found is assumed to be a hosts-file or domain list)
|
182
|
|
blocklistdir = "bl"
|
183
|
|
|
184
|
|
# IPv4 address to forward blocked queries to
|
185
|
|
nullroute = "0.0.0.0"
|
186
|
|
|
187
|
|
# IPv6 address to forward blocked queries to
|
188
|
|
nullroutev6 = "::0"
|
189
|
|
|
190
|
|
# Which clients allowed to make queries
|
191
|
|
accesslist = [
|
192
|
|
"0.0.0.0/0",
|
193
|
|
"::0/0"
|
194
|
|
]
|
195
|
|
|
196
|
|
# Enables serving zone data from a hosts file, left blank for disabled
|
197
|
|
# the form of the entries in the /etc/hosts file are based on IETF RFC 952 which was updated by IETF RFC 1123.
|
198
|
|
hostsfile = ""
|
199
|
|
|
200
|
|
# Network timeout for each dns lookups in duration
|
201
|
|
timeout = "3s"
|
202
|
|
|
203
|
|
# Default error cache TTL in seconds
|
204
|
|
expire = 600
|
205
|
|
|
206
|
|
# Cache size (total records in cache)
|
207
|
|
cachesize = 256000
|
208
|
|
|
209
|
|
# Maximum iteration depth for a query
|
210
|
|
maxdepth = 30
|
211
|
|
|
212
|
|
# Query based ratelimit per second, 0 for disabled
|
213
|
|
ratelimit = 0
|
214
|
|
|
215
|
|
# Client ip address based ratelimit per minute, 0 for disabled
|
216
|
|
clientratelimit = 0
|
217
|
|
|
218
|
|
# Manual blocklist entries
|
219
|
|
blocklist = []
|
220
|
|
|
221
|
|
# Manual whitelist entries
|
222
|
|
whitelist = []
|
223
|
|
|
224
|
|
# DNS server identifier (RFC 5001), it's useful while operating multiple sdns. left blank for disabled
|
225
|
|
nsid = ""
|
226
|
|
|
227
|
|
# Enable to answer version.server, version.bind, hostname.bind, id.server chaos queries.
|
228
|
|
chaos = true
|
229
|
|
|
230
|
|
# Qname minimization level. If higher, it can be more complex and impact the response performance.
|
231
|
|
# If set 0, qname minimization will be disable
|
232
|
|
qname_min_level = 5
|
233
|
|
|
234
|
|
# Empty zones return answer for RFC 1918 zones. Please see http://as112.net/
|
235
|
|
# for details of the problems you are causing and the counter measures that have had to be deployed.
|
236
|
|
# If the list empty, SDNS will be use default zones described at RFC.
|
237
|
|
# emptyzones [
|
238
|
|
# "10.in-addr.arpa."
|
239
|
|
# ]
|
240
|
|
emptyzones = []
|
241
|
|
|
242
|
|
# You can add your own plugins to sdns. The plugin order is very important.
|
243
|
|
# Plugins can be load before cache middleware.
|
244
|
|
# Config keys should be string and values can be anything.
|
245
|
|
# There is an example plugin at https://github.com/semihalev/sdnsexampleplugin
|
246
|
|
# [plugins]
|
247
|
|
# [plugins.example]
|
248
|
|
# path = "exampleplugin.so"
|
249
|
|
# config = {key_1 = "value_1", key_2 = 2, key_3 = true}
|
250
|
|
`
|
251
|
|
|
252
|
|
// Load loads the given config file
|
253
|
|
func Load(cfgfile, version string) (*Config, error) {
|
254
|
2
|
config := new(Config)
|
255
|
|
|
256
|
2
|
if _, err := os.Stat(cfgfile); os.IsNotExist(err) {
|
257
|
2
|
if path.Base(cfgfile) == "sdns.conf" {
|
258
|
|
// compatibility for old default conf file
|
259
|
0
|
if _, err := os.Stat("sdns.toml"); os.IsNotExist(err) {
|
260
|
0
|
if err := generateConfig(cfgfile); err != nil {
|
261
|
0
|
return nil, err
|
262
|
|
}
|
263
|
0
|
} else {
|
264
|
0
|
cfgfile = "sdns.toml"
|
265
|
|
}
|
266
|
|
}
|
267
|
|
}
|
268
|
|
|
269
|
2
|
log.Info("Loading config file", "path", cfgfile)
|
270
|
|
|
271
|
2
|
if _, err := toml.DecodeFile(cfgfile, config); err != nil {
|
272
|
2
|
return nil, fmt.Errorf("could not load config: %s", err)
|
273
|
|
}
|
274
|
|
|
275
|
2
|
if config.Version != configver {
|
276
|
0
|
log.Warn("Config file is out of version, you can generate new one and check the changes.")
|
277
|
|
}
|
278
|
|
|
279
|
2
|
config.sVersion = version
|
280
|
|
|
281
|
2
|
if config.CookieSecret == "" {
|
282
|
2
|
var v uint64
|
283
|
|
|
284
|
2
|
err := binary.Read(rand.Reader, binary.BigEndian, &v)
|
285
|
2
|
if err != nil {
|
286
|
0
|
return nil, err
|
287
|
|
}
|
288
|
|
|
289
|
2
|
config.CookieSecret = fmt.Sprintf("%16x", v)
|
290
|
|
}
|
291
|
|
|
292
|
2
|
return config, nil
|
293
|
|
}
|
294
|
|
|
295
|
|
func generateConfig(path string) error {
|
296
|
2
|
output, err := os.Create(path)
|
297
|
2
|
if err != nil {
|
298
|
0
|
return fmt.Errorf("could not generate config: %s", err)
|
299
|
|
}
|
300
|
|
|
301
|
2
|
defer func() {
|
302
|
2
|
err := output.Close()
|
303
|
2
|
if err != nil {
|
304
|
0
|
log.Warn("Config generation failed while file closing", "error", err.Error())
|
305
|
|
}
|
306
|
|
}()
|
307
|
|
|
308
|
2
|
r := strings.NewReader(fmt.Sprintf(defaultConfig, configver))
|
309
|
2
|
if _, err := io.Copy(output, r); err != nil {
|
310
|
0
|
return fmt.Errorf("could not copy default config: %s", err)
|
311
|
|
}
|
312
|
|
|
313
|
2
|
if abs, err := filepath.Abs(path); err == nil {
|
314
|
2
|
log.Info("Default config file generated", "config", abs)
|
315
|
|
}
|
316
|
|
|
317
|
2
|
return nil
|
318
|
|
}
|