Class: Dnsruby::Message

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

Overview

Defines a DNS packet.

RFC 1035 Section 4.1, RFC 2136 Section 2, RFC 2845

Sections

Message objects have five sections:

  • The header section, a Dnsruby::Header object.

    msg.header=Header.new(...)
    header = msg.header
    
  • The question section, an array of Dnsruby::Question objects.

    msg.add_question(Question.new(domain, type, klass))
    msg.each_question do |question|  ....   end
    
  • The answer section, an array of Dnsruby::RR objects.

    msg.add_answer(RR.create({:name    => "a2.example.com",
    

:type => “A”, :address => “10.0.0.2”}))

msg.each_answer {|answer| ... }
  • The authority section, an array of Dnsruby::RR objects.

    msg.add_authority(rr)
    msg.each_authority {|rr| ... }
    
  • The additional section, an array of Dnsruby::RR objects.

    msg.add_additional(rr)
    msg.each_additional {|rr| ... }
    

In addition, each_resource iterates the answer, additional and authority sections :

msg.each_resource {|rr| ... }

Packet format encoding

Dnsruby::Message#encode
Dnsruby::Message::decode(data)

Additional information

security_level records the current DNSSEC status of this Message. answerfrom records the server which this Message was received from. cached records whether this response came from the cache.

Direct Known Subclasses

Update

Defined Under Namespace

Classes: Section, SecurityLevel

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Message

Create a new Message. Takes optional name, type and class

type defaults to A, and klass defaults to IN

  • Dnsruby::Message.new(“example.com”) # defaults to A, IN

  • Dnsruby::Message.new(“example.com”, ‘AAAA’)

  • Dnsruby::Message.new(“example.com”, Dnsruby::Types.PTR, “HS”)



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/Dnsruby/message.rb', line 189

def initialize(*args)
  @header = Header.new()
  #      @question = Section.new(self)
  @question = []
  @answer = Section.new(self)
  @authority = Section.new(self)
  @additional = Section.new(self)
  @tsigstate = :Unsigned
  @signing = false
  @tsigkey = nil
  @answerfrom = nil
  @answerip = nil
  @send_raw = false
  @do_validation = true
  @do_caching = true
  @security_level = SecurityLevel.UNCHECKED
  @security_error = nil
  @cached = false
  type = Types::A
  klass = Classes::IN
  if (args.length > 0)
    name = args[0]
    if (args.length > 1)
      type = Types.new(args[1])
      if (args.length > 2)
        klass = Classes.new(args[2])
      end
    end
    add_question(name, type, klass)
  end
end

Instance Attribute Details

#additionalObject (readonly)

The additional section, an array of Dnsruby::RR objects.



229
230
231
# File 'lib/Dnsruby/message.rb', line 229

def additional
  @additional
end

#answerObject (readonly) Also known as: pre

The answer section, an array of Dnsruby::RR objects.



225
226
227
# File 'lib/Dnsruby/message.rb', line 225

def answer
  @answer
end

#answerfromObject

If this Message is a response from a server, then answerfrom contains the address of the server



234
235
236
# File 'lib/Dnsruby/message.rb', line 234

def answerfrom
  @answerfrom
end

#answeripObject

If this Message is a response from a server, then answerfrom contains the IP address of the server



237
238
239
# File 'lib/Dnsruby/message.rb', line 237

def answerip
  @answerip
end

#answersizeObject

If this Message is a response from a server, then answersize contains the size of the response



240
241
242
# File 'lib/Dnsruby/message.rb', line 240

def answersize
  @answersize
end

#authorityObject (readonly) Also known as: update

The authority section, an array of Dnsruby::RR objects.



227
228
229
# File 'lib/Dnsruby/message.rb', line 227

def authority
  @authority
end

#cachedObject

If the Message was returned from the cache, the cached flag will be set true. It will be false otherwise.



87
88
89
# File 'lib/Dnsruby/message.rb', line 87

def cached
  @cached
end

#do_cachingObject

do_caching is set by default. If you do not wish dnsruby to inspect the cache before sending the query, nor cache the result of the query, then set do_caching to false.



