gitlab-net-dns 0.9.1

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