dyndnsd 2.1.1 → 3.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require 'ipaddr'
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Dyndnsd
3
4
  module Responder
@@ -8,7 +9,7 @@ module Dyndnsd
8
9
  end
9
10
 
10
11
  # @param env [Hash{String => String}]
11
- # @return [Array{Integer,Hash{String => String},Array{String}}]
12
+ # @return [Array{Integer,Hash{String => String},Array<String>}]
12
13
  def call(env)
13
14
  @app.call(env).tap do |status_code, headers, body|
14
15
  if headers.key?('X-DynDNS-Response')
@@ -23,30 +24,32 @@ module Dyndnsd
23
24
 
24
25
  # @param status_code [Integer]
25
26
  # @param headers [Hash{String => String}]
26
- # @param body [Array{String}]
27
- # @return [Array{Integer,Hash{String => String},Array{String}}]
27
+ # @param body [Array<String>]
28
+ # @return [Array{Integer,Hash{String => String},Array<String>}]
28
29
  def decorate_dyndnsd_response(status_code, headers, body)
29
- if status_code == 200
30
+ case status_code
31
+ when 200
30
32
  [200, {'Content-Type' => 'text/plain'}, [get_success_body(body[0], body[1])]]
31
- elsif status_code == 422
33
+ when 422
32
34
  error_response_map[headers['X-DynDNS-Response']]
33
35
  end
34
36
  end
35
37
 
36
38
  # @param status_code [Integer]
37
39
  # @param headers [Hash{String => String}]
38
- # @param _body [Array{String}]
39
- # @return [Array{Integer,Hash{String => String},Array{String}}]
40
+ # @param _body [Array<String>]
41
+ # @return [Array{Integer,Hash{String => String},Array<String>}]
40
42
  def decorate_other_response(status_code, headers, _body)
41
- if status_code == 400
43
+ case status_code
44
+ when 400
42
45
  [status_code, headers, ['Bad Request']]
43
- elsif status_code == 401
46
+ when 401
44
47
  [status_code, headers, ['badauth']]
45
48
  end
46
49
  end
47
50
 
48
- # @param changes [Array{Symbol}]
49
- # @param myips [Array{String}]
51
+ # @param changes [Array<Symbol>]
52
+ # @param myips [Array<String>]
50
53
  # @return [String]
51
54
  def get_success_body(changes, myips)
52
55
  changes.map { |change| "#{change} #{myips.join(' ')}" }.join("\n")
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Dyndnsd
3
4
  module Responder
@@ -8,7 +9,7 @@ module Dyndnsd
8
9
  end
9
10
 
10
11
  # @param env [Hash{String => String}]
11
- # @return [Array{Integer,Hash{String => String},Array{String}}]
12
+ # @return [Array{Integer,Hash{String => String},Array<String>}]
12
13
  def call(env)
13
14
  @app.call(env).tap do |status_code, headers, body|
14
15
  if headers.key?('X-DynDNS-Response')
@@ -23,30 +24,32 @@ module Dyndnsd
23
24
 
24
25
  # @param status_code [Integer]
25
26
  # @param headers [Hash{String => String}]
26
- # @param body [Array{String}]
27
- # @return [Array{Integer,Hash{String => String},Array{String}}]
27
+ # @param body [Array<String>]
28
+ # @return [Array{Integer,Hash{String => String},Array<String>}]
28
29
  def decorate_dyndnsd_response(status_code, headers, body)
29
- if status_code == 200
30
+ case status_code
31
+ when 200
30
32
  [200, {'Content-Type' => 'text/plain'}, [get_success_body(body[0], body[1])]]
31
- elsif status_code == 422
33
+ when 422
32
34
  error_response_map[headers['X-DynDNS-Response']]
33
35
  end
34
36
  end
35
37
 
36
38
  # @param status_code [Integer]
37
39
  # @param headers [Hash{String => String}]
38
- # @param _body [Array{String}]
39
- # @return [Array{Integer,Hash{String => String},Array{String}}]
40
+ # @param _body [Array<String>]
41
+ # @return [Array{Integer,Hash{String => String},Array<String>}]
40
42
  def decorate_other_response(status_code, headers, _body)
41
- if status_code == 400
43
+ case status_code
44
+ when 400
42
45
  [status_code, headers, ['Bad Request']]
43
- elsif status_code == 401
46
+ when 401
44
47
  [status_code, headers, ['Unauthorized']]
45
48
  end
46
49
  end
47
50
 
48
- # @param changes [Array{Symbol}]
49
- # @param myips [Array{String}]
51
+ # @param changes [Array<Symbol>]
52
+ # @param myips [Array<String>]
50
53
  # @return [String]
51
54
  def get_success_body(changes, myips)