275
276
277
# File 'lib/Dnsruby/message.rb', line 275

def do_caching
  @do_caching
end

#do_validationObject

do_validation is set by default. If you do not wish dnsruby to validate this message (on a Resolver with @dnssec==true), then set do_validation to false. This option does not affect caching, or the header options



270
271
272
# File 'lib/Dnsruby/message.rb', line 270

def do_validation
  @do_validation
end

#headerObject

The header section, a Dnsruby::Header object.



231
232
233
# File 'lib/Dnsruby/message.rb', line 231

def header
  @header
end

#questionObject (readonly) Also known as: zone

The question section, an array of Dnsruby::Question objects.



222
223
224
# File 'lib/Dnsruby/message.rb', line 222

def question
  @question
end

#security_errorObject

If there was a problem verifying this message with DNSSEC, then securiy_error will hold a description of the problem. It defaults to “”



83
84
85
# File 'lib/Dnsruby/message.rb', line 83

def security_error
  @security_error
end

#security_levelObject

If dnssec is set on, then each message will have the security level set To find the precise error (if any), call Dnsruby::Dnssec::validate(msg) - the resultant exception will define the error.



80
81
82
# File 'lib/Dnsruby/message.rb', line 80

def security_level
  @security_level
end

#send_rawObject

Set send_raw if you wish to send and receive the response to this Message with no additional processing. In other words, if set, then Dnsruby will not touch the Header of the outgoing Message. This option does not affect caching or dnssec validation

This option should not normally be set.



265
266
267
# File 'lib/Dnsruby/message.rb', line 265

def send_raw
  @send_raw
end

#tsigerrorObject

If this message has been verified using a TSIG RR then tsigerror contains the error code returned by the TSIG verification. The error will be an RCode



244
245
246
# File 'lib/Dnsruby/message.rb', line 244

def tsigerror
  @tsigerror
end

#tsigstartObject



256
257
258
# File 'lib/Dnsruby/message.rb', line 256

def tsigstart
  @tsigstart
end

#tsigstateObject

Can be

  • :Unsigned - the default state

  • :Signed - the outgoing message has been signed

  • :Verified - the incoming message has been verified by TSIG

  • :Intermediate - the incoming message is an intermediate envelope in a TCP session

in which only every 100th envelope must be signed

  • :Failed - the incoming response failed verification



253
254
255
# File 'lib/Dnsruby/message.rb', line 253

def tsigstate
  @tsigstate
end

Class Method Details

.decode(m) ⇒ Object

Decode the encoded message



597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
# File 'lib/Dnsruby/message.rb', line 597

def Message.decode(m)
  o = Message.new()
  begin
    MessageDecoder.new(m) {|msg|
      o.header = Header.new(msg)
      o.header.qdcount.times {
        question = msg.get_question
        o.question << question
      }
      o.header.ancount.times {
        rr = msg.get_rr
        o.answer << rr
      }
      o.header.nscount.times {
        rr = msg.get_rr
        o.authority << rr
      }
      o.header.arcount.times { |count|
        start = msg.index
        rr = msg.get_rr
        if (rr.type == Types::TSIG)
          if (count!=o.header.arcount-1)
            Dnsruby.log.Error("Incoming message has TSIG record before last record")
            raise DecodeError.new("TSIG record present before last record")
          end
          o.tsigstart = start # needed for TSIG verification
        end
        o.additional << rr
      }
    }
  rescue DecodeError => e
    # So we got a decode error
    # However, we might have been able to fill in many parts of the message
    # So let's raise the DecodeError, but add the partially completed message
    e.partial_message = o
    raise e
  end
  return o
end

Instance Method Details

#==(msg2) ⇒ Object



305
306
307
308
309
310
311
312
313
314
315
# File 'lib/Dnsruby/message.rb', line 305

def ==(other)
  ret = false
  if (other.kind_of?Message)
    ret = @header == other.header &&
      @question[0] == other.question[0] &&
      @answer == other.answer &&
      @authority == other.authority &&
      @additional == other.additional
  end
  return ret
end

#add_additional(rr) ⇒ Object

:nodoc: all



