Class: Ping::ICMP

Inherits:
Ping
  • Object
show all
Defined in:
lib/net/ping/icmp.rb

Overview

The Net::Ping::ICMP class encapsulates an icmp ping.

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, but must be created with root privileges (on UNIX systems), and the port value is ignored.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/net/ping/icmp.rb', line 30

def initialize(host=nil, port=nil, timeout=5)
  begin
    # If we have cap2, but not are root, or have net_raw, raise an error
    require 'cap2'
    current_process = Cap2.process
    unless Process.euid == 0 \
      || current_process.permitted?(:net_raw) \
      && current_process.enabled?(:net_raw)
      raise StandardError, 'requires root privileges or setcap net_raw'
    end
  rescue LoadError
    # Without cap2, raise error if we are not root
    unless Process.euid == 0
      raise StandardError, 'requires root privileges or setcap net_raw'
    end
  end

  if File::ALT_SEPARATOR
    unless Win32::Security.elevated_security?
      raise 'requires elevated security'
    end
  end

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



24
25
26
# File 'lib/net/ping/icmp.rb', line 24

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.



78
79
80
81
# File 'lib/net/ping/icmp.rb', line 78

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.



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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/net/ping/icmp.rb', line 87

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

  socket = Socket.new(
    Socket::PF_INET,
    Socket::SOCK_RAW,
    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