52
55
  changes.map { |change| change == :good ? "Changed to #{myips.join(' ')}" : "No change needed for #{myips.join(' ')}" }.join("\n")
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  # Adapted from https://github.com/eric/metriks-graphite/blob/master/lib/metriks/reporter/graphite.rb
3
4
 
@@ -27,11 +28,9 @@ module Dyndnsd
27
28
  sleep @interval
28
29
 
29
30
  Thread.new do
30
- begin
31
- write
32
- rescue StandardError => e
33
- @on_error[e] rescue nil
34
- end
31
+ write
32
+ rescue StandardError => e
33
+ @on_error[e] rescue nil
35
34
  end
36
35
  end
37
36
  end
@@ -95,8 +94,8 @@ module Dyndnsd
95
94
  # @param file [String]
96
95
  # @param base_name [String]
97
96
  # @param metric [Object]
98
- # @param keys [Array{Symbol}]
99
- # @param snapshot_keys [Array{Symbol}]
97
+ # @param keys [Array<Symbol>]
98
+ # @param snapshot_keys [Array<Symbol>]
100
99
  # @return [void]
101
100
  def write_metric(file, base_name, metric, keys, snapshot_keys = [])
102
101
  time = Time.now.to_i
@@ -1,18 +1,22 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Dyndnsd
3
4
  module Updater
4
5
  class CommandWithBindZone
5
6
  # @param domain [String]
6
- # @param config [Hash{String => Object}]
7
- def initialize(domain, config)
8
- @zone_file = config['zone_file']
9
- @command = config['command']
10
- @generator = Generator::Bind.new(domain, config)
7
+ # @param updater_params [Hash{String => Object}]
8
+ def initialize(domain, updater_params)
9
+ @zone_file = updater_params['zone_file']
10
+ @command = updater_params['command']
11
+ @generator = Generator::Bind.new(domain, updater_params)
11
12
  end
12
13
 
13
14
  # @param db [Dyndnsd::Database]
14
15
  # @return [void]
15
16
  def update(db)
17
+ # do not regenerate zone file (assumed to be persistent) if DB did not change
18
+ return if !db.changed?
19
+
16
20
  Helper.span('updater_update') do |span|
17
21
  span.set_tag('dyndnsd.updater.name', self.class.name&.split('::')&.last || 'None')