409
410
411
412
413
414
# File 'lib/Dnsruby/message.rb', line 409

def add_additional(rr) #:nodoc: all
  if (!@additional.include?rr)
    @additional << rr
    update_counts
  end
end

#add_answer(rr) ⇒ Object Also known as: add_pre

:nodoc: all



383
384
385
386
387
388
# File 'lib/Dnsruby/message.rb', line 383

def add_answer(rr) #:nodoc: all
  if (!@answer.include?rr)
    @answer << rr
    update_counts
  end
end

#add_authority(rr) ⇒ Object Also known as: add_update

:nodoc: all



396
397
398
399
400
401
# File 'lib/Dnsruby/message.rb', line 396

def add_authority(rr) #:nodoc: all
  if (!@authority.include?rr)
    @authority << rr
    update_counts
  end
end

#add_question(question, type = Types.A, klass = Classes.IN) ⇒ Object Also known as: add_zone

Add a new Question to the Message. Takes either a Question, or a name, and an optional type and class.

  • msg.add_question(Question.new(“example.com”, ‘MX’))

  • msg.add_question(“example.com”) # defaults to Types.A, Classes.IN

  • msg.add_question(“example.com”, Types.LOC)



361
362
363
364
365
366
367
# File 'lib/Dnsruby/message.rb', line 361

def add_question(question, type=Types.A, klass=Classes.IN)
  if (!question.kind_of?Question)
    question = Question.new(question, type, klass)
  end
  @question << question
  update_counts
end

#cloneObject



637
638
639
# File 'lib/Dnsruby/message.rb', line 637

def clone
  Message.decode(self.encode)
end

#each_additionalObject



416
417
418
419
420
# File 'lib/Dnsruby/message.rb', line 416

def each_additional
  @additional.each {|rec|
    yield rec
  }
end

#each_answerObject Also known as: each_pre



390
391
392
393
394
# File 'lib/Dnsruby/message.rb', line 390

def each_answer
  @answer.each {|rec|
    yield rec
  }
end

#each_authorityObject Also known as: each_update



403
404
405
406
407
# File 'lib/Dnsruby/message.rb', line 403

def each_authority
  @authority.each {|rec|
    yield rec
  }
end

#each_questionObject Also known as: each_zone



369
370
371
372
373
# File 'lib/Dnsruby/message.rb', line 369

def each_question
  @question.each {|rec|
    yield rec
  }
end

#each_resourceObject

Calls each_answer, each_authority, each_additional



428
429
430
431
432
# File 'lib/Dnsruby/message.rb', line 428

def each_resource
  each_answer {|rec| yield rec}
  each_authority {|rec| yield rec}
  each_additional {|rec| yield rec}
end

#each_sectionObject

Yields each section (question, answer, authority, additional)



423
424
425
# File 'lib/Dnsruby/message.rb', line 423

def each_section
  [@answer, @authority, @additional].each {|section| yield section}
end

#encodeObject

Return the encoded form of the message If there is a TSIG record present and the record has not been signed then sign it



575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
# File 'lib/Dnsruby/message.rb', line 575

def encode
  if ((@tsigkey) && @tsigstate == :Unsigned && !@signing)
    @signing = true
    sign!
    @signing = false
  end
  return MessageEncoder.new {|msg|
    header = @header
    header.encode(msg)
    @question.each {|q|
      msg.put_name(q.qname)
      msg.put_pack('nn', q.qtype.code, q.qclass.code)
    }
    [@answer, @authority, @additional].each {|rr|
      rr.each { |r|
        msg.put_rr(r)
      }
    }
  }.to_s
end

#get_exceptionObject



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
302
303
# File 'lib/Dnsruby/message.rb', line 277

