dns_one 0.5.5 → 0.5.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/dns_one.gemspec +1 -1
- data/lib/dns_one.rb +41 -56
- data/lib/dns_one/backend/db.rb +3 -3
- data/lib/dns_one/backend/file.rb +1 -1
- data/lib/dns_one/backend/http_bell.rb +10 -10
- data/lib/dns_one/cli.rb +1 -3
- data/lib/dns_one/global.rb +3 -0
- data/lib/dns_one/req_log/account.rb +85 -0
- data/lib/dns_one/{stat.rb → req_log/db.rb} +9 -8
- data/lib/dns_one/req_log/file.rb +28 -0
- data/lib/dns_one/req_log/req_log.rb +25 -0
- data/lib/dns_one/server.rb +56 -142
- data/lib/dns_one/util.rb +28 -20
- data/lib/dns_one/version.rb +1 -1
- data/lib/dns_one/zone_search.rb +19 -19
- data/util/dns_one.service +0 -4
- data/util/sample_conf.yml +8 -9
- metadata +10 -7
- data/lib/dns_one/log.rb +0 -72
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1712bca4a25d8e72f60b38c83c121087245f583b
|
4
|
+
data.tar.gz: 6f6d4c432b13d708273643c7abed58b2c7b8da04
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54a23b46cfba3138ad0c76084f71705a5f8194a1d4c5134a0c623117d52a729bfdb404ec73477014fb5deec93c21893a98b7e4bcee810075c52baa40d6ce9a56
|
7
|
+
data.tar.gz: 55a248615c9999cd3a63aa04288af1b32514867971ade91ac2cd555d49053c7d5c8b5049bfa0dab889967973d5ba6bb8b2914253ce41b1c6d61090a738f5d285
|
data/dns_one.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency "rspec", "~> 3.0"
|
27
27
|
|
28
28
|
spec.add_runtime_dependency "thor", '~> 0.19'
|
29
|
-
spec.add_runtime_dependency "rubydns", '~>
|
29
|
+
spec.add_runtime_dependency "rubydns", '~> 2.0.1'
|
30
30
|
spec.add_runtime_dependency "rexec", '~> 1.6'
|
31
31
|
spec.add_runtime_dependency "pg", '~> 0.21'
|
32
32
|
spec.add_runtime_dependency "activerecord", '~> 5.0'
|
data/lib/dns_one.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
1
|
# Core
|
2
|
-
require 'syslog'
|
3
|
-
require 'syslog/logger'
|
4
2
|
require 'ostruct'
|
5
3
|
require 'singleton'
|
6
4
|
require 'fileutils'
|
@@ -20,76 +18,63 @@ require "dns_one/core_ext/string"
|
|
20
18
|
require "dns_one/core_ext/blank"
|
21
19
|
require "dns_one/core_ext/hash"
|
22
20
|
|
23
|
-
require "dns_one/
|
21
|
+
require "dns_one/global"
|
24
22
|
require "dns_one/util"
|
25
23
|
require "dns_one/server"
|
26
|
-
require "dns_one/
|
24
|
+
require "dns_one/req_log/db"
|
27
25
|
|
28
26
|
module DnsOne; class DnsOne
|
29
27
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
Log.w "Cannot change working dir to #{WORK_DIR}. Will continue in #{Dir.pwd}."
|
47
|
-
end
|
28
|
+
DEFAULTS = {
|
29
|
+
conf_file: "/etc/dns_one.yml",
|
30
|
+
work_dir: "/var/local/dns_one",
|
31
|
+
log_file: "/var/log/dns_one/dns_one.log",
|
32
|
+
rubydns_log_file: "/var/log/dns_one/dns_one_rubydns.log",
|
33
|
+
run_as: "dnsone",
|
34
|
+
interfaces: [ [:udp, "0.0.0.0", 53],
|
35
|
+
[:tcp, "0.0.0.0", 53],
|
36
|
+
[:udp, "::", 5300],
|
37
|
+
[:tcp, "::", 5300]
|
38
|
+
],
|
39
|
+
log_req_socket_file: '/tmp/dns_one_log_result.sock'
|
40
|
+
}
|
41
|
+
|
42
|
+
def initialize conf_file: nil
|
43
|
+
@conf = Global.conf = load_conf(conf_file || DEFAULTS[:conf_file])
|
48
44
|
end
|
49
|
-
|
45
|
+
|
50
46
|
def start
|
51
|
-
|
47
|
+
init_loggers
|
48
|
+
chdir
|
49
|
+
Server.new.run
|
52
50
|
end
|
53
51
|
|
54
52
|
private
|
55
|
-
|
56
|
-
def parse_conf conf_file
|
57
|
-
check_conf_file conf_file
|
58
53
|
|
59
|
-
|
60
|
-
conf =
|
54
|
+
def load_conf conf_file
|
55
|
+
conf = DEFAULTS.clone
|
56
|
+
conf.merge! YAML.load_file(conf_file).symbolize_keys
|
57
|
+
Util.hash_to_ostruct_deep conf
|
61
58
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
server: {
|
67
|
-
run_as: conf[:config][:run_as],
|
68
|
-
save_stats: conf[:config][:save_stats],
|
69
|
-
log_result_file: conf[:config][:log_result_file],
|
70
|
-
log_result_socket: conf[:config][:log_result_socket],
|
71
|
-
log_result_socket_file: conf[:config][:log_result_socket_file],
|
72
|
-
},
|
73
|
-
zone_search: {
|
74
|
-
ignore_subdomains: conf[:config][:ignore_subdomains],
|
75
|
-
cache_max: conf[:config][:cache_max],
|
76
|
-
record_sets: conf[:record_sets],
|
77
|
-
backend: conf[:backend]
|
78
|
-
}
|
79
|
-
)
|
59
|
+
rescue => e
|
60
|
+
$stderr.puts e.desc
|
61
|
+
$stderr.puts "Error opening conf file #{conf_file}. Aborting."
|
62
|
+
exit 1
|
80
63
|
end
|
81
64
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
|
65
|
+
def init_loggers
|
66
|
+
Global.logger = Util.init_logger @conf.log_file , Logger::INFO
|
67
|
+
Global.ruby_dns_logger = Util.init_logger @conf.ruby_dns_logger, Logger::WARN
|
68
|
+
end
|
86
69
|
|
87
|
-
|
70
|
+
def chdir
|
71
|
+
d = @conf.work_dir
|
72
|
+
FileUtils.mkdir_p d
|
73
|
+
Dir.chdir d
|
88
74
|
|
89
|
-
|
90
|
-
|
91
|
-
|
75
|
+
rescue => e
|
76
|
+
Global.logger.error e.desc
|
77
|
+
Global.logger.error "Cannot chdir to #{@conf.work_dir}. Will continue in #{Dir.pwd}"
|
92
78
|
end
|
93
79
|
|
94
80
|
end; end
|
95
|
-
|
data/lib/dns_one/backend/db.rb
CHANGED
@@ -31,12 +31,12 @@ module DnsOne; module Backend; class DB < Base
|
|
31
31
|
record_values&.first
|
32
32
|
|
33
33
|
rescue ActiveRecord::StatementInvalid => e
|
34
|
-
|
34
|
+
Global.logger.error "SQL query error. Trying to reconnect #{tries}. Details:\n#{e.desc}"
|
35
35
|
# http://geoff.evason.name/2015/01/18/postgres-ssl-connection-has-been-closed-unexpectedly
|
36
36
|
ActiveRecord::Base.connection.reconnect!
|
37
37
|
find sql, (tries+1)
|
38
38
|
rescue => e
|
39
|
-
|
39
|
+
Global.logger.error "SQL query error. Details:\n#{e.desc}"
|
40
40
|
end
|
41
41
|
|
42
42
|
def build_query dom_name
|
@@ -46,7 +46,7 @@ module DnsOne; module Backend; class DB < Base
|
|
46
46
|
|
47
47
|
def setup_db
|
48
48
|
#require_deps
|
49
|
-
ActiveRecord::Base.logger =
|
49
|
+
ActiveRecord::Base.logger = Global.logger
|
50
50
|
ActiveRecord::Base.establish_connection @conf
|
51
51
|
end
|
52
52
|
|
data/lib/dns_one/backend/file.rb
CHANGED
@@ -24,7 +24,7 @@ module DnsOne; module Backend; class File < Base
|
|
24
24
|
if domain_name and not domain_name.empty?
|
25
25
|
@domain_map[domain_name.strip.downcase] = rec_set_name&.strip || ''
|
26
26
|
else
|
27
|
-
|
27
|
+
Global.logger.warn "Ignoring #{file} line: #{line}"
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
@@ -52,7 +52,7 @@ module DnsOne; module Backend; class HTTPBell < Base
|
|
52
52
|
.map{ |r|
|
53
53
|
id, domain = r.strip.split /\s+/
|
54
54
|
if id !~ /^\d+$/ || domain !~ Util::DOM_REGEX
|
55
|
-
|
55
|
+
Global.logger.warn "invalid line '#{r}'"
|
56
56
|
nil
|
57
57
|
else
|
58
58
|
[id.to_i, domain.downcase]
|
@@ -66,17 +66,17 @@ module DnsOne; module Backend; class HTTPBell < Base
|
|
66
66
|
case point
|
67
67
|
when :start
|
68
68
|
@log_update_t0 = Time.now
|
69
|
-
|
69
|
+
Global.logger.info "update`ing..."
|
70
70
|
when :end
|
71
71
|
dt = '%.2f' % (Time.now - @log_update_t0)
|
72
72
|
dots = '...' if recs.size > LOG_DOM_NUM
|
73
73
|
zones = recs[0, LOG_DOM_NUM].map(&:last).join(', ')
|
74
|
-
|
74
|
+
Global.logger.info "#{recs.size} zone(s) added in #{dt}s: #{zones}#{dots}"
|
75
75
|
else
|
76
|
-
|
76
|
+
Global.logger.error e.desc "Wrong param #{point} for log_update"
|
77
77
|
end
|
78
78
|
rescue => e
|
79
|
-
|
79
|
+
Global.logger.error e.desc
|
80
80
|
end
|
81
81
|
|
82
82
|
def listen_updater_bell
|
@@ -86,19 +86,19 @@ module DnsOne; module Backend; class HTTPBell < Base
|
|
86
86
|
require "socket"
|
87
87
|
dts = TCPServer.new '0.0.0.0', @conf[:http_bell_port]
|
88
88
|
allow_ips = @conf[:http_bell_allow_ips]
|
89
|
-
|
89
|
+
Global.logger.info 'Starting bell listener...'
|
90
90
|
Thread.new do
|
91
91
|
loop do
|
92
92
|
Thread.start(dts.accept) do |client|
|
93
|
-
|
93
|
+
Global.logger.info 'accepted'
|
94
94
|
numeric_address = client.peeraddr[3]
|
95
95
|
if !allow_ips || allow_ips.include?(numeric_address)
|
96
|
-
|
96
|
+
Global.logger.info 'will update'
|
97
97
|
update
|
98
98
|
else
|
99
|
-
|
99
|
+
Global.logger.warn "Ignoring bell ring from #{numeric_address}."
|
100
100
|
end
|
101
|
-
|
101
|
+
Global.logger.info 'closing connection'
|
102
102
|
client.close
|
103
103
|
end
|
104
104
|
end
|
data/lib/dns_one/cli.rb
CHANGED
@@ -0,0 +1,85 @@
|
|
1
|
+
module DnsOne; module Backend; class Account
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
@conf = Global.conf
|
5
|
+
@stat = {}
|
6
|
+
@last_stat = nil
|
7
|
+
@mutex = Mutex.new
|
8
|
+
open_socket
|
9
|
+
reap
|
10
|
+
end
|
11
|
+
|
12
|
+
def on_response ip_address, domain_name, res_class, rcode, resp_log, from_cache
|
13
|
+
@mutex.synchronize {
|
14
|
+
@stat[:requests] ||= 0
|
15
|
+
@stat[:requests] += 1
|
16
|
+
|
17
|
+
@stat[:cache] ||= 0
|
18
|
+
@stat[:cache] += 1 if from_cache
|
19
|
+
|
20
|
+
rcode_uc = Util.const_underscore rcode
|
21
|
+
@stat[:rcode] ||= {}
|
22
|
+
@stat[:rcode][rcode_uc] ||= 0
|
23
|
+
@stat[:rcode][rcode_uc] += 1
|
24
|
+
|
25
|
+
req_resource = Util.last_mod(res_class).downcase
|
26
|
+
@stat[:req_resource] ||= {}
|
27
|
+
@stat[:req_resource][req_resource] ||= 0
|
28
|
+
@stat[:req_resource][req_resource] += 1
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def reap
|
33
|
+
Thread.new do
|
34
|
+
loop do
|
35
|
+
sleep (300 - Time.now.to_f % 300)
|
36
|
+
@mutex.synchronize {
|
37
|
+
@last_stat = @stat.deep_dup
|
38
|
+
reset @stat
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def open_socket
|
45
|
+
conf = @conf
|
46
|
+
mutex = @mutex
|
47
|
+
stat = @stat
|
48
|
+
sock = Thread.new do
|
49
|
+
sleep 1
|
50
|
+
begin
|
51
|
+
Socket.unix_server_loop(Global.conf.log_req_socket_file) do |sock, addr|
|
52
|
+
Thread.new do
|
53
|
+
loop do
|
54
|
+
begin
|
55
|
+
mutex.synchronize {
|
56
|
+
sock.write "#{ last_stat.to_json }\n"
|
57
|
+
}
|
58
|
+
rescue Errno::EPIPE => e
|
59
|
+
break
|
60
|
+
rescue => e
|
61
|
+
Global.logger.error e.desc
|
62
|
+
break
|
63
|
+
end
|
64
|
+
Thread.pass
|
65
|
+
sleep 0.1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
rescue => e
|
70
|
+
Global.logger.error e.desc
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def reset hash
|
76
|
+
hash.each_key do |k|
|
77
|
+
if hash[k].is_a? Hash
|
78
|
+
reset hash[k]
|
79
|
+
else
|
80
|
+
hash[k] = 0
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
module DnsOne; class
|
1
|
+
module DnsOne; module Backend; class DB
|
2
|
+
|
2
3
|
DB_FNAME = "stat.db"
|
3
4
|
META_STAT_ON = false
|
4
5
|
META_STAT_FILE = '/tmp/dnsone_sql_prof.log'
|
@@ -12,19 +13,19 @@ module DnsOne; class Stat
|
|
12
13
|
ensure_db
|
13
14
|
end
|
14
15
|
|
15
|
-
def
|
16
|
-
|
16
|
+
def on_response ip_address, domain_name, res_class, rcode, resp_log, from_cache
|
17
|
+
Global.logger.debug "saving stat (user: #{ `id -un #{Process.uid}`.strip })"
|
17
18
|
rsql(
|
18
19
|
"INSERT INTO responses (time, rcode, req_resource, cache) VALUES (?, ?, ?, ?)",
|
19
20
|
[
|
20
21
|
Time.now.to_i,
|
21
22
|
Resolv::DNS::RCode.const_get(rcode),
|
22
|
-
|
23
|
-
(
|
23
|
+
res_class::TypeValue,
|
24
|
+
(from_cache ? 1 : 0)
|
24
25
|
]
|
25
26
|
)
|
26
27
|
rescue => e
|
27
|
-
|
28
|
+
Global.logger.error e.desc
|
28
29
|
end
|
29
30
|
|
30
31
|
# select rcode, count(*) from responses where time > strftime('%s', 'now') - 300 group by rcode
|
@@ -120,7 +121,7 @@ module DnsOne; class Stat
|
|
120
121
|
opts = {}
|
121
122
|
opts[:readonly] = true if @conf[:readonly]
|
122
123
|
|
123
|
-
#
|
124
|
+
# Global.logger.info "Opening stat db #{db_file} (cwd: #{Dir.pwd})."
|
124
125
|
@db = SQLite3::Database.new db_file, opts
|
125
126
|
|
126
127
|
if new_db
|
@@ -165,5 +166,5 @@ module DnsOne; class Stat
|
|
165
166
|
@meta_stats_log.info "#{time} #{dur} #{sql}"
|
166
167
|
end
|
167
168
|
|
168
|
-
end; end
|
169
|
+
end; end; end
|
169
170
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module DnsOne; module Backend; class File
|
2
|
+
if @conf.req_log_file
|
3
|
+
path = @conf.req_log_file.is_a?(String) ? @conf.req_log_file :
|
4
|
+
l = Logger.new @conf.ruby_dns_logger, 10, (10 * 2**20)
|
5
|
+
l.level = Logger::WARN
|
6
|
+
Global.ruby_dns_logger = l
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.log_result ip_address, domain_name, res_class, rcode, resp_log, from_cache
|
10
|
+
fields = []
|
11
|
+
|
12
|
+
fields << domain_name
|
13
|
+
fields << Util.last_mod(res_class)
|
14
|
+
fields << rcode
|
15
|
+
fields << resp_log.map{ |rec|
|
16
|
+
Util.last_mod(rec.res_class) +
|
17
|
+
':' +
|
18
|
+
[rec.val].flatten.join(',')
|
19
|
+
}.join(';')
|
20
|
+
fields << ip_address
|
21
|
+
fields << (from_cache ? '1' : '0')
|
22
|
+
|
23
|
+
fields.map!{|v| v.blank? ? '-' : v}
|
24
|
+
|
25
|
+
Global.logger.info "result: #{ fields.join ' ' }"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module DnsOne; module ReqLog; class ReqLog
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
@conf = Global.conf
|
5
|
+
end
|
6
|
+
|
7
|
+
def on_response *args
|
8
|
+
if @conf.log_req_db
|
9
|
+
@db ||= Db.new
|
10
|
+
@db.on_response *args
|
11
|
+
end
|
12
|
+
|
13
|
+
if @conf.log_req_file
|
14
|
+
@file = File.new
|
15
|
+
@file.on_response *args
|
16
|
+
end
|
17
|
+
|
18
|
+
if @conf.log_req_account
|
19
|
+
@account ||= Account.new
|
20
|
+
@account.on_response *args
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end; end; end
|
25
|
+
|
data/lib/dns_one/server.rb
CHANGED
@@ -1,141 +1,69 @@
|
|
1
1
|
|
2
2
|
require "dns_one/zone_search"
|
3
|
+
require "dns_one/req_log/req_log"
|
3
4
|
require 'socket'
|
4
5
|
require 'json'
|
5
6
|
|
6
7
|
module DnsOne; class Server
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
DNS_DAEMON_INTERFACES = [
|
12
|
-
[:udp, "0.0.0.0", 53],
|
13
|
-
[:tcp, "0.0.0.0", 53],
|
14
|
-
[:udp, "::", 5300],
|
15
|
-
[:tcp, "::", 5300]
|
16
|
-
]
|
17
|
-
|
18
|
-
def initialize conf, conf_zone_search
|
19
|
-
@conf = conf
|
20
|
-
@zone_search = ZoneSearch.instance.setup conf_zone_search
|
21
|
-
if conf[:log_result_socket]
|
22
|
-
@log_result = {}
|
23
|
-
@log_result_mutex = Mutex.new
|
24
|
-
end
|
8
|
+
def initialize
|
9
|
+
@zone_search = ZoneSearch.instance.setup
|
10
|
+
@req_log = nil
|
25
11
|
end
|
26
12
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
stat = nil
|
31
|
-
if conf[:log_result_socket]
|
32
|
-
log_result = @log_result
|
33
|
-
log_result_mutex = @log_result_mutex
|
34
|
-
launch_log_result_socket
|
35
|
-
log_result_last_reset = 0
|
13
|
+
def start
|
14
|
+
if RExec.current_user == 'root'
|
15
|
+
RExec.change_user Global.conf.run_as
|
36
16
|
end
|
17
|
+
@req_log ||= ReqLog::ReqLog.new
|
18
|
+
Global.logger.info "Running as #{RExec.current_user}"
|
19
|
+
end
|
37
20
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
run_as = conf[:run_as] || DEFAULT_RUN_AS
|
42
|
-
stat = Stat.new user: run_as if !stat
|
43
|
-
RExec.change_user run_as
|
44
|
-
else
|
45
|
-
stat = Stat.new if !stat
|
46
|
-
end
|
47
|
-
Log.i "Running as #{RExec.current_user}"
|
48
|
-
end
|
49
|
-
|
50
|
-
match(/(.+)/) do |t| # transaction
|
51
|
-
rcode = :NoError
|
52
|
-
resp_log = []
|
53
|
-
|
54
|
-
begin
|
55
|
-
domain_name = t.question.to_s
|
56
|
-
ip_address = t.options[:peer] rescue nil
|
57
|
-
|
58
|
-
records, from_cache = zone_search.query domain_name, t.resource_class, ip_address
|
59
|
-
|
60
|
-
if records
|
61
|
-
if records.empty?
|
62
|
-
t.fail! :NoError
|
63
|
-
else
|
64
|
-
records.each do |rec|
|
65
|
-
resp_log << rec
|
66
|
-
t.respond! *[rec.val].flatten, {resource_class: rec.res_class, section: rec.section}
|
67
|
-
end
|
68
|
-
end
|
69
|
-
else
|
70
|
-
rcode = :NXDomain
|
71
|
-
t.fail! :NXDomain
|
72
|
-
end
|
73
|
-
rescue => e
|
74
|
-
rcode = :ServFail
|
75
|
-
end
|
76
|
-
|
77
|
-
begin
|
78
|
-
if conf[:save_stats]
|
79
|
-
stat.save rcode, t.resource_class, from_cache
|
80
|
-
end
|
21
|
+
def resolve transaction
|
22
|
+
rcode = :NoError
|
23
|
+
resp_log = []
|
81
24
|
|
82
|
-
|
83
|
-
|
84
|
-
|
25
|
+
begin
|
26
|
+
domain_name = transaction.question.to_s
|
27
|
+
ip_address = transaction.options[:peer] rescue nil
|
85
28
|
|
86
|
-
|
87
|
-
log_result_mutex.synchronize {
|
88
|
-
# Reset log_result every 5 min
|
89
|
-
if Time.now.to_i / 300 > log_result_last_reset / 300
|
90
|
-
log_result_last_reset = Time.now.to_i
|
91
|
-
log_result.each_key do |k|
|
92
|
-
if log_result[k].is_a? Hash
|
93
|
-
log_result[k].each_key do |k2|
|
94
|
-
log_result[k][k2] = 0
|
95
|
-
end
|
96
|
-
else
|
97
|
-
log_result[k] = 0
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
29
|
+
records, from_cache = @zone_search.query domain_name, transaction.resource_class, ip_address
|
101
30
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
log_result[:rcode] ||= {}
|
110
|
-
log_result[:rcode][rcode_uc] ||= 0
|
111
|
-
log_result[:rcode][rcode_uc] += 1
|
112
|
-
|
113
|
-
req_resource = Util.last_mod(t.resource_class).downcase
|
114
|
-
log_result[:req_resource] ||= {}
|
115
|
-
log_result[:req_resource][req_resource] ||= 0
|
116
|
-
log_result[:req_resource][req_resource] += 1
|
117
|
-
}
|
31
|
+
if records
|
32
|
+
if records.empty?
|
33
|
+
transaction.fail! :NoError
|
34
|
+
else
|
35
|
+
records.each do |rec|
|
36
|
+
resp_log << rec
|
37
|
+
transaction.respond! *[rec.val].flatten, {resource_class: rec.res_class, section: rec.section}
|
118
38
|
end
|
119
|
-
rescue => e
|
120
|
-
Log.exc e
|
121
39
|
end
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
otherwise do |t|
|
127
|
-
t.fail! :NXDomain
|
40
|
+
else
|
41
|
+
rcode = :NXDomain
|
42
|
+
transaction.fail! :NXDomain
|
128
43
|
end
|
44
|
+
rescue => e
|
45
|
+
rcode = :ServFail
|
129
46
|
end
|
47
|
+
|
48
|
+
@req_log.on_response *[
|
49
|
+
ip_address,
|
50
|
+
domain_name,
|
51
|
+
transaction.resource_class,
|
52
|
+
rcode,
|
53
|
+
resp_log,
|
54
|
+
from_cache
|
55
|
+
]
|
56
|
+
|
57
|
+
raise e if e
|
130
58
|
end
|
131
59
|
|
132
60
|
def dns_daemon_interfaces
|
133
61
|
if RExec.current_user == 'root'
|
134
|
-
|
62
|
+
Global.conf.interfaces
|
135
63
|
else
|
136
|
-
ports =
|
64
|
+
ports = Global.conf.interfaces.map do |port|
|
137
65
|
if port[2] <= 1024
|
138
|
-
|
66
|
+
Global.logger.warn "Changing listening port #{port.join ':'} to #{port[2] + 10000} for non-root process."
|
139
67
|
port[2] += 10000
|
140
68
|
end
|
141
69
|
port
|
@@ -144,34 +72,20 @@ module DnsOne; class Server
|
|
144
72
|
end
|
145
73
|
end
|
146
74
|
|
147
|
-
def
|
148
|
-
|
149
|
-
log_result_mutex = @log_result_mutex
|
150
|
-
conf = @conf
|
75
|
+
def run
|
76
|
+
srv = self
|
151
77
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
break
|
164
|
-
rescue => e
|
165
|
-
Log.exc e
|
166
|
-
break
|
167
|
-
end
|
168
|
-
Thread.pass
|
169
|
-
sleep 0.1
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
rescue => e
|
174
|
-
Log.exc e
|
78
|
+
RubyDNS::run_server(dns_daemon_interfaces) do
|
79
|
+
on(:start) do
|
80
|
+
srv.start
|
81
|
+
end
|
82
|
+
|
83
|
+
match(/(.+)/) do |transaction|
|
84
|
+
srv.resolve transaction
|
85
|
+
end
|
86
|
+
|
87
|
+
otherwise do |transaction|
|
88
|
+
transaction.fail! :NXDomain
|
175
89
|
end
|
176
90
|
end
|
177
91
|
end
|
data/lib/dns_one/util.rb
CHANGED
@@ -4,7 +4,7 @@ module DnsOne; class Util
|
|
4
4
|
|
5
5
|
class << self
|
6
6
|
def die msg
|
7
|
-
|
7
|
+
Global.logger.fatal msg
|
8
8
|
exit 1
|
9
9
|
end
|
10
10
|
|
@@ -33,25 +33,6 @@ module DnsOne; class Util
|
|
33
33
|
constant.to_s.split('::').last
|
34
34
|
end
|
35
35
|
|
36
|
-
def log_result ip_address, domain_name, res_class, rcode, resp_log, from_cache
|
37
|
-
fields = []
|
38
|
-
|
39
|
-
fields << domain_name
|
40
|
-
fields << Util.last_mod(res_class)
|
41
|
-
fields << rcode
|
42
|
-
fields << resp_log.map{ |rec|
|
43
|
-
Util.last_mod(rec.res_class) +
|
44
|
-
':' +
|
45
|
-
[rec.val].flatten.join(',')
|
46
|
-
}.join(';')
|
47
|
-
fields << ip_address
|
48
|
-
fields << (from_cache ? '1' : '0')
|
49
|
-
|
50
|
-
fields.map!{|v| v.blank? ? '-' : v}
|
51
|
-
|
52
|
-
Log.i "result: #{ fields.join ' ' }"
|
53
|
-
end
|
54
|
-
|
55
36
|
def const_underscore name
|
56
37
|
name = name.to_s.dup
|
57
38
|
name.gsub!('::', '/')
|
@@ -62,6 +43,33 @@ module DnsOne; class Util
|
|
62
43
|
name
|
63
44
|
end
|
64
45
|
|
46
|
+
def hash_to_ostruct_deep hash
|
47
|
+
os = OpenStruct.new
|
48
|
+
hash.each_pair{ |k, v|
|
49
|
+
if v.is_a? Hash
|
50
|
+
os[k] = hash_to_ostruct_deep v
|
51
|
+
else
|
52
|
+
os[k] = v
|
53
|
+
end
|
54
|
+
}
|
55
|
+
os
|
56
|
+
end
|
57
|
+
|
58
|
+
def init_logger logdev, level = Logger::WARN, shift_age = 10, shift_size = 2**20
|
59
|
+
if logdev.is_a? String
|
60
|
+
begin
|
61
|
+
FileUtils.mkdir_p File.dirname(logdev)
|
62
|
+
File.write logdev, ''
|
63
|
+
rescue => e
|
64
|
+
$stderr.puts "#{e.desc}\nCannot open log file #{logdev}. Will use STDOUT."
|
65
|
+
logdev = $stdout
|
66
|
+
end
|
67
|
+
end
|
68
|
+
l = Logger.new logdev, shift_age, shift_size
|
69
|
+
l.level = level
|
70
|
+
l
|
71
|
+
end
|
72
|
+
|
65
73
|
end
|
66
74
|
|
67
75
|
end; end
|
data/lib/dns_one/version.rb
CHANGED
data/lib/dns_one/zone_search.rb
CHANGED
@@ -11,11 +11,11 @@ module DnsOne; class ZoneSearch
|
|
11
11
|
Name = Resolv::DNS::Name
|
12
12
|
IN = Resolv::DNS::Resource::IN
|
13
13
|
|
14
|
-
def setup
|
15
|
-
@conf = conf
|
14
|
+
def setup
|
15
|
+
@conf = Global.conf
|
16
16
|
check_record_sets
|
17
17
|
@backend = set_backend
|
18
|
-
@cache = Cache.new
|
18
|
+
@cache = Cache.new Global.conf.cache_max
|
19
19
|
@ignore_subdomains_re = build_ignore_subdomains_re
|
20
20
|
|
21
21
|
if @backend.preload_dummy?
|
@@ -30,25 +30,25 @@ module DnsOne; class ZoneSearch
|
|
30
30
|
|
31
31
|
dom_name = dom_name.dup
|
32
32
|
res_class_short = Util.last_mod res_class # :A, :NS, found in conf.yml:record_sets items
|
33
|
-
|
33
|
+
Global.logger.debug "request #{ dom_name }/#{res_class_short} from #{ip_address}..."
|
34
34
|
|
35
35
|
records = []
|
36
36
|
|
37
37
|
rec_set_name, from_cache = find_record_set dom_name
|
38
|
-
|
38
|
+
Global.logger.debug "domain #{ rec_set_name ? "found, rec_set_name = '#{rec_set_name}'" : 'not found' }"
|
39
39
|
return unless rec_set_name
|
40
40
|
|
41
41
|
# use first record set if rec_set_name == ''
|
42
|
-
rec_set_name = @conf
|
42
|
+
rec_set_name = @conf.record_sets.to_h.keys.first if rec_set_name == ''
|
43
43
|
|
44
|
-
rec_set = @conf
|
45
|
-
|
44
|
+
rec_set = @conf.record_sets[rec_set_name]
|
45
|
+
Global.logger.debug "record set #{ rec_set ? 'found' : 'not found' }"
|
46
46
|
return records unless rec_set
|
47
47
|
|
48
48
|
# TODO: move parsing logic to own class
|
49
49
|
|
50
50
|
recs = rec_set[res_class_short.to_sym]
|
51
|
-
|
51
|
+
Global.logger.debug "record(s) #{ recs ? 'found' : 'not found' }"
|
52
52
|
|
53
53
|
# Loop over 1 or more
|
54
54
|
recs = [recs]
|
@@ -72,25 +72,25 @@ module DnsOne; class ZoneSearch
|
|
72
72
|
private
|
73
73
|
|
74
74
|
def build_ignore_subdomains_re
|
75
|
-
if i = @conf
|
75
|
+
if i = @conf.ignore_subdomains.presence
|
76
76
|
s = i.strip.split(/\s+/).map(&:downcase).join '|'
|
77
77
|
/^(#{ s })\./i
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
81
|
def set_backend
|
82
|
-
if file = @conf
|
82
|
+
if file = @conf.backend.file
|
83
83
|
unless ::File.exists? file
|
84
|
-
Util.die "Domain list file #{file} not found."
|
84
|
+
Util.die "Domain list file #{file} not found (pwd = #{Dir.pwd})."
|
85
85
|
end
|
86
86
|
Backend::File.new file
|
87
|
-
elsif @conf
|
88
|
-
unless @conf
|
87
|
+
elsif @conf.backend.http_bell_url
|
88
|
+
unless @conf.backend.http_bell_record_set
|
89
89
|
Util.die "backend.http_bell_record_set not set."
|
90
90
|
end
|
91
|
-
Backend::HTTPBell.new @conf
|
91
|
+
Backend::HTTPBell.new @conf.backend
|
92
92
|
else
|
93
|
-
Backend::DB.new @conf
|
93
|
+
Backend::DB.new @conf.backend
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
@@ -101,7 +101,7 @@ module DnsOne; class ZoneSearch
|
|
101
101
|
enabled_cache = use_cache && @backend.allow_cache
|
102
102
|
|
103
103
|
if enabled_cache and rec_set = @cache.find(dom_name)
|
104
|
-
|
104
|
+
Global.logger.debug "found in cache (#{@cache.stat})"
|
105
105
|
[rec_set, true]
|
106
106
|
else
|
107
107
|
if rec_set = @backend.find(dom_name)
|
@@ -130,11 +130,11 @@ module DnsOne; class ZoneSearch
|
|
130
130
|
end
|
131
131
|
|
132
132
|
def check_record_sets
|
133
|
-
if @conf
|
133
|
+
if @conf.record_sets.blank?
|
134
134
|
Util.die "Record sets cannot be empty. Check file."
|
135
135
|
end
|
136
136
|
|
137
|
-
@conf
|
137
|
+
@conf.record_sets.each_pair do |rec_set_name, records|
|
138
138
|
unless records[:NS] and records[:NS].length >= 1
|
139
139
|
Util.die "Record set #{rec_set_name} is invalid. It must have at least 1 NS record."
|
140
140
|
end
|
data/util/dns_one.service
CHANGED
data/util/sample_conf.yml
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
# log_result_socket_file: /tmp/dns_one_log_result.sock
|
2
|
+
run_as: dnsserver # optional, but highly recommended! adduser --system dnsserver
|
3
|
+
ignore_subdomains: www en it es pt ru fr at # optional, defaults to an empty list
|
4
|
+
# cache_max: 100000 # optional, defaults to 10000
|
5
|
+
# log_file: /var/log/dns_one.log # optional, defaults to /var/log/dns_one.log
|
6
|
+
log_req_db: false
|
7
|
+
log_req_file: false
|
8
|
+
log_req_account: true
|
9
|
+
# log_req_socket_file: /tmp/socket.socket
|
11
10
|
|
12
11
|
backend:
|
13
12
|
##############
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dns_one
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Lobato
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-05-
|
11
|
+
date: 2018-05-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
75
|
+
version: 2.0.1
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
82
|
+
version: 2.0.1
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: rexec
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -170,10 +170,13 @@ files:
|
|
170
170
|
- lib/dns_one/core_ext/exception.rb
|
171
171
|
- lib/dns_one/core_ext/hash.rb
|
172
172
|
- lib/dns_one/core_ext/string.rb
|
173
|
-
- lib/dns_one/
|
173
|
+
- lib/dns_one/global.rb
|
174
|
+
- lib/dns_one/req_log/account.rb
|
175
|
+
- lib/dns_one/req_log/db.rb
|
176
|
+
- lib/dns_one/req_log/file.rb
|
177
|
+
- lib/dns_one/req_log/req_log.rb
|
174
178
|
- lib/dns_one/server.rb
|
175
179
|
- lib/dns_one/setup.rb
|
176
|
-
- lib/dns_one/stat.rb
|
177
180
|
- lib/dns_one/util.rb
|
178
181
|
- lib/dns_one/version.rb
|
179
182
|
- lib/dns_one/zone_search.rb
|
@@ -200,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
200
203
|
version: '0'
|
201
204
|
requirements: []
|
202
205
|
rubyforge_project:
|
203
|
-
rubygems_version: 2.
|
206
|
+
rubygems_version: 2.5.1
|
204
207
|
signing_key:
|
205
208
|
specification_version: 4
|
206
209
|
summary: DNS server for many zones sharing only one or few records, written in Ruby.
|
data/lib/dns_one/log.rb
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
|
2
|
-
class Log < Logger
|
3
|
-
SYSLOG_MIN_SEVERITY = Logger::WARN
|
4
|
-
|
5
|
-
class << self
|
6
|
-
|
7
|
-
# 'def [d|i|w|e|f] msg' for DEBUG INFO WARN ERROR FATAL
|
8
|
-
Logger::Severity::constants.each do |severity|
|
9
|
-
method_name = severity.to_s[0].downcase
|
10
|
-
define_method(method_name) do |msg|
|
11
|
-
log severity, msg
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def setup
|
16
|
-
@syslog = Syslog::Logger.new "dns_one"
|
17
|
-
@log_file = setfile "/var/log/dns_one.log"
|
18
|
-
@logger = Logger.new @log_file
|
19
|
-
@logger.level = Logger::INFO
|
20
|
-
end
|
21
|
-
|
22
|
-
def ruby_dns_logger
|
23
|
-
l = Logger.new setfile("/var/log/dns_one_rubydns.log")
|
24
|
-
l.level = Logger::WARN
|
25
|
-
l
|
26
|
-
end
|
27
|
-
|
28
|
-
def exc exception
|
29
|
-
e exception.desc
|
30
|
-
end
|
31
|
-
|
32
|
-
def logger
|
33
|
-
@logger
|
34
|
-
end
|
35
|
-
|
36
|
-
def log_file_desc
|
37
|
-
case @log_file
|
38
|
-
when STDOUT
|
39
|
-
'STDOUT'
|
40
|
-
when STDERR
|
41
|
-
'STDERR'
|
42
|
-
else
|
43
|
-
@log_file
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def setfile file
|
50
|
-
if File.writable?(file) or File.writable?(File.dirname(file))
|
51
|
-
file
|
52
|
-
else
|
53
|
-
STDERR
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def log severity, msg
|
58
|
-
met_name = severity.downcase
|
59
|
-
|
60
|
-
@logger.send met_name, msg
|
61
|
-
|
62
|
-
if sev_num(severity) >= SYSLOG_MIN_SEVERITY
|
63
|
-
@syslog.send met_name, msg
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def sev_num sev
|
68
|
-
Object.const_get "Logger::#{sev}"
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|