trema/trema-edge

View on GitHub
src/examples/simple_router/router-utils.rb

Summary

Maintainability
A
35 mins
Test Coverage
#
# A router implementation in Trema
#
# Copyright (C) 2013 NEC Corporation
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#


require 'ipaddr'


def get_checksum csum, val
  sum = ( ~csum & 0xffff ) + val
  while sum > 0xffff
    sum = ( sum & 0xffff ) + ( sum >> 16 )
  end
  ~sum & 0xffff
end


class IPAddr
  def to_a
    self.to_s.split( "." ).collect do | each |
      each.to_i
    end
  end
end


class EthernetHeader
  attr_accessor :eth_dst, :eth_src, :eth_type


  def initialize eth_dst, eth_src, eth_type
    @eth_dst = eth_dst
    @eth_src = eth_src
    @eth_type = eth_type
  end


  def pack
    ( @eth_dst.to_a + @eth_src.to_a + [ eth_type ] ).pack( "C12n" )
  end
end


class ARPPacket
  attr_accessor :type, :tha, :sha, :tpa, :spa


  def initialize type, tha, sha, tpa, spa
    @type = type
    @tha = tha
    @sha = sha
    @tpa = tpa
    @spa = spa
  end


  def pack
    eth_header = EthernetHeader.new( @tha, @sha, 0x0806 )

    # arp
    arp = [ 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, @type ]
    arp += @sha.to_a + @spa.to_a + @tha.to_a + @tpa.to_a

    while arp.length < 46 do
      arp += [ 0x00 ]
    end

    eth_header.pack + arp.pack( "C*" )
  end
end


class ARPRequest < ARPPacket
  def initialize sha, tpa, spa
    tha = [ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff ]
    super( 1, tha, sha, tpa, spa )
  end
end


class ARPReply < ARPPacket
  def initialize tha, sha, tpa, spa
    super( 2, tha, sha, tpa, spa )
  end
end


class IPPacket
  attr_accessor :id, :protocol, :daddr, :saddr, :payload


  def initialize options
    @id = options[ :id ]
    @protocol = options[ :protocol ]
    @daddr = options[ :daddr ]
    @saddr = options[ :saddr ]
    @payload = options[ :payload ]
    @tot_len = 20 + payload.length
  end


  def pack
    csum = get_checksum( 0, 0x4500 )
    header = [ 0x45, 0x00 ] # Version, IHL, ToS

    csum = get_checksum( csum, @tot_len )
    header += [ @tot_len >> 8, @tot_len & 0xff ] # len

    csum = get_checksum( csum, @id )
    header += [ @id >> 8, @id & 0xff ] # ID

    csum = get_checksum( csum, 0x4000 )
    header += [ 0x40, 0x00 ] # Flags, Frag offset

    csum = get_checksum( csum, 0x40 * 0x100 + @protocol )
    header += [ 0x40, @protocol ] # ttl, protocol

    csum = get_checksum( csum, @saddr.to_i >> 16 )
    csum = get_checksum( csum, @saddr.to_i & 0xffff )
    csum = get_checksum( csum, @daddr.to_i >> 16 )
    csum = get_checksum( csum, @daddr.to_i & 0xffff )
    header += [ csum >> 8, csum & 0xff ] # checksum
    header += @saddr.to_a + @daddr.to_a

    header.pack( "C*" ) + @payload.pack
  end
end


class ICMPPacket
  attr_reader :payload, :length


  def initialize type, code, payload
    @type = type
    @code = code
    @payload = payload
    @length = 4 + payload.length
  end


  def pack
    @checksum = get_checksum( 0, @type * 0x100 + @code )

    words = @payload.pack( "C*" ).unpack( "n*" )
    words.each do | each |
      @checksum = get_checksum( @checksum, each )
    end

    [ @type, @code, @checksum ].pack( "C2n" ) + @payload.pack( "C*" )
  end
end


class ICMPEchoReply < ICMPPacket
  def initialize payload
    super( 0x00, 0x00, payload )
  end
end


module RouterUtils
  def create_arp_request_from interface, addr
    arp = ARPRequest.new( interface.hwaddr, addr, interface.ipaddr )
    arp.pack
  end


  def create_arp_reply_from message, replyaddr
    arp = ARPReply.new( message.eth_src, replyaddr, message.arp_spa, message.arp_tpa )
    arp.pack
  end


  def create_icmpv4_reply entry, interface, message
    offset = 14 + 20 + 4
    payload = message.data[ offset .. message.data.length - 1 ]
    icmp = ICMPEchoReply.new( payload )
    ip_packet = IPPacket.new( :id => message.ipv4_id,
                              :protocol => message.ip_proto,
                              :ttl => 64,
                              :daddr => message.ipv4_src,
                              :saddr => message.ipv4_dst,
                              :payload => icmp )
    eth_header = EthernetHeader.new( entry.hwaddr, interface.hwaddr, 0x0800 )

    eth_header.pack + ip_packet.pack
  end
end


### Local variables:
### mode: Ruby
### coding: utf-8
### indent-tabs-mode: nil
### End: