Class: Dnsruby::SingleResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/Dnsruby/SingleResolver.rb

Overview

Dnsruby::SingleResolver

The SingleResolver class targets a single resolver, and controls the sending of a single packet with a packet timeout. It performs no retries. Only two threads are used - the client thread and a select thread (which is reused across all queries).

Methods

Synchronous

These methods raise an exception or return a response message with rcode==NOERROR

  • Dnsruby::SingleResolver#send_message(msg [, use_tcp]))

  • Dnsruby::SingleResolver#query(name [, type [, klass]])

Asynchronous

These methods use a response queue, or an EventMachine::Deferrable to return the response and the error to the client. See Dnsruby::Resolver for details of how to enable the EventMachine implementation. More information about the EventMachine implementation is available in the EVENTMACHINE file in the Dnsruby distribution

  • Dnsruby::SingleResolver#send_async(…)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ SingleResolver

Can take a hash with the following optional keys :

  • :server

  • :port

  • :use_tcp

  • :ignore_truncation

  • :src_addr

  • :src_port

  • :udp_size

  • :persistent_tcp

  • :persistent_udp

  • :tsig

  • :packet_timeout

  • :recurse



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/Dnsruby/SingleResolver.rb', line 152

def initialize(*args)
  arg=args[0]
  @packet_timeout = Resolver::DefaultPacketTimeout
  @port = Resolver::DefaultPort
  @udp_size = Resolver::DefaultUDPSize
  @use_tcp = false
  @tsig = nil
  @ignore_truncation = false
  @src_addr        = '0.0.0.0'
  @src_port        = 0
  @recurse = true
  @persistent_udp = false
  @persistent_tcp = false
  @dnssec = false
  
  if (arg==nil)
    # Get default config
    config = Config.new
    @server = config.nameserver[0]
  elsif (arg.kind_of?String)
    @server=arg
  elsif (arg.kind_of?Hash)
    arg.keys.each do |attr|
      begin
        send(attr.to_s+"=", arg[attr])
      rescue Exception
        TheLog.error("Argument #{attr} not valid\n")
      end
      #        end
    end
  end
  #Check server is IP
  @server=Config.resolve_server(@server)
  
end

Instance Attribute Details

#ignore_truncationObject

Don’t worry if the response is truncated - return it anyway.

Defaults to false



60
61
62
# File 'lib/Dnsruby/SingleResolver.rb', line 60

def ignore_truncation
  @ignore_truncation
end

#packet_timeoutObject

Returns the value of attribute packet_timeout.



42
43
44
# File 'lib/Dnsruby/SingleResolver.rb', line 42

def packet_timeout
  @packet_timeout
end

#persistent_tcpObject

Should the TCP socket persist between queries?

Defaults to false



74
75
76
# File 'lib/Dnsruby/SingleResolver.rb', line 74

def persistent_tcp
  @persistent_tcp
end

#persistent_udpObject

Should the UDP socket persist between queries?

Defaults to false



79
80
81
# File 'lib/Dnsruby/SingleResolver.rb', line 79

def persistent_udp
  @persistent_udp
end

#portObject

The port on the resolver to send queries to.

Defaults to 53



47
48
49
# File 'lib/Dnsruby/SingleResolver.rb', line 47

def port
  @port
end

#recurseObject

should the Recursion Desired bit be set on queries?

Defaults to true



84
85
86
# File 'lib/Dnsruby/SingleResolver.rb', line 84

def recurse
  @recurse
end

#serverObject

The address of the resolver to send queries to



92
93
94
# File 'lib/Dnsruby/SingleResolver.rb', line 92

def server
  @server
end

#src_addressObject

The source address to send queries from

Defaults to localhost



65
66
67
# File 'lib/Dnsruby/SingleResolver.rb', line 65

def src_address
  @src_address
end

#src_portObject

The source port to send queries from

Defaults to 0 - random port



69
70
71
# File 'lib/Dnsruby/SingleResolver.rb', line 69

def src_port
  @src_port
end

#tsigObject

The TSIG record to sign/verify messages with



55
56
57
# File 'lib/Dnsruby/SingleResolver.rb', line 55

def tsig
  @tsig
end

#udp_sizeObject

The max UDP packet size

Defaults to 512



89
90
91
# File 'lib/Dnsruby/SingleResolver.rb', line 89

