redns 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d2bafcebc9a854b66739ac0f38dd909dd56ec4ef
4
- data.tar.gz: e3da07761ddd463efaecdbf544d28ef4a8fa7bbd
3
+ metadata.gz: a285fa417ce8a6cd882a1a882dae99db88dea5e3
4
+ data.tar.gz: a9182c4722a3e97dc04c7ae18f3eeb998fe06034
5
5
  SHA512:
6
- metadata.gz: 51dc9b6de6d62d44cad19112d858a98ad14cc8457213841665c5526da4d6e5eac7045d9c20f0750d8f3622d412e77d4c0f8ec39f013ee4eb160ebacb33d48d2a
7
- data.tar.gz: 617feea176a9adf4f3e7895bc0f46da460850654ccaa598e26aa168dea00acfa262490f9cee88d62af526b747dafbcbd96f67e588dc2e723ccc4efbc6d476ace
6
+ metadata.gz: 83d57d0a82ef870f6b40269388d203bc85169071a48648352bf758e5b35756ec4d8b3aee678e68599fe602cb9340a55f918bb12e08bb10d2523610d79b5b284d
7
+ data.tar.gz: ec67b76f94fb65d902e6ae437d33e6301a8b04d1798935015f0b67d8758b97a85d10b96d90497274bd4070a8c9e3c11d9aaca726f3f56aaf34227fd6d26f07a7
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.2.1
@@ -3,8 +3,8 @@ require 'socket'
3
3
  class ReDNS::Connection < EventMachine::Connection
4
4
  # == Constants ============================================================
5
5
 
6
- TIMEOUT_DEFAULT = 5.0
7
- ATTEMPTS_DEFAULT = 5
6
+ TIMEOUT_DEFAULT = 2.5
7
+ ATTEMPTS_DEFAULT = 10
8
8
  SEQUENCE_LIMIT = 0x10000
9
9
 
10
10
  # == Properties ===========================================================
@@ -54,6 +54,10 @@ class ReDNS::Connection < EventMachine::Connection
54
54
  @nameservers ||= ReDNS::Support.default_nameservers
55
55
  end
56
56
 
57
+ def nameserver_score
58
+ @nameserver_score ||= Hash.new(0)
59
+ end
60
+
57
61
  # Configure the nameservers to use. Supplied value can be either a string
58
62
  # containing one IP address, an Array containing multiple IP addresses, or
59
63
  # nil which reverts to defaults.
@@ -64,7 +68,7 @@ class ReDNS::Connection < EventMachine::Connection
64
68
 
65
69
  # Picks a random nameserver from the configured list.
66
70
  def random_nameserver
67
- nameservers[rand(nameservers.length)]
71
+ nameservers.sample
68
72
  end
69
73
 
70
74
  # Returns the current port in use.
@@ -83,28 +87,24 @@ class ReDNS::Connection < EventMachine::Connection
83
87
  end
84
88
 
85
89
  serialized_message = message.serialize.to_s
86
- target_nameserver = random_nameserver
87
90
 
88
- result = send_datagram(
89
- serialized_message,
90
- target_nameserver,
91
- ReDNS::Support.dns_port
92
- )
91
+ nameservers = self.nameservers_by_score
93
92
 
94
- if (result > 0)
95
- @callback[@sequence] = {
96
- serialized_message: serialized_message,
97
- type: type,
98
- filter_by_type: (type == :any || !filter) ? false : type,
99
- nameserver: target_nameserver,
100
- attempts: self.attempts - 1,
101
- callback: callback,
102
- at: Time.now
103
- }
104
- else
105
- callback.call(nil)
106
- end
93
+ entry = @callback[@sequence] = {
94
+ id: @sequence,
95
+ serialized_message: serialized_message,
96
+ type: type,
97
+ filter_by_type: (type == :any || !filter) ? false : type,
98
+ nameservers: nameservers,
99
+ nameserver: nameservers.pop,
100
+ attempts: self.attempts,
101
+ callback: callback,
102
+ at: Time.now
103
+ }
107
104
 
105
+ send_request!(entry)
106
+
107
+ ensure
108
108
  @sequence += 1
109
109
  @sequence %= SEQUENCE_LIMIT
110
110
  end
@@ -124,6 +124,14 @@ class ReDNS::Connection < EventMachine::Connection
124
124
  EventMachine.add_periodic_timer(1) do
125
125
  check_for_timeouts!
126
126
  end
