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)

Direct Known Subclasses

Update

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”)



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/Dnsruby/message.rb', line 70

def initialize(*args)
  @header = Header.new()
  @question = []
  @answer = []
  @authority = []
  @additional = []
  @tsigstate = :Unsigned
  @signing = false
  @tsigkey = nil
  @answerfrom = nil
  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.



102
103
104
# File 'lib/Dnsruby/message.rb', line 102

def additional
  @additional
end

#answerObject (readonly) Also known as: pre

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



98
99
100
# File 'lib/Dnsruby/message.rb', line 98

def answer
  @answer
end

#answerfromObject

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



107
108
109
# File 'lib/Dnsruby/message.rb', line 107

def answerfrom
  @answerfrom
end

#answersizeObject

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



110
111
112
# File 'lib/Dnsruby/message.rb', line 110

def answersize
  @answersize
end

#authorityObject (readonly) Also known as: update

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



100
101
102
# File 'lib/Dnsruby/message.rb', line 100

def authority
  @authority
end

#headerObject

The header section, a Dnsruby::Header object.



104
105
106
# File 'lib/Dnsruby/message.rb', line 104

def header
  @header
end

#questionObject (readonly) Also known as: zone

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



95
96
97
# File 'lib/Dnsruby/message.rb', line 95

def question
  @question
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



114
115
116
# File 'lib/Dnsruby/message.rb', line 114

def tsigerror
  @tsigerror
end

#tsigstartObject



126
127
128
# File 'lib/Dnsruby/message.rb', line 126

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

  • :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



123
124
125
# File 'lib/Dnsruby/message.rb', line 123

def tsigstate
  @tsigstate
end

Class Method Details

.decode(m) ⇒ Object

Decode the encoded message



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

def Message.decode(m)
  o = Message.new()
  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)
          TheLog.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
    }
  }
  return o
end

Instance Method Details

#==(other) ⇒ Object

++



129
130
131
132
133
134
135
136
137
138
139
# File 'lib/Dnsruby/message.rb', line 129

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

#add_additional(rr) ⇒ Object

:nodoc: all



188
189
190
191
192
193
# File 'lib/Dnsruby/message.rb', line 188

def add_additional(rr) #:nodoc: all
  if (!@additional.include?rr)
    @additional << rr
    @header.arcount = @additional.length
  end
end

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

:nodoc: all



162
163
164
165
166
167
# File 'lib/Dnsruby/message.rb', line 162

def add_answer(rr) #:nodoc: all
  if (!@answer.include?rr)
    @answer << rr
    @header.ancount = @answer.length
  end
end

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

:nodoc: all



175
176
177
178
179
180
# File 'lib/Dnsruby/message.rb', line 175

def add_authority(rr) #:nodoc: all
  if (!@authority.include?rr)
    @authority << rr
    @header.nscount = @authority.length
  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)



147
148
149
150
151
152
153
# File 'lib/Dnsruby/message.rb', line 147

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

#each_additionalObject



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

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

#each_answerObject Also known as: each_pre



169
170
171
172
173
# File 'lib/Dnsruby/message.rb', line 169

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

#each_authorityObject Also known as: each_update



182
183
184
185
186
# File 'lib/Dnsruby/message.rb', line 182

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

#each_questionObject Also known as: each_zone



155
156
157
158
159
# File 'lib/Dnsruby/message.rb', line 155

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

#each_resourceObject

Calls each_answer, each_authority, each_additional



202
203
204
205
206
# File 'lib/Dnsruby/message.rb', line 202

def each_resource
  each_answer {|rec| yield rec}
  each_authority {|rec| yield rec}
  each_additional {|rec| yield rec}
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



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/message.rb', line 307

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|
        name = r.name
        ttl = r.ttl
        if (r.type == Types.TSIG)
          msg.put_name(name, true)
        else
          msg.put_name(name)
        end
        msg.put_pack('nnN', r.type.code, r.klass.code, ttl)
        msg.put_length16 {r.encode_rdata(msg)}
      }
    }
  }.to_s
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.)



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/Dnsruby/message.rb', line 221

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.)



293
294
295
296
297
298
299
300
301
302
# File 'lib/Dnsruby/message.rb', line 293

def sign!(*args)
  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)


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

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

#to_sObject



249
250
251
252
253
254
255
256
257
258
259
260
261
262
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
# File 'lib/Dnsruby/message.rb', line 249

def to_s
  retval = "";
  
  if (@answerfrom != nil && @answerfrom != "")
    retval = retval + ";; Answer received from #{@answerfrom} (#{@answersize} bytes)\n;;\n";
  end
  
  retval = retval + ";; HEADER SECTION\n";
  retval = retval + @header.to_s;
  
  retval = retval + "\n";
  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";
  }
  
  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";
  }
  
  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";
  }
  
  retval = retval + "\n";
  retval = retval + ";; ADDITIONAL SECTION (#{@header.arcount}  record#{@header.arcount == 1 ? '' : 's'})\n";
  each_additional { |rr|
    retval = retval + rr.to_s+ "\n";
  }
  
  return retval;
end

#tsigObject

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



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

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

#verified?Boolean

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

Returns:

  • (Boolean)


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

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