def udp_size
  @udp_size
end

#use_tcpObject

Use TCP rather than UDP as the transport.

Defaults to false



52
53
54
# File 'lib/Dnsruby/SingleResolver.rb', line 52

def use_tcp
  @use_tcp
end

Class Method Details

.get_tsig(args) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/Dnsruby/SingleResolver.rb', line 107

def SingleResolver.get_tsig(args)
  tsig = nil
  if (args.length == 1)
    if (args[0])
      if (args[0].instance_of?RR::TSIG)
        tsig = args[0]
      elsif (args[0].instance_of?Array)
        tsig = RR.new_from_hash({:type => Types.TSIG, :klass => Classes.ANY, :name => args[0][0], :key => args[0][1]})
      end
    else
      TheLog.info("TSIG signing switched off")
      return nil
    end
  elsif (args.length ==2)
    tsig = RR.new_from_hash({:type => Types.TSIG, :klass => Classes.ANY, :name => args[0], :key => args[1]})
  else
    raise ArgumentError.new("Wrong number of arguments to tsig=")
  end
  TheLog.info("TSIG signing now using #{tsig.name}, key=#{tsig.key}")
  return tsig
end

Instance Method Details

#check_response(response, response_bytes, query, client_queue, client_query_id, tcp) ⇒ Object



393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/Dnsruby/SingleResolver.rb', line 393

def check_response(response, response_bytes, query, client_queue, client_query_id, tcp)
  if (!check_tsig(query, response, response_bytes))
    return false
  end
  if (response.header.tc && !tcp)
    # Try to resend over tcp
    TheLog.debug("Truncated - resending over TCP")
    send_async(query, client_queue, client_query_id, true)
    return false
  end
  return true
end

#check_tsig(query, response, response_bytes) ⇒ Object



406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/Dnsruby/SingleResolver.rb', line 406

def check_tsig(query, response, response_bytes)
  if (query.tsig)
    if (response.tsig)
      if !query.tsig.verify(query, response, response_bytes)
        # Discard packet and wait for correctly signed response
        TheLog.error("TSIG authentication failed!")
        return false
      end
    else
      # Treated as having format error and discarded (RFC2845, 4.6)
      TheLog.error("Expecting TSIG signed response, but got unsigned response - discarding")
      return false
    end
  elsif (response.tsig)
    # Error - signed response to unsigned query
    TheLog.error("Signed response to unsigned query")
    return false
  end      
  return true
end

#closeObject



188
189
190
# File 'lib/Dnsruby/SingleResolver.rb', line 188

def close
  # @TODO@ What about closing?
end

#make_query_packet(packet, use_tcp) ⇒ Object

Prepare the packet for sending



428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
# File 'lib/Dnsruby/SingleResolver.rb', line 428

def make_query_packet(packet, use_tcp) #:nodoc: all
  if (packet.header.opcode == OpCode.QUERY || @recurse)
    packet.header.rd=true
  end
  
  if (@dnssec)
    # RFC 3225
    TheLog.debug(";; Adding EDNS extention with UDP packetsize #{udp_packet_size} and DNS OK bit set\n")
    optrr = RR::OPT.new(udp_packet_size)   # Decimal UDPpayload
    optrr.d_o=true
    
    packet.add_additional(optrr)
    
  elsif ((udp_packet_size > Resolver::DefaultUDPSize) && !use_tcp)
    TheLog.debug(";; Adding EDNS extention with UDP packetsize  #{udp_packet_size}.\n")
    # RFC 3225
    optrr = RR::OPT.new(udp_packet_size)
    
    packet.add_additional(optrr)
  end
  
  if (@tsig && !packet.signed?)
    @tsig.apply(packet)
  end
  return packet.encode
end

#query(name, type = Types.A, klass = Classes.IN) ⇒ Object

Synchronously send a query for the given name. The type will default to A, and the class to IN.



194
195
196
197
198
199
# File 'lib/Dnsruby/SingleResolver.rb', line 194

def query(name, type=Types.A, klass=Classes.IN)
  msg = Message.new
  msg.header.rd = 1
  msg.add_question(name, type, klass)
  return send_message(msg)
end

#send_async(*args) ⇒ Object

Asynchronously send a Message to the server. The send can be done using just Dnsruby, or using EventMachine.

Dnsruby pure Ruby event loop :