127
+
128
+ EventMachine.add_periodic_timer(30) do
129
+ update_nameserver_scores!
130
+ end
131
+ end
132
+
133
+ def peer_addr
134
+ Socket.unpack_sockaddr_in(self.get_peername)
127
135
  end
128
136
 
129
137
  # EventMachine: Called when data is received on the active socket.
@@ -131,6 +139,10 @@ class ReDNS::Connection < EventMachine::Connection
131
139
  message = ReDNS::Message.new(ReDNS::Buffer.new(data))
132
140
 
133
141
  if (callback = @callback.delete(message.id))
142
+ _, ip = self.peer_addr
143
+
144
+ self.nameserver_score[ip] += 1
145
+
134
146
  answers = message.answers
135
147
 
136
148
  if (type = callback[:filter_by_type])
@@ -146,10 +158,35 @@ class ReDNS::Connection < EventMachine::Connection
146
158
  end
147
159
 
148
160
  protected
149
- # Returns the next nameserver in the list for a given entry, wrapping around
150
- # to the beginning if required.
151
- def nameserver_after(nameserver)
152
- self.nameservers[(self.nameservers.index(nameserver).to_i + 1) % self.nameservers.length]
161
+ def nameservers_by_score
162
+ self.nameservers.sort_by do |nameserver|
163
+ self.nameserver_score[nameserver]
164
+ end
165
+ end
166
+
167
+ def send_request!(params)
168
+ rv = send_datagram(
169
+ params[:serialized_message],
170
+ params[:nameserver],
171
+ ReDNS::Support.dns_port
172
+ )
173
+
174
+ # A non-positive result means there was some kind of error.
175
+ params[:retry] = rv <= 0
176
+
177
+ rescue EventMachine::ConnectionError
178
+ # This is thrown if an invalid address is configured in the nameservers.
179
+ params[:retry] = true
180
+ ensure
181
+ params[:attempts] -= 1
182
+ end
183
+
184
+ def update_nameserver_scores!
185
+ # Sorts nameservers by least to most timeouts, also shaves number of
186
+ # timeouts in half to ignore temporary problems.
187
+ self.nameservers.each do |nameserver|
188
+ self.nameserver_score[nameserver] /= 2
189
+ end
153
190
  end
154
191
 
155
192
  # Checks all pending queries for timeouts and triggers callbacks or retries
@@ -157,32 +194,37 @@ protected
157
194
  def check_for_timeouts!
158
195
  timeout_at = Time.now - (@timeout || TIMEOUT_DEFAULT)
159
196
 
197
+ # Iterate over a copy of the keys to avoid issues with deleting entries
198
+ # from a Hash being iterated.
160
199
  @callback.keys.each do |k|
161
- if (params = @callback[k])
162
- if (params[:at] < timeout_at)
163
- if (params[:attempts] > 0)
164
- # If this request can be retried, find a different nameserver.
165
- target_nameserver = nameserver_after(params[:target_nameserver])
166
-
167
- # Send exactly the same request to it so that the request ID will
168
- # match to the same callback.
169
- send_datagram(
170
- params[:serialized_message],
171
- target_nameserver,
172
- ReDNS::Support.dns_port
173
- )
174
-
175
- params[:target_nameserver] = target_nameserver
176
- params[:attempts] -= 1
177
- else
178
- params[:callback].call(nil)
179
- @callback.delete(k)
180
- end
181
- end
182
- else
200
+ params = @callback[k]
201
+
202
+ unless (params)
183
203
  # Was missing params so should be deleted if not already removed.
184
204
  @callback.delete(k)
185
205
  end
206
+
207
+ if (params[:at] < timeout_at or params[:retry])
208
+ nameserver_score[params[:nameserver]] -= 1
209
+
210
+ if (params[:attempts] > 0)
211
+ if (params[:nameservers].empty?)
212
+ params[:nameservers] = self.nameservers_by_score
213
+ end
214
+
215
+ # If this request can be retried, find a different nameserver.
216
+ params[:nameserver] = params[:nameservers].pop
217
+
218
+ # Send exactly the same request to it so that the request ID will
219
+ # match to the same callback. The first successful response is
220
+ # considered valid.
221
+ send_request!(params)
222
+ else
223
+ params[:callback].call(nil)
224
+
225
+ @callback.delete(k)
226
+ end
227
+ end
186
228
  end
187
229
  end
188
230
  end
@@ -18,7 +18,7 @@ class ReDNS::Name < ReDNS::Fragment
18
18
  when String