18
22
 
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'resolv'
4
+ require 'securerandom'
5
+
6
+ require 'async/dns'
7
+
8
+ module Dyndnsd
9
+ module Updater
10
+ class ZoneTransferServer
11
+ DEFAULT_SERVER_LISTENS = ['0.0.0.0@53'].freeze
12
+
13
+ # @param domain [String]
14
+ # @param updater_params [Hash{String => Object}]
15
+ def initialize(domain, updater_params)
16
+ @domain = domain
17
+
18
+ @server_listens = self.class.parse_endpoints(updater_params['server_listens'] || DEFAULT_SERVER_LISTENS)
19
+ @notify_targets = (updater_params['send_notifies'] || []).map { |e| self.class.parse_endpoints([e]) }
20
+
21
+ @zone_rr_ttl = updater_params['zone_ttl']
22
+ @zone_nameservers = updater_params['zone_nameservers'].map { |n| Resolv::DNS::Name.create(n) }
23
+ @zone_email_address = Resolv::DNS::Name.create(updater_params['zone_email_address'])
24
+ @zone_additional_ips = updater_params['zone_additional_ips'] || []
25
+
26
+ @server = ZoneTransferServerHelper.new(@server_listens, @domain)
27
+
28
+ # run Async::DNS server in background thread
29
+ Thread.new do
30
+ @server.run
31
+ end
32
+ end
33
+
34
+ # @param db [Dyndnsd::Database]
35
+ # @return [void]
36
+ def update(db)
37
+ Helper.span('updater_update') do |span|
38
+ span.set_tag('dyndnsd.updater.name', self.class.name&.split('::')&.last || 'None')
39
+
40
+ soa_rr = Resolv::DNS::Resource::IN::SOA.new(
41
+ @zone_nameservers[0], @zone_email_address,
42
+ db['serial'],
43
+ 10_800, # 3h
44
+ 300, # 5m
45
+ 604_800, # 1w
46
+ 3_600 # 1h
47
+ )
48
+
49
+ default_options = {ttl: @zone_rr_ttl}
50
+
51
+ # array containing all resource records for an AXFR request in the right order
52
+ rrs = []
53
+ # AXFR responses need to start with zone's SOA RR
54
+ rrs << [soa_rr, default_options]
55
+
56
+ # return RRs for all of the zone's nameservers
57
+ @zone_nameservers.each do |ns|
58
+ rrs << [Resolv::DNS::Resource::IN::NS.new(ns), default_options]
59
+ end
60
+
61
+ # return A/AAAA RRs for all additional IPv4s/IPv6s for the domain itself
62
+ @zone_additional_ips.each do |ip|
63
+ rrs << [create_addr_rr_for_ip(ip), default_options]
64
+ end
65
+
66
+ # return A/AAAA RRs for the dyndns hostnames
67
+ db['hosts'].each do |hostname, ips|
68
+ ips.each do |ip|
69
+ rrs << [create_addr_rr_for_ip(ip), default_options.merge({name: hostname})]
70
+ end
71
+ end
72
+
73
+ # AXFR responses need to end with zone's SOA RR again
74
+ rrs << [soa_rr, default_options]
75
+
76
+ # point Async::DNS server thread's variable to this new RR array
77
+ @server.axfr_rrs = rrs
78
+
79
+ # only send DNS NOTIFY if there really was a change
80
+ if db.changed?
81
+ send_dns_notify
82
+ end
83
+ end
84
+ end
85
+
86
+ # converts into suitable parameter form for Async::DNS::Resolver or Async::DNS::Server
87
+ #
88
+ # @param endpoint_list [Array<String>]
89
+ # @return [Array{Array{Object}}]
90
+ def self.parse_endpoints(endpoint_list)
91
+ endpoint_list.map { |addr_string| addr_string.split('@') }
92
+ .map { |addr_parts| [addr_parts[0], addr_parts[1].to_i || 53] }
93
+ .map { |addr| [:tcp, :udp].map { |type| [type] + addr } }
94
+ .flatten(1)
95
+ end
96
+
97
+ private
98
+
99
+ # creates correct Resolv::DNS::Resource object for IP address type
100
+ #
101
+ # @param ip_string [String]
102
+ # @return [Resolv::DNS::Resource::IN::A,Resolv::DNS::Resource::IN::AAAA]
103
+ def create_addr_rr_for_ip(ip_string)
104
+ ip = IPAddr.new(ip_string).native
105
+
106
+ if ip.ipv6?
107
+ Resolv::DNS::Resource::IN::AAAA.new(ip.to_s)
108
+ else
109
+ Resolv::DNS::Resource::IN::A.new(ip.to_s)
110
+ end
111
+ end
112
+
113
+ # https://tools.ietf.org/html/rfc1996
114
+ #
115
+ # @return [void]
116
+ def send_dns_notify
117
+ Async::Reactor.run do
118
+ @notify_targets.each do |notify_target|
119
+ target = Async::DNS::Resolver.new(notify_target)
120
+
121
+ # assemble DNS NOTIFY message
122
+ request = Resolv::DNS::Message.new(SecureRandom.random_number(2**16))
123
+ request.opcode = Resolv::DNS::OpCode::Notify
124
+ request.add_question("#{@domain}.", Resolv::DNS::Resource::IN::SOA)
125
+
126
+ _response = target.dispatch_request(request)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ class ZoneTransferServerHelper < Async::DNS::Server
133
+ attr_accessor :axfr_rrs
134
+
135
+ def initialize(endpoints, domain)
136
+ super(endpoints, logger: Dyndnsd.logger)
137
+ @domain = domain
138
+ end
139
+
140
+ # @param name [String]
141
+ # @param resource_class [Resolv::DNS::Resource]
142
+ # Since solargraph cannot parse this: param transaction [Async::DNS::Transaction]
143
+ # @return [void]
144
+ def process(name, resource_class, transaction)
145
+ if name != @domain || resource_class != Resolv::DNS::Resource::Generic::Type252_Class1
146
+ transaction.fail!(:NXDomain)
147
+ return
148
+ end
149
+
150
+ # https://tools.ietf.org/html/rfc5936
151
+ transaction.append_question!
152
+ @axfr_rrs.each do |rr|
153
+ transaction.add([rr[0]], rr[1])
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -1,4 +1,5 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Dyndnsd
3
- VERSION = '2.1.1'.freeze
4
+ VERSION = '3.1.0.rc1'
4
5
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dyndnsd
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 3.1.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Nicolai
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-01 00:00:00.000000000 Z
11
+ date: 2020-08-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rack
14
+ name: async-dns
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '2.0'
19
+ version: 1.2.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '2.0'
26
+ version: 1.2.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: json
28
+ name: jaeger-client
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 1.0.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 1.0.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: metriks
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -67,33 +67,33 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.5.0
69
69
  - !ruby/object:Gem::Dependency
