#
# 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.4 2002/08/16 18:23:19 gotoyuzo Exp $

require 'socket'

module ICMPModule
  VERSION = "0.2.2"

  # muximun packet size
  IP_MAXPACKET = 65535

  # Protocol
  IPPROTO_IP   = 0
  IPPROTO_ICMP = 1
  IPPROTO_TCP  = 6
  IPPROTO_UDP  = 17

  # 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 < StandardError; end

  class IP < 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
    def inspect; "<IP: size=#{size} src=#{ip_src} dst=#{ip_dst}>"; end
  end

  class ICMP < 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

    def inspect; "<ICMP: size=#{size} type=#{icmp_type}>"; end
  end

  # TCP options
  TCPOPT_EOL              = 0
  TCPOPT_NOP              = 1
  TCPOPT_MAXSEG           = 2
  TCPOLEN_MAXSEG          = 4
  TCPOPT_WINDOW           = 3
  TCPOLEN_WINDOW          = 3
  TCPOPT_SACK_PERMITTED   = 4                      # Experimental
  TCPOLEN_SACK_PERMITTED  = 2
  TCPOPT_SACK             = 5                      # Experimental
  TCPOPT_TIMESTAMP        = 8
  TCPOLEN_TIMESTAMP       = 10
  TCPOLEN_TSTAMP_APPA     = (TCPOLEN_TIMESTAMP+2)  # appendix A
  TCPOPT_MD5SIGNATURE     = 19                     # RFC 2385
  TCPOLEN_MD5SIGNATURE    = 18
  TCPOPT_TSTAMP_HDR       = 
    (TCPOPT_NOP<<24|TCPOPT_NOP<<16|TCPOPT_TIMESTAMP<<8|TCPOLEN_TIMESTAMP)

  TCP_MSS                 = 512
  TCP_MAXWIN              = 65535   # largest value for (unscaled) window
  TCP_MAX_WINSHIFT        = 14      # maximum window shift
  TCP_MAXBURST            = 4       # maximum segments in a burst

  class TCP < String
    def self.new(s=nil); s ? super(s) : super("\0" * 20); end
    def th_sport; self[0..1].unpack("n")[0]; end
    def th_dport; self[2..3].unpack("n")[0]; end
    def th_seq;   self[4..7].unpack("n")[0]; end
    def th_ack;   self[8..11].unpack("n")[0]; end
    def th_x2;    (self[12] >> 4) & 0xf; end
    def th_off;   self[12] & 0xf ; end
    def th_flags; self[13]; end
    def th_win;   self[14..15].unpack("n")[0]; end
    def th_sum;   self[16..17].unpack("n")[0]; end
    def th_urp;   self[18..19].unpack("n")[0]; end
    def options;  self[20...(th_off*4)]; end
    def body;     self[(th_off*4)..-1]; end

    def fin?;  th_flags[0] == 1 end
    def syn?;  th_flags[1] == 1 end
    def rst?;  th_flags[2] == 1 end
    def push?; th_flags[3] == 1 end
    def ack?;  th_flags[4] == 1 end
    def urg?;  th_flags[5] == 1 end

    def each_option
      i = 0
      opt = options
      while true
        case kind = opt[i]
        when nil        then break
        when TCPOPT_EOL then break
        when TCPOPT_NOP then i += 1
        else
          len = opt[i+1]
          value = opt[i,len][2..-1]
          yield [kind, value]
          i += len
        end
      end
    end

    def inspect; "<TCP: size=#{size} src=#{th_sport} dst=#{th_dport}>"; end
  end

  class UDP < String
    def self.new(s=nil); s ? super(s) : super("\0" * 8); end
    def uh_sport; self[0..1].unpack("n")[0]; end
    def uh_dport; self[2..3].unpack("n")[0]; end
    def uh_ulen;  self[4..5].unpack("n")[0]; end
    def uh_sum;   self[6..7].unpack("n")[0]; end
    def body;     seld[7..-1]; end
    def inspect; "<UDP: size=#{size} src=#{uh_sport} dst=#{uh_dport}>"; 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)
    case ip.ip_p
    when IPPROTO_IP   then klass = IP
    when IPPROTO_ICMP then klass = ICMP
    when IPPROTO_TCP  then klass = TCP
    when IPPROTO_UDP  then klass = UDP
    else klass = String
    end
    [ ip, klass.new(ip.body) ]
  end
end