A client_queue is supplied by the client, along with an optional client_query_id to identify the response. The client_query_id is generated, if not supplied, and returned to the client. When the response is known, the tuple (query_id, response_message, response_exception) is put in the queue for the client to process.

The query is sent synchronously in the caller’s thread. The select thread is then used to listen for and process the response (up to pushing it to the client_queue). The client thread is then used to retrieve the response and deal with it.

Takes :

  • msg - the message to send

  • client_queue - a Queue to push the response to, when it arrives

  • client_query_id - an optional ID to identify the query to the client

  • use_tcp - whether to use TCP (defaults to SingleResolver.use_tcp)

Returns :

  • client_query_id - to identify the query response to the client. This ID is

generated if it is not passed in by the client

If the native Dsnruby networking layer is being used, then this method returns the client_query_id

id = res.send_async(msg, queue)
NOT SUPPORTED : id = res.send_async(msg, queue, use_tcp)
id = res.send_async(msg, queue, id)
id = res.send_async(msg, queue, id, use_tcp)

If EventMachine is being used :

If EventMachine is being used (see Dnsruby::Resolver::use_eventmachine),then this method returns an EM::Deferrable object. If a queue (and ID) is passed in, then the response will also be pushed to the Queue (as well as the deferrable completing).

deferrable = res.send_async(msg)
deferrable = res.send_async(msg, use_tcp)
deferrable = res.send_async(msg, q, id, use_tcp)


263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/Dnsruby/SingleResolver.rb', line 263

def send_async(*args) # msg, client_queue, client_query_id, use_tcp=@use_tcp)
  # @TODO@ Need to select a good Header ID here - see forgery-resilience RFC draft for details
  msg = args[0]
  client_query_id = nil
  client_queue = nil
  use_tcp = @use_tcp
  if (msg.kind_of?String)
    msg = Message.new(msg)
  end
  query_packet = make_query_packet(msg, use_tcp)
  if (udp_packet_size < query_packet.length)
    TheLog.debug("Query packet length exceeds max UDP packet size - using TCP")
    use_tcp = true
  end
  if (args.length > 1)
    if (args[1].class==Queue)
      client_queue = args[1]
    elsif (args.length == 2)
      use_tcp = args[1]
    end
    if (args.length > 2)
      client_query_id = args[2]
      if (args.length > 3)
        use_tcp = args[3]
      end
    end
  end
  # Need to keep track of the request mac (if using tsig) so we can validate the response (RFC2845 4.1)
  #Are we using EventMachine or native Dnsruby?
  if (Resolver.eventmachine?)
    return send_eventmachine(query_packet, msg, client_query_id, client_queue, use_tcp)
  else
    if (!client_query_id)
      client_query_id = Time.now + rand(10000)
    end
    send_dnsruby(query_packet, msg, client_query_id, client_queue, use_tcp)
    return client_query_id
  end
end

#send_dnsruby(query_bytes, query, client_query_id, client_queue, use_tcp) ⇒ Object

This method sends the packet using the built-in pure Ruby event loop, with no dependencies.



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/Dnsruby/SingleResolver.rb', line 337

