gitlab-net-dns 0.9.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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.gitlab-ci.yml +20 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +3 -0
  6. data/.rubocop_defaults.yml +359 -0
  7. data/.rubocop_todo.yml +207 -0
  8. data/.travis.yml +13 -0
  9. data/CHANGELOG.md +113 -0
  10. data/Gemfile +9 -0
  11. data/LICENSE.txt +56 -0
  12. data/README.md +172 -0
  13. data/Rakefile +38 -0
  14. data/THANKS.rdoc +24 -0
  15. data/bin/console +14 -0
  16. data/demo/check_soa.rb +104 -0
  17. data/demo/threads.rb +18 -0
  18. data/gitlab-net-dns.gemspec +24 -0
  19. data/lib/net/dns.rb +104 -0
  20. data/lib/net/dns/header.rb +705 -0
  21. data/lib/net/dns/names.rb +120 -0
  22. data/lib/net/dns/packet.rb +560 -0
  23. data/lib/net/dns/question.rb +185 -0
  24. data/lib/net/dns/resolver.rb +1214 -0
  25. data/lib/net/dns/resolver/socks.rb +148 -0
  26. data/lib/net/dns/resolver/timeouts.rb +70 -0
  27. data/lib/net/dns/rr.rb +356 -0
  28. data/lib/net/dns/rr/a.rb +114 -0
  29. data/lib/net/dns/rr/aaaa.rb +94 -0
  30. data/lib/net/dns/rr/classes.rb +130 -0
  31. data/lib/net/dns/rr/cname.rb +74 -0
  32. data/lib/net/dns/rr/hinfo.rb +96 -0
  33. data/lib/net/dns/rr/mr.rb +70 -0
  34. data/lib/net/dns/rr/mx.rb +82 -0
  35. data/lib/net/dns/rr/ns.rb +70 -0
  36. data/lib/net/dns/rr/null.rb +50 -0
  37. data/lib/net/dns/rr/ptr.rb +77 -0
  38. data/lib/net/dns/rr/soa.rb +75 -0
  39. data/lib/net/dns/rr/srv.rb +41 -0
  40. data/lib/net/dns/rr/txt.rb +58 -0
  41. data/lib/net/dns/rr/types.rb +191 -0
  42. data/lib/net/dns/version.rb +8 -0
  43. data/spec/fixtures/resolv.conf +4 -0
  44. data/spec/spec_helper.rb +14 -0
  45. data/spec/unit/resolver/dns_timeout_spec.rb +36 -0
  46. data/spec/unit/resolver/tcp_timeout_spec.rb +46 -0
  47. data/spec/unit/resolver/udp_timeout_spec.rb +46 -0
  48. data/test/test_helper.rb +13 -0
  49. data/test/unit/header_test.rb +164 -0
  50. data/test/unit/names_test.rb +21 -0
  51. data/test/unit/packet_test.rb +47 -0
  52. data/test/unit/question_test.rb +81 -0
  53. data/test/unit/resolver_test.rb +114 -0
  54. data/test/unit/rr/a_test.rb +106 -0
  55. data/test/unit/rr/aaaa_test.rb +102 -0
  56. data/test/unit/rr/classes_test.rb +83 -0
  57. data/test/unit/rr/cname_test.rb +90 -0
  58. data/test/unit/rr/hinfo_test.rb +111 -0
  59. data/test/unit/rr/mr_test.rb +99 -0
  60. data/test/unit/rr/mx_test.rb +106 -0
  61. data/test/unit/rr/ns_test.rb +80 -0
  62. data/test/unit/rr/types_test.rb +71 -0
  63. data/test/unit/rr_test.rb +127 -0
  64. metadata +172 -0
