On Fri, Dec 01, 2006 at 04:47:05AM +0900, Daniel Berger wrote:
> Well, you could start by porting the ping_icmp() function from Ping.pm 
> to Ruby.  I started working on it but I got stuck and I need to work on 
> other things.  Below is what I've got so far:

I've made some progress; see attached. Let me know what you think.

-- 
Jos Backus
jos at catnook.com
#!/usr/bin/env ruby

require 'rubygems'
require 'net/ping'

module Net
  class Ping::ICMP < Ping   

    ICMP_ECHOREPLY = 0
    ICMP_ECHO      = 8
    ICMP_STRUCT    = 'C2 n3 A'
    ICMP_SUBCODE   = 0
    ICMP_FLAGS     = 0
    ICMP_PORT      = 0

    def initialize(*args)
      super(args)
      raise 'requires root privileges' if Process.euid > 0 # unsure
      @seq = 0
      @data_size = 0
      @data = ''
      @pid = Process.pid & 0xffff
    end

    def checksum(msg)
      length    = msg.length
      num_short = length / 2
      check     = 0

      msg.unpack("n#{num_short}").each do |short|
        check += short
      end

      if length % 2 > 0
        check += msg[length-1, 1].unpack('C') << 8
      end

      check = (check >> 16) + (check & 0xffff)
      return (~((check >> 16) + check) & 0xffff)
    end

    def ping(ip, timeout)
      ret = nil
      socket = Socket.new(
        Socket::PF_INET,
        Socket::SOCK_RAW,
        Socket::IPPROTO_ICMP
      )

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

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

      begin
      saddr = Socket.pack_sockaddr_in(ICMP_PORT, ip)
      rescue => exc
	return nil
      end

      @from_ip = @from_type = @from_subcode = nil
      socket.send(msg, ICMP_FLAGS, saddr) # Send the message

      done = false
      finish_time = Time.now + timeout
      while !done and timeout > 0
        nfound = select([socket], nil, nil, timeout)
	timeout = finish_time - Time.now
        if nfound.nil? # timed out
          ret = nil
          done = true
        elsif !nfound[0].empty?
          recv_msg = ''
          from_pid = -1
          from_seq = -1
          recv_msg, from_saddr = socket.recvfrom(1500, ICMP_FLAGS)
          from_port, from_ip = Socket.unpack_sockaddr_in(from_saddr)
          from_type, from_subcode = recv_msg[20, 2].unpack('C2')
          case from_type
          when ICMP_ECHOREPLY
            if recv_msg.length >= 28
              from_pid, from_seq = recv_msg[24, 4].unpack('n3')
            end
          else
            if recv_msg.length >= 56
              from_pid, from_seq = recv_msg[52, 4].unpack('n3')
            end
          end
          @from_ip = from_ip
          @from_type = from_type
          @from_subcode = from_subcode
          if from_pid == @pid and from_seq == @seq
            if from_type == ICMP_ECHOREPLY
              ret = 1
            end
            done = true
          end
        else
	  # Shouldn't happen
          done = true
        end
      end
      return ret
    end

  end

end

p = Net::Ping::ICMP.new
p p.ping(ARGV.shift, 5)