19
19
  super(name: contents)
20
20
 
21
- unless (ReDNS::Support.is_ip?(name) or self.name.match(/\.$/))
21
+ unless (ReDNS::Support.is_ip?(name) or self.name.match(/\.\z/))
22
22
  self.name += '.'
23
23
  end
24
24
  else
@@ -64,8 +64,12 @@ class ReDNS::Name < ReDNS::Fragment
64
64
  # point and read from there, but preserve the position where the
65
65
  # pointer was found to leave the buffer in that final state.
66
66
 
67
- if (additional_offset = buffer.unpack('C')[0])
68
- pointer = (c & 0x3F << 8) | additional_offset
67
+
68
+
69
+ # The pointer is encoded as two sequential bytes representing the
70
+ # positional offset.
71
+ if (byte = buffer.unpack('C')[0])
72
+ pointer = (c & 0x3F) << 8 | byte
69
73
 
70
74
  return_to_offset ||= buffer.offset
71
75
  buffer.rewind
@@ -100,7 +104,7 @@ class ReDNS::Name < ReDNS::Fragment
100
104
  buffer.advance(return_to_offset)
101
105
  end
102
106
 
103
- self.name.encode!('UTF-8')
107
+ self.name.encode!('UTF-8', undef: :replace, invalid: :replace)
104
108
 
105
109
  self
106
110
  end
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: redns 0.2.0 ruby lib
5
+ # stub: redns 0.2.1 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "redns".freeze
9
- s.version = "0.2.0"
9
+ s.version = "0.2.1"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["tadman".freeze]
14
- s.date = "2017-09-14"
14
+ s.date = "2017-09-19"
15
15
  s.description = "ReDNS is a pure Ruby DNS library with drivers for reactor-model engines such as EventMachine".freeze
16
16
  s.email = "github@tadman.ca".freeze
17
17
  s.executables = ["redig".freeze]
@@ -46,6 +46,8 @@ Gem::Specification.new do |s|
46
46
  "lib/redns/resource.rb",
47
47
  "lib/redns/support.rb",
48
48
  "redns.gemspec",
49
+ "test/examples/aspmx4.googlemail.com",
50
+ "test/examples/postageapp.com.mx",
49
51
  "test/helper.rb",
50
52
  "test/test_redns.rb",
51
53
  "test/test_redns_address.rb",
@@ -13,4 +13,12 @@ require 'eventmachine'
13
13
  require 'redns'
14
14
 
15
15
  class Test::Unit::TestCase
16
+ def example_buffer(name)
17
+ ReDNS::Buffer.new(
18
+ File.open(
19
+ File.expand_path("examples/#{name}", File.dirname(__FILE__)),
20
+ 'r:BINARY'
21
+ ).read
22
+ )
23
+ end
16
24
  end
@@ -94,6 +94,24 @@ class TestReDNSConnection < Test::Unit::TestCase
94
94
  EventMachine.stop_event_loop
95
95
  end
96
96
  end
97
+
98
+ def test_broken_ns
99
+ address = :fail
100
+
101
+ EventMachine.run do
102
+ dns = ReDNS::Connection.instance do |c|
103
+ c.nameservers = '129.1.1.1'
104
+ end
105
+
106
+ dns.resolve('example.com') do |result|
107
+ address = result
108
+
109
+ EventMachine.stop_event_loop
110
+ end
111
+ end
112
+
113
+ assert_nil address
114
+ end
97
115
 
98
116
  def test_simple_attempts
99
117
  address = :fail
@@ -168,4 +168,12 @@ class TestReDNSMessage < Test::Unit::TestCase
168
168
 
169
169
  assert_equal 45532, question.id
170
170
  end
171
+
172
+ def test_decode_example
173
+ message = ReDNS::Message.new(example_buffer('postageapp.com.mx'))
174
+
175
+ message.answers.each do |answer|
176
+ assert_equal 'UTF-8', answer.rdata.to_a[0].encoding.to_s
177
+ end
178
+ end
171
179
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - tadman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-14 00:00:00.000000000 Z
11
+ date: 2017-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -74,6 +74,8 @@ files:
74
74
  - lib/redns/resource.rb
75
75
  - lib/redns/support.rb
76
76
  - redns.gemspec
77
+ - test/examples/aspmx4.googlemail.com
78
+ - test/examples/postageapp.com.mx
77
79
  - test/helper.rb
78
80
  - test/test_redns.rb
79
81
  - test/test_redns_address.rb