#
# icmp.rb -- ICMPModule
#
# Copyright (C) 2000 GOTOU YUUZOU <gotoyuzo@notwork.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $Id: icmp.rb,v 1.2 2001/09/20 17:13:42 gotoyuzo Exp $

require 'socket'

module ICMPModule
  # Protocol
  IPPROTO_IP   = 0
  IPPROTO_ICMP = 1

  # Type and code field values
  ICMP_ECHOREPLY                =  0  # echo reply
  ICMP_UNREACH                  =  3  # dest unreachable, codes:
    ICMP_UNREACH_NET            =  0  #   bad net
    ICMP_UNREACH_HOST           =  1  #   bad host
    ICMP_UNREACH_PROTOCOL       =  2  #   bad protocol
    ICMP_UNREACH_PORT           =  3  #   bad port
    ICMP_UNREACH_NEEDFRAG       =  4  #   IP_DF caused drop
    ICMP_UNREACH_SRCFAIL        =  5  #   src route failed
    ICMP_UNREACH_NET_UNKNOWN    =  6  #   unknown net
    ICMP_UNREACH_HOST_UNKNOWN   =  7  #   unknown host
    ICMP_UNREACH_ISOLATED       =  8  #   src host isolated
    ICMP_UNREACH_NET_PROHIB     =  9  #   prohibited access
    ICMP_UNREACH_HOST_PROHIB    = 10  #   ditto
    ICMP_UNREACH_TOSNET         = 11  #   bad tos for net
    ICMP_UNREACH_TOSHOST        = 12  #   bad tos for host
    ICMP_UNREACH_ADMIN_PROHIBIT = 13  #   communication administratively
                                      #   prohibited
  ICMP_SOURCEQUENCH             =  4  # packet lost, slow down
  ICMP_REDIRECT                 =  5  # shorter route, codes:
    ICMP_REDIRECT_NET           =  0  #   for network
    ICMP_REDIRECT_HOST          =  1  #   for host
    ICMP_REDIRECT_TOSNET        =  2  #   for tos and net
    ICMP_REDIRECT_TOSHOST       =  3  #   for tos and host
  ICMP_ECHO                     =  8  # echo service
  ICMP_ROUTERADVERT             =  9  # router advertisement (RFC1256)
  ICMP_ROUTERSOLICIT            = 10  # router solicitation (RFC1256)
  ICMP_TIMXCEED                 = 11  # time exceeded, code:
    ICMP_TIMXCEED_INTRANS       =  0  #   ttl==0 in transit
    ICMP_TIMXCEED_REASS         =  1  #   ttl==0 in reass
  ICMP_PARAMPROB                = 12  # ip header bad, code:
    ICMP_PARAMPROB_OPTABSENT    =  1  #   req. opt. absent
  ICMP_TSTAMP                   = 13  # timestamp request
  ICMP_TSTAMPREPLY              = 14  # timestamp reply
  ICMP_IREQ                     = 15  # information request
  ICMP_IREQREPLY                = 16  # information reply
  ICMP_MASKREQ                  = 17  # address mask request (RFC950)
  ICMP_MASKREPLY                = 18  # address mask reply (RFC950)

  ICMP_MINLEN                   =  8
  ICMP_TSLEN                    = 20
  ICMP_MASKLEN                  = 12
  ICMP_ADVLENMIN                = 36

  class IMCPError_rb < StandardError; end

  class IP_rb < String
    def self.new(s=nil); s ? super(s) : super("\0" * 20); end
    def ip_v;     (self[0] >> 4) & 0xf end
    def ip_hl;    self[0] & 0xf end
    def ip_tos;   self[1]; end
    def ip_len;   self[2..3].unpack("n")[0]; end
    def ip_id;    self[4..5].unpack("n")[0]; end
    def ip_off;   self[6..7].unpack("n")[0]; end
    def ip_ttl;   self[8]; end
    def ip_p;     self[9]; end
    def ip_sum;   self[10..11].unpack("n")[0]; end
    def ip_src;   "%d.%d.%d.%d" % [self[12], self[13], self[14], self[15]]; end
    def ip_dst;   "%d.%d.%d.%d" % [self[16], self[17], self[18], self[19]]; end
    def body;     self[(ip_hl*4)..-1]; end
  end

  class ICMP_rb < String
    def self.new(s=nil); s ? super(s) : super("\0" * ICMP_ADVLENMIN); end
    def icmp_type;    self[0]; end
    def icmp_type=v;  self[0]=v; end
    def icmp_code;    self[1]; end
    def icmp_code=v;  self[1]=v; end
    def icmp_cksum;   self[2..3].unpack("n")[0]; end
    def icmp_cksum=v; self[2..3]=[v].pack("n"); end
    def icmp_id;      self[4..5].unpack("n")[0]; end
    def icmp_id=v;    self[4..5]=[v].pack("n"); end
    def icmp_seq;     self[6..7].unpack("n")[0]; end
    def icmp_seq=v;   self[6..7]=[v].pack("n"); end
    def icmp_data;    self[8..-1]; end
    def icmp_data=v;  self[8..-1]=v; end
    def icmp_gwaddr;  "%d.%d.%d.%d" % [self[4], self[5], self[6], self[7]]; end
    def icmp_ip;      IP.new(self[8..-1]); end
    def icmp_ip=v;    self[8..-1]=v; end

    def truncate
      olen = self.size
      case self.icmp_type
      when ICMP_IREQ, ICMP_IREQREPLY
        nlen = ICMP_MINLEN

      when ICMP_UNREACH, ICMP_TIMXCEED, ICMP_PARAMPROB,
           ICMP_SOURCEQUENCH, ICMP_REDIRECT
        nlen = ICMP_ADVLENMIN

      when ICMP_TSTAMP, ICMP_TSTAMPREPLY
        nlen = ICMP_TSLEN

      when ICMP_ROUTERADVERT, ICMP_ROUTERSOLICIT
        nlen = icmp_advlen

      when ICMP_MASKREQ, ICMP_MASKREPLY
        nlen = ICMP_MASKLEN

      when ICMP_ECHO, ICMP_ECHOREPLY
        nlen = olen

      else
        raise ICMPError, "unknown icmp_type %d" % self.icmp_type
      end
      self[nlen..-1] = ""
    end

    def icmp_advlen
      8 + (icmp_ip.ip_hl * 4) + 8
    end

    def set_cksum
      self.icmp_cksum = 0
      sum = 0
      self.unpack("n*").each{ |i| sum += i }
      sum += (self[-1] << 8) if self.size % 2 == 1
      sum = (sum & 0xffff) + (sum >> 16)
      sum += (sum >> 16)
      self.icmp_cksum = ~sum & 0xffff
      self
    end

    def setup
      truncate
      set_cksum
    end
  end

  class ICMPSocket < Socket
    def ICMPSocket.new
      super("AF_INET", "SOCK_RAW", IPPROTO_ICMP)
    end
  end

  def make_sockaddr_in(family, port, addr)
    s = [ family ].pack("S")
    s << [ port ].pack("n") # network byteorder
    s << addr
    s << "\0" * 8
    s
  end

  def split(s)
    ip = IP.new s
    len = ip.ip_hl * 4 # ip_hl is header length in 32 bit word.
    ip[len..-1] = ""
    [ ip, ICMP.new(s[len..-1]) ]
  end

  begin
    require 'icmpmodule'
    IP = IP_c
    ICMP = ICMP_c
    ICMPError = ICMPError_c
  rescue LoadError
    IP = IP_rb
    ICMP = ICMP_rb
    ICMPError = ICMPError_rb
  end
end
