Class: Netchk::ICMP

Inherits:
Net::Ping
  • Object
show all
Defined in:
lib/netchk/icmp.rb

Overview

Modified version of Net::Ping::ICMP that does not check for root privileges and uses a DGRAM socket instead of a raw socket.

Constant Summary collapse

ICMP_ECHOREPLY =

Echo reply

0
ICMP_ECHO =

Echo request

8
ICMP_SUBCODE =
0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host = nil, port = nil, timeout = 5) ⇒ ICMP

Creates and returns a new Ping::ICMP object. This is similar to its superclass constructor, and the port value is ignored.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/netchk/icmp.rb', line 30

def initialize(host = nil, port = nil, timeout = 5)
  @seq = 0
  @bind_port = 0
  @bind_host = nil
  @data_size = 56
  @data = ''

  0.upto(@data_size) { |n| @data << (n % 256).chr }

  @ping_id = (Thread.current.object_id ^ Process.pid) & 0xffff

  super(host, port, timeout)
  @port = nil # This value is not used in ICMP pings.
end

Instance Attribute Details

#data_sizeObject

Returns the data size, i.e. number of bytes sent on the ping. The default size is 56.



26
27
28
# File 'lib/netchk/icmp.rb', line 26

def data_size
  @data_size
end

Instance Method Details

#bind(host, port = 0) ⇒ Object

Associates the local end of the socket connection with the given host and port. The default port is 0.



56
57
58
59
# File 'lib/netchk/icmp.rb', line 56

def bind(host, port = 0)
  @bind_host = host
  @bind_port = port
end

#ping(host = @host) ⇒ Object

Pings the host specified in this method or in the constructor. If a host was not specified either here or in the constructor, an ArgumentError is raised.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/netchk/icmp.rb', line 65

def ping(host = @host)
  super(host)
  bool = false

  socket = Socket.new(
    Socket::PF_INET,
    Socket::SOCK_DGRAM,
    Socket::IPPROTO_ICMP
  )

  if @bind_host
    saddr = Socket.pack_sockaddr_in(@bind_port, @bind_host)
    socket.bind(saddr)
  end

  @seq = (@seq + 1) % 65536
  pstring = 'C2 n3 A' << @data_size.to_s
  timeout = @timeout

  checksum = 0
  msg = [ICMP_ECHO, ICMP_SUBCODE, checksum, @ping_id, @seq, @data].pack(pstring)

  checksum = checksum(msg)
  msg = [ICMP_ECHO, ICMP_SUBCODE, checksum, @ping_id, @seq, @data].pack(pstring)

  begin
    saddr = Socket.pack_sockaddr_in(0, host)
  rescue Exception
    socket.close unless socket.closed?
    return bool
  end

  start_time = Time.now

  socket.send(msg, 0, saddr) # Send the message

  begin
    Timeout.timeout(@timeout) {
      while true
        io_array = select([socket], nil, nil, timeout)

        if io_array.nil? || io_array[0].empty?
          raise Timeout::Error if io_array.nil?
          return false
        end

        ping_id = nil
        seq = nil

        data = socket.recvfrom(1500).first
        type = data[20, 2].unpack('C2').first

        case type
        when ICMP_ECHOREPLY
          if data.length >= 28
            ping_id, seq = data[24, 4].unpack('n3')
          end
        else
          if data.length > 56
            ping_id, seq = data[52, 4].unpack('n3')
          end
        end

        if ping_id == @ping_id && seq == @seq && type == ICMP_ECHOREPLY
          bool = true
          break
        end
      end
    }
  rescue Exception => err
    @exception = err
  ensure
    socket.close if socket
  end

  # There is no duration if the ping failed
  @duration = Time.now - start_time if bool
end