# -*- coding: binary -*-
require 'socket'
require 'ipaddr'

class RawSocket # :nodoc:

  @@id_arr = []
  
  def initialize(src_addr,dest_addr)
    
    # Define socket
    begin
      @socket = Socket.new PF_INET, SOCK_RAW, IPPROTO_RAW
    rescue SystemCallError => e
      raise SystemCallError, "You must be root to use raw sockets! #{e}"
    end
    
    @socket.setsockopt IPPROTO_IP, IP_HDRINCL, 1

    # Checks addresses
    @src_addr  = check_addr src_addr
    @dest_addr = check_addr dest_addr

    # Source and destination port are zero
    @src_port  = 0
    @dest_port = 0

    # Set correct protocol version in the header
    @version = @dest_addr.ipv4? ? "0100" : "0110"
    
    # Total length: must be overridden by subclasses
    @tot_lenght = 20

    # Protocol: must be overridden by subclasses
    @protocol = 1 # ICMP by default

    # Generate a new id
    # @id = genID
    @id = 1234

    # Generate peer sockaddr
    @to = Socket.pack_sockaddr_in @dest_port, @dest_addr.to_s    
  end

  def send(payload = '')
    packet = make_ip_header([[ @version+'0101', 'B8' ],            # version, hlen
                              [ 0, 'C' ],                          # tos
                              [ @tot_lenght + payload.size, 'n' ], # total len
                              [ @id, 'n' ],                        # id
                              [ 0, 'n' ],                          # flags, offset
                              [ 64, 'C' ],                         # ttl
                              [ @protocol, 'C' ],                  # protocol
                              [ 0, 'n' ],                          # checksum
                              [ @src_addr.to_i, 'N' ],             # source
                              [ @dest_addr.to_i, 'N' ],            # destination
                            ])
    packet << make_transport_header(payload.size)
    packet << [payload].pack("a*")
    @socket.send(packet,0,@to)
  end 

  private
  
  def check_addr addr
    case addr
    when String
      IPAddr.new addr
    when IPAddr
      addr
    else
      raise ArgumentError, "Wrong address format: #{addr}"
    end
  end
  
  def check_port port
    if (1..65535).include? port and port.kind_of? Integer
      port
    else
      raise ArgumentError, "Port #{port} not valid"
    end
  end
  
  def genID
    while (@@id_arr.include?(q = rand(65535)))
    end
    @@id_arr.push(q)
    q
  end

  def ipchecksum(data)
    checksum = data.unpack("n*").inject(0) { |s, x| s + x }
    ((checksum >> 16) + (checksum & 0xffff)) ^ 0xffff
  end

  def make_ip_header(parts)
    template = ''
    data = []
    parts.each do |part|
      data += part[0..-2]
      template << part[-1]
    end
    data_str = data.pack(template)
    checksum = ipchecksum(data_str)
    data[-3] = checksum
    data.pack(template)
  end
  
  def make_transport_header
    ""
  end
  
end

class UdpRawSocket < RawSocket # :nodoc:

  def initialize(src_addr,src_port,dest_addr,dest_port)
    
    super(src_addr,dest_addr)
    
    # Check ports
    @src_port  = check_port src_port
    @dest_port = check_port dest_port
    
    # Total length: must be overridden by subclasses
    @tot_lenght = 20 + 8 # 8 bytes => UDP Header

    # Protocol: must be overridden by subclasses
    @protocol = 17 # UDP protocol

    @to = Socket.pack_sockaddr_in @dest_port, @dest_addr.to_s    
  end

  private
  
  def make_udp_header(parts)
    template = ''
    data = []
    parts.each do |part|
      data += part[0..-2]
      template << part[-1]
    end
    data.pack(template)
  end 
  
  def make_transport_header(pay_size)
    make_udp_header([
                      [ @src_port, 'n'],         # source port
                      [ @dest_port, 'n' ],       # destination port
                      [ 8 + pay_size, 'n' ],     # len
                      [ 0, 'n' ]                 # checksum (mandatory)
                    ]) 
  end
  
end