def get_exception
  exception = nil
  if (rcode==RCode.NXDOMAIN)
    exception = NXDomain.new
  elsif (rcode==RCode.SERVFAIL)
    exception = ServFail.new
  elsif (rcode==RCode.FORMERR)
    exception = FormErr.new
  elsif (rcode==RCode.NOTIMP)
    exception = NotImp.new
  elsif (rcode==RCode.REFUSED)
    exception = Refused.new
  elsif (rcode==RCode.NOTZONE)
    exception = NotZone.new
  elsif (rcode==RCode.NOTAUTH)
    exception = NotAuth.new
  elsif (rcode==RCode.NXRRSET)
    exception = NXRRSet.new
  elsif (rcode==RCode.YXRRSET)
    exception = YXRRSet.new
  elsif (rcode==RCode.YXDOMAIN)
    exception = YXDomain.new
  elsif (rcode >= RCode.BADSIG && rcode <= RCode.BADALG)
    return VerifyError.new # @TODO@
  end
  return exception
end

#get_optObject



475
476
477
478
479
480
481
482
# File 'lib/Dnsruby/message.rb', line 475

def get_opt
  each_additional do |r|
    if (r.type == Types::OPT)
      return r
    end
  end
  return nil
end

#rcodeObject



484
485
486
487
488
489
490
491
492
# File 'lib/Dnsruby/message.rb', line 484

def rcode
  rcode = @header.get_header_rcode
  opt = get_opt
  if (opt != nil)
    rcode = rcode.code + (opt.xrcode.code << 4)
    rcode = RCode.new(rcode)
  end
  return rcode;
end

#remove_additionalObject



317
318
319
320
# File 'lib/Dnsruby/message.rb', line 317

def remove_additional
  @additional = Section.new(self)
  @header.arcount = 0
end

#rrset(name, type, klass = Classes::IN) ⇒ Object

Return the first rrset of the specified attributes in the message



323
324
325
326
327
328
329
330
# File 'lib/Dnsruby/message.rb', line 323

def rrset(name, type, klass = Classes::IN)
  [@answer, @authority, @additional].each do |section|
    if ((rrset = section.rrset(name, type, klass)).length > 0)
      return rrset
    end
  end
  return RRSet.new
end

#rrsets(type, klass = Classes::IN) ⇒ Object

Return the rrsets of the specified type in the message



333
334
335
336
337
338
339
340
341
342
343
# File 'lib/Dnsruby/message.rb', line 333

def rrsets(type, klass=Classes::IN)
  rrsetss = []
  [@answer, @authority, @additional].each do |section|
    if ((rrsets = section.rrsets(type, klass)).length > 0)
      rrsets.each {|rrset|
        rrsetss.push(rrset)
      }
    end
  end
  return rrsetss
end

#section_rrsets(type = nil, include_opt = false) ⇒ Object

Return a hash, with the section as key, and the RRSets in that section as the data : => section_rrs



347
348
349
350
351
352
353
# File 'lib/Dnsruby/message.rb', line 347

def section_rrsets(type = nil, include_opt = false)
  ret = {}
  ["answer", "authority", "additional"].each do |section|
    ret[section] = self.send(section).rrsets(type, include_opt)
  end
  return ret
end

#set_tsig(*args) ⇒ Object

Sets the TSIG to sign this message with. Can either be a Dnsruby::RR::TSIG object, or it can be a (name, key) tuple, or it can be a hash which takes Dnsruby::RR::TSIG attributes (e.g. name, key, fudge, etc.)



447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
# File 'lib/Dnsruby/message.rb', line 447

def set_tsig(*args)
  if (args.length == 1)
    if (args[0].instance_of?RR::TSIG)
      @tsigkey = args[0]
    elsif (args[0].instance_of?Hash)
      @tsigkey = RR.create({:type=>'TSIG', :klass=>'ANY'}.merge(args[0]))
    else
      raise ArgumentError.new("Wrong type of argument to Dnsruby::Message#set_tsig - should be TSIG or Hash")
    end
  elsif (args.length == 2)
    @tsigkey = RR.create({:type=>'TSIG', :klass=>'ANY', :name=>args[0], :key=>args[1]})
  else
    raise ArgumentError.new("Wrong number of arguments to Dnsruby::Message#set_tsig")
  end
end

#sign!(*args) ⇒ Object

Signs the message. If used with no arguments, then the message must have already been set (set_tsig). Otherwise, the arguments can either be a Dnsruby::RR::TSIG object, or a (name, key) tuple, or a hash which takes Dnsruby::RR::TSIG attributes (e.g. name, key, fudge, etc.)

