redns 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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