@@ -0,0 +1,148 @@
1
+ require 'socket'
2
+ require 'ipaddr'
3
+
4
+ class RawSocket # :nodoc:
5
+ @@id_arr = []
6
+
7
+ def initialize(src_addr, dest_addr)
8
+ # Define socket
9
+ begin
10
+ @socket = Socket.new PF_INET, SOCK_RAW, IPPROTO_RAW
11
+ rescue SystemCallError => e
12
+ raise SystemCallError, "You must be root to use raw sockets! #{e}"
13
+ end
14
+
15
+ @socket.setsockopt IPPROTO_IP, IP_HDRINCL, 1
16
+
17
+ # Checks addresses
18
+ @src_addr = check_addr src_addr
19
+ @dest_addr = check_addr dest_addr
20
+
21
+ # Source and destination port are zero
22
+ @src_port = 0
23
+ @dest_port = 0
24
+
25
+ # Set correct protocol version in the header
26
+ @version = @dest_addr.ipv4? ? "0100" : "0110"
27
+
28
+ # Total lenght: must be overridden by subclasses
29
+ @tot_lenght = 20
30
+
31
+ # Protocol: must be overridden by subclasses
32
+ @protocol = 1 # ICMP by default
33
+
34
+ # Generate a new id
35
+ # @id = genID
36
+ @id = 1234
37
+
38
+ # Generate peer sockaddr
39
+ @to = Socket.pack_sockaddr_in @dest_port, @dest_addr.to_s
40
+ end
41
+
42
+ def send(payload = '')
43
+ packet = make_ip_header([
44
+ [@version + '0101', 'B8'], # version, hlen
45
+ [0, 'C'], # tos
46
+ [@tot_lenght + payload.size, 'n'], # total len
47
+ [@id, 'n'], # id
48
+ [0, 'n'], # flags, offset
49
+ [64, 'C'], # ttl
50
+ [@protocol, 'C'], # protocol
51
+ [0, 'n'], # checksum
52
+ [@src_addr.to_i, 'N'], # source
53
+ [@dest_addr.to_i, 'N'], # destination
54
+ ])
55
+ packet << make_transport_header(payload.size)
56
+ packet << [payload].pack("a*")
57
+ @socket.send(packet, 0, @to)
58
+ end
59
+
60
+ private
61
+
62
+ def check_addr(addr)
63
+ case addr
64
+ when String
65
+ IPAddr.new(addr)
66
+ when IPAddr
67
+ addr
68
+ else
69
+ raise ArgumentError, "Wrong address format: #{addr}"
70
+ end
71
+ end
72
+
73
+ def check_port(port)
74
+ if (1..65_535).cover?(port) && port.is_a?(Integer)
75
+ port
76
+ else
77
+ raise ArgumentError, "Port #{port} not valid"
78
+ end
79
+ end
80
+
81
+ def genID
82
+ while @@id_arr.include?(q = rand(65_535))
83
+ end
84
+ @@id_arr.push(q)
85
+ q
86
+ end
87
+
88
+ def ipchecksum(data)
89
+ checksum = data.unpack("n*").inject(0) { |s, x| s + x }
90
+ ((checksum >> 16) + (checksum & 0xffff)) ^ 0xffff
91
+ end
92
+
93
+ def make_ip_header(parts)
94
+ template = ''
95
+ data = []
96
+ parts.each do |part|
97
+ data += part[0..-2]
98
+ template << part[-1]
99
+ end
100
+ data_str = data.pack(template)
101
+ checksum = ipchecksum(data_str)
102
+ data[-3] = checksum
103
+ data.pack(template)
104
+ end
105
+
106
+ def make_transport_header
107
+ ""
108
+ end
109
+ end
110
+
111
+ class UdpRawSocket < RawSocket # :nodoc:
112
+ def initialize(src_addr, src_port, dest_addr, dest_port)
113
+ super(src_addr, dest_addr)
114
+
115
+ # Check ports
116
+ @src_port = check_port src_port
117
+ @dest_port = check_port dest_port
118
+
119
+ # Total lenght: must be overridden by subclasses
120
+ @tot_lenght = 20 + 8 # 8 bytes => UDP Header
121
+
122
+ # Protocol: must be overridden by subclasses
123
+ @protocol = 17 # UDP protocol
124
+
125
+ @to = Socket.pack_sockaddr_in @dest_port, @dest_addr.to_s
126
+ end
127
+
128
+ private
129
+
130
+ def make_udp_header(parts)
131
+ template = ''
132
+ data = []
133
+ parts.each do |part|
134
+ data += part[0..-2]
135
+ template << part[-1]
136
+ end
137
+ data.pack(template)
138
+ end
139
+
140
+ def make_transport_header(pay_size)
141
+ make_udp_header([
142
+ [@src_port, 'n'], # source port
143
+ [@dest_port, 'n'], # destination port
144
+ [8 + pay_size, 'n'], # len
145
+ [0, 'n'] # checksum (mandatory)
146
+ ])
147
+ end
148
+ end
@@ -0,0 +1,70 @@
1
+ require 'timeout'
2
+
3
+ module Net # :nodoc:
4
+ module DNS
5
+ class Resolver
6
+ class DnsTimeout
7
+ attr_reader :seconds
8
+
9
+ def initialize(seconds)
10
+ if seconds.is_a?(Numeric) && seconds >= 0
11
+ @seconds = seconds
12
+ else
13
+ raise ArgumentError, "Invalid value for tcp timeout"
14
+ end
15
+ end
16
+
17
+ # Returns a string representation of the timeout corresponding
18
+ # to the number of <tt>@seconds</tt>.
19
+ def to_s
20
+ @seconds == 0 ? @output.to_s : @seconds.to_s
21
+ end
22
+
23
+ def pretty_to_s
24
+ transform(@seconds)
25
+ end
26
+
27
+ # Executes the method's block. If the block execution terminates before +sec+
28
+ # seconds has passed, it returns true. If not, it terminates the execution
29
+ # and raises Timeout::Error.
30
+ # If @seconds is 0 or nil, no timeout is set.
31
+ def timeout(&block)
32
+ raise LocalJumpError, "no block given" unless block_given?
33
+
34
+ Timeout.timeout(@seconds, &block)
35
+ end
36
+
37
+ private
38
+
39
+ def transform(secs)
40
+ case secs
41
+ when 0
42
+ to_s
43
+ when 1..59
44
+ "#{secs} seconds"
45
+ when 60..3559
46
+ "#{secs / 60} minutes and #{secs % 60} seconds"
47
+ else
48
+ hours = secs / 3600
49
+ secs -= (hours * 3600)
50
+ "#{hours} hours, #{secs / 60} minutes and #{secs % 60} seconds"
51
+ end
52
+ end
53
+ end
54
+
55
+ class TcpTimeout < DnsTimeout
56
+ def initialize(seconds)
57
+ @output = "infinite"
58
+ super
59
+ end
60
+ end
61
+
62
+ class UdpTimeout < DnsTimeout
63
+ def initialize(seconds)
64
+ @output = "not defined"
65
+ super
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,356 @@
1
+ require 'ipaddr'
2
+ require_relative 'names'
3
+ require_relative 'rr/types'
4
+ require_relative 'rr/classes'
5
+
6
+ %w[a aaaa cname hinfo mr mx ns ptr soa srv txt].each do |file|
7
+ require_relative "rr/#{file}"
8
+ end
9
+
10
+ module Net
11
+ module DNS
12
+ #
13
+ # = Net::DNS::RR - DNS Resource Record class
14
+ #
15
+ # The Net::DNS::RR is the base class for DNS Resource
16
+ # Record (RR) objects. A RR is a pack of data that represents
17
+ # resources for a DNS zone. The form in which this data is
18
+ # shows can be drawed as follow:
19
+ #
20
+ # "name ttl class type data"
21
+ #
22
+ # The +name+ is the name of the resource, like an canonical
23
+ # name for an +A+ record (internet ip address). The +ttl+ is the
24
+ # time to live, expressed in seconds. +type+ and +class+ are
25
+ # respectively the type of resource (+A+ for ip addresses, +NS+
26
+ # for nameservers, and so on) and the class, which is almost
27
+ # always +IN+, the Internet class. At the end, +data+ is the
28
+ # value associated to the name for that particular type of
29
+ # resource record. An example:
30
+ #
31
+ # # A record for IP address
32
+ # "www.example.com 86400 IN A 172.16.100.1"
33
+ #
34
+ # # NS record for name server
35
+ # "www.example.com 86400 IN NS ns.example.com"
36
+ #
37
+ # A new RR object can be created in 2 ways: passing a string
38
+ # such the ones above, or specifying each field as the pair
39
+ # of an hash. See the Net::DNS::RR.new method for details.
40
+ #
41
+ class RR
42
+ include Names
43
+
44
+ # Base error class.
45
+ class Error < StandardError
46
+ end
47
+
48
+ # Error in parsing binary data, maybe from a malformed packet.
49
+ class DataError < Error
50
+ end
51
+
52
+ # Regexp matching an RR string
53
+ RR_REGEXP = Regexp.new("^\\s*(\\S+)\\s*(\\d+)?\\s+(" +
54
+ Net::DNS::RR::Classes.regexp +
55
+ "|CLASS\\d+)?\\s*(" +
56
+ Net::DNS::RR::Types.regexp +
57
+ "|TYPE\\d+)?\\s*(.*)$", Regexp::IGNORECASE)
58
+
59
+ # Dimension of the sum of class, type, TTL and rdlength fields in a
60
+ # RR portion of the packet, in bytes
61
+ RRFIXEDSZ = 10
62
+
63
+ # Create a new instance of Net::DNS::RR class, or an instance of
64
+ # any of the subclass of the appropriate type.
65
+ #
66
+ # Argument can be a string or an hash. With a sting, we can pass
67
+ # a RR resource record in the canonical format:
68
+ #
69
+ # a = Net::DNS::RR.new("foo.example.com. 86400 A 10.1.2.3")
70
+ # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
71
+ # cname = Net::DNS::RR.new("www.example.com 300 IN CNAME www1.example.com")
72
+ # txt = Net::DNS::RR.new('baz.example.com 3600 HS TXT "text record"')
73
+ #
74
+ # Incidentally, +a+, +mx+, +cname+ and +txt+ objects will be instances of
75
+ # respectively Net::DNS::RR::A, Net::DNS::RR::MX, Net::DNS::RR::CNAME and
76
+ # Net::DNS::RR::TXT classes.
77
+ #
78
+ # The name and RR data are required; all other informations are optional.
79
+ # If omitted, the +TTL+ defaults to 10800, +type+ default to +A+ and the RR class
80
+ # defaults to +IN+. Omitting the optional fields is useful for creating the
81
+ # empty RDATA sections required for certain dynamic update operations.
82
+ # All names must be fully qualified. The trailing dot (.) is optional.
83
+ #
84
+ # The preferred method is however passing an hash with keys and values:
85
+ #
86
+ # rr = Net::DNS::RR.new(
87
+ # :name => "foo.example.com",
88
+ # :ttl => 86400,
89
+ # :cls => "IN",
90
+ # :type => "A",
91
+ # :address => "10.1.2.3"
92
+ # )
93
+ #
94
+ # rr = Net::DNS::RR.new(
95
+ # :name => "foo.example.com",
96
+ # :rdata => "10.1.2.3"
97
+ # )
98
+ #
99
+ # Name and data are required; all the others fields are optionals like
100
+ # we've seen before. The data field can be specified either with the
101
+ # right name of the resource (+:address+ in the example above) or with
102
+ # the generic key +:rdata+. Consult documentation to find the exact name
103
+ # for the resource in each subclass.
104
+ #
105
+ def initialize(arg)
106
+ instance = case arg
107
+ when String
108
+ new_from_string(arg)
109
+ when Hash
110
+ new_from_hash(arg)
111
+ else
112
+ raise ArgumentError, "Invalid argument, must be a RR string or an hash of values"
113
+ end
114
+
115
+ if @type.to_s == "ANY"
116
+ @cls = Net::DNS::RR::Classes.new("IN")
117
+ end
118
+
119
+ build_pack
120
+ set_type
121
+
122
+ instance
123
+ end
124
+
125
+ # Return a new RR object of the correct type (like Net::DNS::RR::A
126
+ # if the type is A) from a binary string, usually obtained from
127
+ # network stream.
128
+ #
129
+ # This method is used when parsing a binary packet by the Packet
130
+ # class.
131
+ #
132
+ def self.parse(data)
133
+ o = allocate
134
+ obj, offset = o.send(:new_from_binary, data, 0)
135
+ obj
136
+ end
137
+
138
+ # Same as RR.parse, but takes an entire packet binary data to
139
+ # perform name expansion. Default when analizing a packet
140
+ # just received from a network stream.
141
+ #
142
+ # Return an instance of appropriate class and the offset
143
+ # pointing at the end of the data parsed.
144
+ #
145
+ def self.parse_packet(data, offset)
146
+ o = allocate
147
+ o.send(:new_from_binary, data, offset)
148
+ end
149
+
150
+ attr_reader :name
151
+
152
+ attr_reader :ttl
153
+
154
+ # Type accessor
155
+ def type
156
+ @type.to_s
157
+ end
158
+
159
+ # Class accessor
160
+ def cls
161
+ @cls.to_s
162
+ end
163
+
164
+ def value
165
+ get_inspect
166
+ end
167
+
168
+ # Data belonging to that appropriate class,
169
+ # not to be used (use real accessors instead)
170
+ attr_reader :rdata
171
+
172
+ # Return the RR object in binary data format, suitable
173
+ # for using in network streams.
174
+ #
175
+ # raw_data = rr.data
176
+ # puts "RR is #{raw_data.size} bytes long"
177
+ #
178
+ def data
179
+ str = pack_name(@name)
180
+ str + [@type.to_i, @cls.to_i, ttl, @rdlength].pack("n2 N n") + get_data
181
+ end
182
+
183
+ # Return the RR object in binary data format, suitable
184
+ # for using in network streams, with names compressed.
185
+ # Must pass as arguments the offset inside the packet
186
+ # and an hash of compressed names.
187
+ #
188
+ # This method is to be used in other classes and is
189
+ # not intended for user space programs.
190
+ #
191
+ # TO FIX in one of the future releases
192
+ #
193
+ def comp_data(offset, compnames)
194
+ str, offset, names = dn_comp(@name, offset, compnames)
195
+ str += [@type.to_i, @cls.to_i, ttl, @rdlength].pack("n2 N n")
196
+ offset += Net::DNS::RRFIXEDSZ
197
+ [str, offset, names]
198
+ end
199
+
200
+ # Returns a human readable representation of this record.
201
+ # The value is always a String.
202
+ #
203
+ # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
204
+ # #=> example.com. 7200 IN MX 10 mailhost.example.com.
205
+ #
206
+ def inspect
207
+ to_s
208
+ end
209
+
210
+ # Returns a String representation of this record.
211
+ #
212
+ # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
213
+ # mx.to_s
214
+ # #=> "example.com. 7200 IN MX 10 mailhost.example.com."
215
+ #
216
+ def to_s
217
+ items = to_a.map(&:to_s)
218
+ if @name.size < 24
219
+ items.pack("A24 A8 A8 A8 A*")
220
+ else
221
+ items.join(" ")
222
+ end.to_s
223
+ end
224
+
225
+ # Returns an Array with all the attributes for this record.
226
+ #
227
+ # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
228
+ # mx.to_a
229
+ # #=> ["example.com.", 7200, "IN", "MX", "10 mailhost.example.com."]
230
+ #
231
+ def to_a
232
+ [name, ttl, cls.to_s, type.to_s, value]
233
+ end
234
+
235
+ private
236
+
237
+ def new_from_string(rrstring)
238
+ unless rrstring =~ RR_REGEXP
239
+ raise ArgumentError,
240
+ "Format error for RR string (maybe CLASS and TYPE not valid?)"
241
+ end
242
+
243
+ # Name of RR - mandatory
244
+ begin
245
+ @name = Regexp.last_match(1).downcase
246
+ rescue NoMethodError
247
+ raise ArgumentError, "Missing name field in RR string #{rrstring}"
248
+ end
249
+
250
+ # Time to live for RR, default 3 hours
251
+ @ttl = Regexp.last_match(2) ? Regexp.last_match(2).to_i : 10_800
252
+
253
+ # RR class, default to IN
254
+ @cls = Net::DNS::RR::Classes.new Regexp.last_match(3)
255
+
256
+ # RR type, default to A
257
+ @type = Net::DNS::RR::Types.new Regexp.last_match(4)
258
+
259
+ # All the rest is data
260
+ @rdata = Regexp.last_match(5) ? Regexp.last_match(5).strip : ""
261
+
262
+ if self.class == Net::DNS::RR
263
+ Net::DNS::RR.const_get(@type.to_s).new(rrstring)
264
+ else
265
+ subclass_new_from_string(@rdata)
266
+ self.class
267
+ end
268
+ end
269
+
270
+ def new_from_hash(args)
271
+ # Name field is mandatory
272
+ unless args.key? :name
273
+ raise ArgumentError, ":name field is mandatory"
274
+ end
275
+
276
+ @name = args[:name].downcase
277
+ @ttl = args[:ttl] ? args[:ttl].to_i : 10_800 # Default 3 hours
278
+ @type = Net::DNS::RR::Types.new args[:type]
279
+ @cls = Net::DNS::RR::Classes.new args[:cls]
280
+
281
+ @rdata = args[:rdata] ? args[:rdata].strip : ""
282
+ @rdlength = args[:rdlength] || @rdata.size
283
+
284
+ if self.class == Net::DNS::RR
285
+ Net::DNS::RR.const_get(@type.to_s).new(args)
286
+ else
287
+ hash = args.delete_if { |k, _| %i[name ttl type cls].include?(k) }
288
+ if hash.key? :rdata
289
+ subclass_new_from_string(hash[:rdata])
290
+ else
291
+ subclass_new_from_hash(hash)
292
+ end
293
+ self.class
294
+ end
295
+ end
296
+
297
+ def new_from_binary(data, offset)
298
+ if self.class == Net::DNS::RR
299
+ temp = dn_expand(data, offset)[1]
300
+ type = Net::DNS::RR::Types.new data.unpack("@#{temp} n")[0]
301
+ (eval "Net::DNS::RR::#{type}").parse_packet(data, offset)
302
+ else
303
+ @name, offset = dn_expand(data, offset)
304
+ rrtype, cls, @ttl, @rdlength = data.unpack("@#{offset} n2 N n")
305
+ @type = Net::DNS::RR::Types.new rrtype
306
+ @cls = Net::DNS::RR::Classes.new cls
307
+ offset += RRFIXEDSZ
308
+ offset = subclass_new_from_binary(data, offset)
309
+ build_pack
310
+ set_type
311
+ [self, offset]
312
+ end
313
+ end
314
+
315
+ # Methods to be overridden by subclasses
316
+ def subclass_new_from_array(arr)
317
+ end
318
+
319
+ def subclass_new_from_string(str)
320
+ end
321
+
322
+ def subclass_new_from_hash(hash)
323
+ end
324
+
325
+ def subclass_new_from_binary(data, offset)
326
+ end
327
+
328
+ def build_pack
329
+ end
330
+
331
+ def get_inspect
332
+ @rdata
333
+ end
334
+
335
+ def get_data
336
+ @rdata
337
+ end
338
+
339
+ def set_type
340
+ # TODO: Here we should probably
341
+ # raise NotImplementedError
342
+ # if we want the method to be implemented in any subclass.
343
+ end
344
+
345
+ def self.new(*args)
346
+ o = allocate
347
+ obj = o.send(:initialize, *args)
348
+ if self == Net::DNS::RR
349
+ obj
350
+ else
351
+ o
352
+ end
353
+ end
354
+ end
355
+ end
356
+ end