Class: Dap::Filter::FilterDecodeNTPReply

Inherits:
Object
  • Object
show all
Includes:
BaseDecoder
Defined in:
lib/dap/filter/udp.rb

Overview

Decode a NTP reply

Instance Attribute Summary

Attributes included from Base

#name, #opts

Instance Method Summary collapse

Methods included from BaseDecoder

#process

Methods included from Base

#initialize, #process

Instance Method Details

#decode(sdata) ⇒ Object



440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
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
553
554
555
556
# File 'lib/dap/filter/udp.rb', line 440

def decode(sdata)
  info = {}
  return if sdata.length < 4

  # Make a copy since our parser is destructive
  data = sdata.dup

  # TODO: all of this with bitstruct?
  # The format of the packet depends largely on the version, so extract just the version.
  # Fortunately the version is in the same place regardless of NTP protocol version --
  # The 3rd-5th bits of the first byte of the response
  ntp_flags = data.slice!(0,1).unpack('C').first
  ntp_version = (ntp_flags & 0b00111000) >> 3
  info['ntp.version'] = ntp_version
  info['ntp.mode'] = (ntp_flags & 0b00000111)

  # NTP 2 & 3 share a common header, so parse those together
  if ntp_version == 2 || ntp_version == 3

    if info['ntp.mode'] == 7
      # if it is mode 7, parse that:
      #     0                   1                   2                   3
      #     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      #    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      #    |R|M| VN  | Mode|A|  Sequence   | Implementation|   Req Code    |
      #    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      #    |  Err  | Number of data items  |  MBZ  |   Size of data item   |
      #    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      #    ... data ...

      return info if data.size < 8
      info['ntp.response'] = ntp_flags >> 7
      info['ntp.more'] = (ntp_flags & 0b01000000) >> 6
      ntp_auth_seq, ntp_impl, ntp_rcode = data.slice!(0,3).unpack('C*')
      info['ntp.implementation'] = ntp_impl
      info['ntp.request_code'] = ntp_rcode
      mode7_data = data.slice!(0,4).unpack('n*')
      info['ntp.mode7.err'] = mode7_data.first >> 11
      info['ntp.mode7.data_items_count'] = mode7_data.first & 0b0000111111111111
      info['ntp.mode7.mbz'] = mode7_data.last >> 11
      info['ntp.mode7.data_item_size'] = mode7_data.last & 0b0000111111111111

      # extra monlist response data
      if ntp_rcode == 42
        if info['ntp.mode7.data_item_size'] == 72
          remote_addresses = []
          local_addresses = []
          idx = 0
          1.upto(info['ntp.mode7.data_items_count']) do

            #u_int32 firsttime; /* first time we received a packet */
            #u_int32 lasttime;  /* last packet from this host */
            #u_int32 restr;     /* restrict bits (was named lastdrop) */
            #u_int32 count;     /* count of packets received */
            #u_int32 addr;      /* host address V4 style */
            #u_int32 daddr;     /* destination host address */
            #u_int32 flags;     /* flags about destination */
            #u_short port;      /* port number of last reception */
            data_block = data[idx, 30]
            # Occasionally not all of data captured, need to defensively handle this case.
            if data_block
              firsttime,lasttime,restr,count,raddr,laddr,flags,dport = data_block.unpack("NNNNNNNn")
              # even if data_block is not nil, might not have all of the 30 bytes of data, so make sure
              # that remote and local address are non-nil.
              remote_addresses << [raddr].pack("N").unpack("C*").map{|x| x.to_s }.join(".") if raddr
              local_addresses << [laddr].pack("N").unpack("C*").map{|x| x.to_s }.join(".")  if laddr
              idx += info['ntp.mode7.data_item_size']
            else
              break
            end
          end

          info['ntp.monlist.remote_addresses'] = remote_addresses.join(' ')
          info['ntp.monlist.remote_addresses.count'] = remote_addresses.size
          info['ntp.monlist.local_addresses'] = local_addresses.join(' ')
          info['ntp.monlist.local_addresses.count'] = local_addresses.size
        end
      end
    elsif info['ntp.mode'] == 6
      # control responses, supposedly.  abort if there isn't enough to have an empty control response
      return info if data.size < 12
      ntp_control_flags = data.slice!(0,1).unpack('C').first
      info["ntp.control.response"] = ntp_control_flags >> 7
      info["ntp.control.error"] = (ntp_control_flags & 0b01000000) >> 6
      info["ntp.control.more"] = (ntp_control_flags & 0b00100000) >> 5
      info["ntp.control.opcode"] = (ntp_control_flags & 0b00011111)
      %w(seq status association_id offset count).each do |field|
        info["ntp.control.#{field}"] = data.slice!(0,2).unpack('n').first
      end
      data = data.slice(0,info["ntp.control.count"])
      # decode readvar responses
      if info["ntp.control.opcode"] == 2
        info["ntp.control.readvar"] =  data
        data.strip!
        data.gsub!(/\r\n/, ' ')
        data.split(/, /).each do |pair|
          next unless pair =~ /=/
          key, value = pair.split(/=/)
          if value
            value.gsub!(/^['"]/, '')
            value.gsub!(/['"]$/, '')
          end
          info["ntp.control.readvar.#{key}"] = value
        end
      else
        info["ntp.control.data"] = data
      end
    end
  elsif ntp_version == 4
    info['ntp.leap_indicator'] = ntp_flags >> 6
    info['ntp.peer.stratum'], info['ntp.peer.interval'], info['ntp.peer.precision'] = data.slice!(0,3).unpack('C*')
    info['ntp.root.delay'], info['ntp.root.dispersion'], info['ntp.ref_id'] = data.slice!(0,12).unpack('N*')
    info['ntp.timestamp.reference'], info['ntp.timestamp.origin'], info['ntp.timestamp.receive'], info['ntp.timestamp.transmit'] = data.slice!(0,32).unpack('Q*')
  end

  info
end