70
- name: rack-tracer
70
+ name: rack
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 0.9.0
75
+ version: '2.0'
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: 0.9.0
82
+ version: '2.0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: jaeger-client
84
+ name: rack-tracer
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 0.10.0
89
+ version: 0.9.0
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 0.10.0
96
+ version: 0.9.0
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: bundler
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -109,21 +109,21 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: rake
112
+ name: bundler-audit
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - ">="
115
+ - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '0'
117
+ version: 0.7.0
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - ">="
122
+ - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '0'
124
+ version: 0.7.0
125
125
  - !ruby/object:Gem::Dependency
126
- name: rspec
126
+ name: rack-test
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - ">="
@@ -137,7 +137,7 @@ dependencies:
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  - !ruby/object:Gem::Dependency
140
- name: rack-test
140
+ name: rake
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - ">="
@@ -151,33 +151,33 @@ dependencies:
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
153
  - !ruby/object:Gem::Dependency
154
- name: rubocop
154
+ name: rspec
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
- - - "~>"
157
+ - - ">="
158
158
  - !ruby/object:Gem::Version
159
- version: 0.80.0
159
+ version: '0'
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
- - - "~>"
164
+ - - ">="
165
165
  - !ruby/object:Gem::Version
166
- version: 0.80.0
166
+ version: '0'
167
167
  - !ruby/object:Gem::Dependency
168
- name: bundler-audit
168
+ name: rubocop
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
171
  - - "~>"
172
172
  - !ruby/object:Gem::Version
173
- version: 0.6.0
173
+ version: 0.89.0
174
174
  type: :development
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
- version: 0.6.0
180
+ version: 0.89.0
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: solargraph
183
183
  requirement: !ruby/object:Gem::Requirement
@@ -194,24 +194,19 @@ dependencies:
194
194
  version: '0'
195
195
  description: A small, lightweight and extensible DynDNS server written with Ruby and
196
196
  Rack.
197
- email: chrnicolai@gmail.com
197
+ email:
198
198
  executables:
199
199
  - dyndnsd
200
200
  extensions: []
201
- extra_rdoc_files: []
201
+ extra_rdoc_files:
202
+ - README.md
203
+ - CHANGELOG.md
204
+ - LICENSE
202
205
  files:
203
- - ".gitignore"
204
- - ".rubocop.yml"
205
- - ".solargraph.yml"
206
- - ".travis.yml"
207
206
  - CHANGELOG.md
208
- - Gemfile
209
207
  - LICENSE
210
208
  - README.md
211
- - Rakefile
212
- - bin/dyndnsd
213
- - dyndnsd.gemspec
214
- - init.d/debian-6-dyndnsd
209
+ - exe/dyndnsd
215
210
  - lib/dyndnsd.rb
216
211
  - lib/dyndnsd/database.rb
217
212
  - lib/dyndnsd/generator/bind.rb
@@ -220,15 +215,15 @@ files:
220
215
  - lib/dyndnsd/responder/rest_style.rb
221
216
  - lib/dyndnsd/textfile_reporter.rb
222
217
  - lib/dyndnsd/updater/command_with_bind_zone.rb
218
+ - lib/dyndnsd/updater/zone_transfer_server.rb
223
219
  - lib/dyndnsd/version.rb
224
- - spec/daemon_spec.rb
225
- - spec/spec_helper.rb
226
- - spec/support/dummy_database.rb
227
- - spec/support/dummy_updater.rb
228
220
  homepage: https://github.com/cmur2/dyndnsd
229
221
  licenses:
230
222
  - Apache-2.0
231
- metadata: {}
223
+ metadata:
224
+ bug_tracker_uri: https://github.com/cmur2/dyndnsd/issues
225
+ changelog_uri: https://github.com/cmur2/dyndnsd/blob/master/CHANGELOG.md
226
+ source_code_uri: https://github.com/cmur2/dyndnsd
232
227
  post_install_message:
233
228
  rdoc_options: []
234
229
  require_paths:
@@ -237,19 +232,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
237
232
  requirements:
238
233
  - - ">="
239
234
  - !ruby/object:Gem::Version
240
- version: '2.3'
235
+ version: '2.5'
241
236
  required_rubygems_version: !ruby/object:Gem::Requirement
242
237
  requirements:
243
- - - ">="
238
+ - - ">"
244
239
  - !ruby/object:Gem::Version
245
- version: '0'
240
+ version: 1.3.1
246
241
  requirements: []
247
- rubygems_version: 3.0.6
242
+ rubygems_version: 3.1.2
248
243
  signing_key:
249
244
  specification_version: 4
250
245
  summary: dyndnsd.rb
251
- test_files:
252
- - spec/daemon_spec.rb
253
- - spec/spec_helper.rb
254
- - spec/support/dummy_database.rb
255
- - spec/support/dummy_updater.rb
246
+ test_files: []