def send_dnsruby(query_bytes, query, client_query_id, client_queue, use_tcp) #:nodoc: all
  endtime = Time.now + @packet_timeout
  # First send the query (synchronously)
  # @TODO@ persisent sockets
  st = SelectThread.instance
  socket = nil
  begin
    #@TODO@ Different OSes have different interpretations of "random port" here.
    #Apparently, Linux will just give you the same port as last time, unless it is still
    #open, in which case you get n+1.
    #We need to determine an actual (random) number here, then ask the OS for it, and
    #continue until we get one.
    if (use_tcp) 
      socket = TCPSocket.new(@server, @port, @src_addr, @src_port)
    else
      socket = UDPSocket.new()
      socket.bind(@src_addr, @src_port)
      socket.connect(@server, @port)
    end
  rescue Exception => e
    if (socket!=nil)
      socket.close
    end
    err=IOError.new("dnsruby can't connect to #{@server}:#{@port} from #{@src_addr}:#{@src_port}, use_tcp=#{use_tcp}, exception = #{e.class}, #{e}")
    TheLog.error("#{err}")
    st.push_exception_to_select(client_query_id, client_queue, err, nil) # @TODO Do we still need this? Can we not just send it from here?
    return
  end
  if (socket==nil)
    err=IOError.new("dnsruby can't connect to #{@server}:#{@port} from #{@src_addr}:#{@src_port}, use_tcp=#{use_tcp}")
    TheLog.error("#{err}")
    st.push_exception_to_select(client_query_id, client_queue, err, nil) # @TODO Do we still need this? Can we not just send it from here?
    return
  end
  TheLog.debug("Sending packet to #{@server}:#{@port} from #{@src_addr}:#{@src_port}, use_tcp=#{use_tcp}")
  begin
    if (use_tcp)
      lenmsg = [query_bytes.length].pack('n')
      socket.send(lenmsg, 0)
    end
    socket.send(query_bytes, 0)
  rescue Exception => e
    socket.close
    err=IOError.new("Send failed to #{@server}:#{@port} from #{@src_addr}:#{@src_port}, use_tcp=#{use_tcp}, exception : #{e}")
    TheLog.error("#{err}")
    st.push_exception_to_select(client_query_id, client_queue, err, nil)
    return
  end
  
  # Then listen for the response
  query_settings = SelectThread::QuerySettings.new(query_bytes, query, @ignore_truncation, client_queue, client_query_id, socket, @server, @port, endtime, udp_packet_size, self)
  # The select thread will now wait for the response and send that or a timeout
  # back to the client_queue.
  st.add_to_select(query_settings)
end

#send_eventmachine(msg_bytes, msg, client_query_id, client_queue, use_tcp, client_deferrable = nil, packet_timeout = @packet_timeout) ⇒ Object

This method sends the packet using EventMachine



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/Dnsruby/SingleResolver.rb', line 304

def send_eventmachine(msg_bytes, msg, client_query_id, client_queue, use_tcp, client_deferrable=nil, packet_timeout = @packet_timeout) #:nodoc: all
  start_time = Time.now
  if (!client_deferrable)
    client_deferrable = EventMachine::DefaultDeferrable.new
  end
  packet_deferrable = EventMachineInterface.send(:msg=>msg_bytes, :timeout=>packet_timeout, :server=>@server, :port=>@port, :src_addr=>@src_addr, :src_port=>@src_port, :use_tcp=>use_tcp)
  packet_deferrable.callback { |response, response_bytes|
    ret = true
    if (response.header.tc && !use_tcp && !@ignore_truncation)
      # Try to resend over tcp
      TheLog.debug("Truncated - resending over TCP")
      send_eventmachine(msg_bytes, msg, client_query_id, client_queue, true, client_deferrable, packet_timeout - (Time.now-start_time))
    else
      if (!check_tsig(msg, response, response_bytes))
        send_eventmachine(msg_bytes, msg, client_query_id, client_queue, true, client_deferrable, packet_timeout - (Time.now-start_time))
        return
      end
      client_deferrable.set_deferred_status :succeeded, response
      if (client_queue)
        client_queue.push([client_query_id, response, nil])
      end
    end
  }
  packet_deferrable.errback { |response, error|
    client_deferrable.set_deferred_status :failed, response, error
    if (client_queue)
      client_queue.push([client_query_id, response, error])
    end
  }
  return client_deferrable
end

#send_message(msg, use_tcp = @use_tcp) ⇒ Object

Synchronously send a Message to the server. If a valid response is returned, then that is returned to the client. Otherwise a ResolvError or ResolvTimeout will be thrown.

Takes the message to send, and an optional use_tcp parameter which defaults to SingleResolver.use_tcp



208
209
210
211
212
213
214
215
216
217
# File 'lib/Dnsruby/SingleResolver.rb', line 208

def send_message(msg, use_tcp=@use_tcp)
  q = Queue.new
  send_async(msg, q, Time.now + rand(10000), use_tcp)
  id, msg, error = q.pop
  if (error != nil)
    raise error
  else
    return msg
  end
end

#udp_packet_sizeObject

Return the packet size to use for UDP



456
457
458
459
460
461
# File 'lib/Dnsruby/SingleResolver.rb', line 456

def udp_packet_size
  # if @udp_size > DefaultUDPSize then we use EDNS and 
  # @udp_size should be taken as the maximum packet_data length
  ret = (@udp_size > Resolver::DefaultUDPSize ? @udp_size : Resolver::DefaultUDPSize) 
  return ret
end