require 'ipaddr' module PacketGen module Header # IPv6 header class # @author Sylvain Daubert class IPv6 < Struct.new(:version, :traffic_class, :flow_label, :length, :next, :hop, :src, :dst, :body) include StructFu include HeaderMethods extend HeaderClassMethods # IPv6 address, as a group of 8 2-byte words # @author Sylvain Daubert class Addr < Struct.new(:a1, :a2, :a3, :a4, :a5, :a6, :a7, :a8) include StructFu # @param [Hash] options # @option options [Integer] :a1 # @option options [Integer] :a2 # @option options [Integer] :a3 # @option options [Integer] :a4 # @option options [Integer] :a5 # @option options [Integer] :a6 # @option options [Integer] :a7 # @option options [Integer] :a8 def initialize(options={}) super Int16.new(options[:a1]), Int16.new(options[:a2]), Int16.new(options[:a3]), Int16.new(options[:a4]), Int16.new(options[:a5]), Int16.new(options[:a6]), Int16.new(options[:a7]), Int16.new(options[:a8]) end # Parse a colon-delimited address # @param [String] str # @return [self] def parse(str) return self if str.nil? addr = IPAddr.new(str) raise ArgumentError, 'string is not a IPv6 address' unless addr.ipv6? addri = addr.to_i self.a1 = addri >> 112 self.a2 = addri >> 96 & 0xffff self.a3 = addri >> 80 & 0xffff self.a4 = addri >> 64 & 0xffff self.a5 = addri >> 48 & 0xffff self.a6 = addri >> 32 & 0xffff self.a7 = addri >> 16 & 0xffff self.a8 = addri & 0xffff self end # Read a Addr6 from a binary string # @param [String] str # @return [self] def read(str) force_binary str self[:a1].read str[0, 2] self[:a2].read str[2, 2] self[:a3].read str[4, 2] self[:a4].read str[6, 2] self[:a5].read str[8, 2] self[:a6].read str[10, 2] self[:a7].read str[12, 2] self[:a8].read str[14, 2] self end %i(a1 a2 a3 a4 a5 a6 a7 a8).each do |sym| class_eval "def #{sym}; self[:#{sym}].to_i; end\n" \ "def #{sym}=(v); self[:#{sym}].read v; end" end # Addr6 in human readable form (colon-delimited hex string) # @return [String] def to_x IPAddr.new(to_a.map { |a| a.to_i.to_s(16) }.join(':')).to_s end end # @param [Hash] options # @option options [Integer] :version # @option options [Integer] :traffic_length # @option options [Integer] :flow_label # @option options [Integer] :length payload length # @option options [Integer] :next # @option options [Integer] :hop # @option options [String] :src colon-delimited source address # @option options [String] :dst colon-delimited destination address # @option options [String] :body binary string def initialize(options={}) super options[:version] || 6, options[:traffic_class] || 0, options[:flow_label] || 0, Int16.new(options[:length]), Int8.new(options[:next]), Int8.new(options[:hop] || 64), Addr.new.parse(options[:src] || '::1'), Addr.new.parse(options[:dst] || '::1'), StructFu::String.new.read(options[:body]) end # Read a IP header from a string # @param [String] str binary string # @return [self] def read(str) return self if str.nil? raise ParseError, 'string too short for Eth' if str.size < self.sz force_binary str first32 = str[0, 4].unpack('N').first self.version = first32 >> 28 self.traffic_class = (first32 >> 20) & 0xff self.flow_label = first32 & 0xfffff self[:length].read str[4, 2] self[:next].read str[6, 1] self[:hop].read str[7, 1] self[:src].read str[8, 16] self[:dst].read str[24, 16] self[:body].read str[40..-1] self end # Compute length and set +len+ field # @return [Integer] def calc_length self.length = body.length end # Getter for length attribute # @return [Integer] def length self[:length].to_i end # Setter for length attribute # @param [Integer] i # @return [Integer] def length=(i) self[:length].read i end # Getter for next attribute # @return [Integer] def next self[:next].to_i end # Setter for next attribute # @param [Integer] i # @return [Integer] def next=(i) self[:next].read i end # Getter for hop attribute # @return [Integer] def hop self[:hop].to_i end # Setter for hop attribute # @param [Integer] i # @return [Integer] def hop=(i) self[:hop].read i end # Getter for src attribute # @return [String] def src self[:src].to_x end alias :source :src # Setter for src attribute # @param [String] addr # @return [Integer] def src=(addr) self[:src].parse addr end alias :source= :src= # Getter for dst attribute # @return [String] def dst self[:dst].to_x end alias :destination :dst # Setter for dst attribute # @param [String] addr # @return [Integer] def dst=(addr) self[:dst].parse addr end alias :destination= :dst= # Get binary string # @return [String] def to_s first32 = (version << 28) | (traffic_class << 20) | flow_label [first32].pack('N') << to_a[3..-1].map { |field| field.to_s }.join end end Eth.bind_header IPv6, proto: 0x86DD IP.bind_header IPv6, proto: 41 # 6to4 end end