NOTE that this method should only be called by the resolver, rather than the client code. To use signing from the client, call Dnsruby::Resolver#tsig=



561
562
563
564
565
566
567
568
569
570
# File 'lib/Dnsruby/message.rb', line 561

def sign!(*args) #:nodoc: all
  if (args.length > 0)
    set_tsig(*args)
    sign!
  else
    if ((@tsigkey) && @tsigstate == :Unsigned)
      @tsigkey.apply(self)
    end
  end
end

#signed?Boolean

Was this message signed by a TSIG?

Returns:

  • (Boolean)


464
465
466
467
468
# File 'lib/Dnsruby/message.rb', line 464

def signed?
  return (@tsigstate == :Signed ||
      @tsigstate == :Verified ||
      @tsigstate == :Failed)
end

#to_sObject



494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
# File 'lib/Dnsruby/message.rb', line 494

def to_s
  retval = "";
  
  if (@answerfrom != nil && @answerfrom != "")
    retval = retval + ";; Answer received from #{@answerfrom} (#{@answersize} bytes)\n;;\n";
  end
  retval = retval + ";; Security Level : #{@security_level.string}\n"
  
  retval = retval + ";; HEADER SECTION\n"
  # OPT pseudosection? EDNS flags, udpsize
  opt = get_opt
  if (!opt)
    retval = retval + @header.to_s
  else
    retval = retval + @header.to_s_with_rcode(rcode())
  end
  retval = retval + "\n"
  
  if (opt)
    retval = retval + opt.to_s
    retval = retval + "\n"
  end
        
  section = (@header.opcode == OpCode.UPDATE) ? "ZONE" : "QUESTION";
  retval = retval +  ";; #{section} SECTION (#{@header.qdcount}  record#{@header.qdcount == 1 ? '' : 's'})\n";
  each_question { |qr|
    retval = retval + ";; #{qr.to_s}\n";
  }
  
  if (@answer.size > 0)
    retval = retval + "\n";
    section = (@header.opcode == OpCode.UPDATE) ? "PREREQUISITE" : "ANSWER";
    retval = retval + ";; #{section} SECTION (#{@header.ancount}  record#{@header.ancount == 1 ? '' : 's'})\n";
    each_answer { |rr|
      retval = retval + rr.to_s + "\n";
    }
  end
  
  if (@authority.size > 0)
    retval = retval + "\n";
    section = (@header.opcode == OpCode.UPDATE) ? "UPDATE" : "AUTHORITY";
    retval = retval + ";; #{section} SECTION (#{@header.nscount}  record#{@header.nscount == 1 ? '' : 's'})\n";
    each_authority { |rr|
      retval = retval + rr.to_s + "\n";
    }
  end
  
  if ((@additional.size > 0 && !opt) || (@additional.size > 1))
    retval = retval + "\n";
    retval = retval + ";; ADDITIONAL SECTION (#{@header.arcount}  record#{@header.arcount == 1 ? '' : 's'})\n";
    each_additional { |rr|
      if (rr.type != Types::OPT)
        retval = retval + rr.to_s+ "\n"
      end
    }
  end
  
  return retval;
end

#tsigObject

Returns the TSIG record from the ADDITIONAL section, if one is present.



435
436
437
438
439
440
441
442
# File 'lib/Dnsruby/message.rb', line 435

def tsig
  if (@additional.last)
    if (@additional.last.rr_type == Types.TSIG)
      return @additional.last
    end
  end
  return nil
end

#update_countsObject

:nodoc:all



375
376
377
378
379
380
# File 'lib/Dnsruby/message.rb', line 375

def update_counts # :nodoc:all
  @header.ancount = @answer.length
  @header.arcount = @additional.length
  @header.qdcount = @question.length
  @header.nscount = @authority.length
end

#verified?Boolean

If this message was signed by a TSIG, was the TSIG verified?

Returns:

  • (Boolean)


471
472
473
# File 'lib/Dnsruby/message.rb', line 471

def verified?
  return (@tsigstate == :Verified)
end