lib/packetgen/packet.rb in packetgen-3.3.3 vs lib/packetgen/packet.rb in packetgen-4.0.0

- old
+ new

@@ -59,24 +59,24 @@ # Create a new Packet # @param [String] protocol base protocol for packet # @param [Hash] options specific options for +protocol+ # @return [Packet] def self.gen(protocol, options={}) - self.new.add protocol, options + self.new.add(protocol, options) end # Parse a binary string and generate a Packet from it. # # auto-detect first header - # Packet.parse str + # Packet.parse(str) # # force decoding a Ethernet header for first header - # Packet.parse str, first_header: 'Eth' + # Packet.parse(str, first_header: 'Eth') # @param [String] binary_str # @param [String,nil] first_header First protocol header. +nil+ means discover it! # @return [Packet] # @raise [ArgumentError] +first_header+ is an unknown header def self.parse(binary_str, first_header: nil) - new.parse binary_str, first_header: first_header + new.parse(binary_str, first_header: first_header) end # Capture packets from wire. # Same arguments as {Capture#initialize} # @see Capture#initialize @@ -110,20 +110,27 @@ raise ArgumentError, e unless File.extname(filename.downcase) == '.pcap' Pcap.read(filename) end - # Write packets to +filename+ + # Write packets to +filename+, as pcap or PcapNG file # # For more options, see {PcapNG::File}. # @param [String] filename # @param [Array<Packet>] packets packets to write # @return [void] + # @since 4.0.0 write a pcap file if filename extension is +.pcap+ + # @author Sylvain Daubert + # @author LemonTree55 (pcap) def self.write(filename, packets) - pf = PcapNG::File.new - pf.array_to_file packets - pf.to_f filename + if filename.end_with?('.pcap') + Pcap.write(filename, packets) + else + pf = PcapNG::File.new + pf.read_array(packets) + pf.to_f(filename) + end end # @private def initialize @headers = [] @@ -140,11 +147,11 @@ klass = check_protocol(protocol) # options[:packet]= self is speedier than options.merge(packet: self) options[:packet] = self header = klass.new(options) - add_header header + add_header(header) self end # Insert a header in packet # @param [Header] prev header after which insert new one @@ -157,11 +164,11 @@ nxt = prev.body # options[:packet]= self is speedier than options.merge(packet: self) options[:packet] = self header = klass.new(options) - add_header header, previous_header: prev + add_header(header, previous_header: prev) idx = headers.index(prev) + 1 headers[idx, 0] = header header[:body] = nxt self end @@ -171,41 +178,44 @@ # pkt.is?('IP') #=> true # pkt.is?('TCP') #=> false # @return [Boolean] # @raise [ArgumentError] unknown protocol def is?(protocol) - klass = check_protocol protocol + klass = check_protocol(protocol) headers.any?(klass) end # Recalculate all packet checksums # @return [void] def calc_checksum headers.reverse_each do |header| - header.calc_checksum if header.respond_to? :calc_checksum + header.calc_checksum if header.respond_to?(:calc_checksum) end end # Recalculate all packet length fields # @return [void] def calc_length headers.reverse_each do |header| - header.calc_length if header.respond_to? :calc_length + header.calc_length if header.respond_to?(:calc_length) end end # Recalculate all calculatable fields (for now: length and checksum) # @return [void] + # @author LemonTree55 def calc - calc_length - calc_checksum + headers.reverse_each do |header| + header.calc_length if header.respond_to?(:calc_length) + header.calc_checksum if header.respond_to?(:calc_checksum) + end end # Get packet body # @return [Types] def body - last_header[:body] if last_header.respond_to? :body + last_header[:body] if last_header.respond_to?(:body) end # Set packet body # @param [String] str # @return [void] @@ -227,26 +237,26 @@ PcapNG::File.new.read_array([self]).to_f(filename) end alias write to_f # Send packet on wire. Use first header +#to_w+ method. - # @param [String] iface interface name. Default to first non-loopback interface + # @param [String,nil] iface interface name. Default to first non-loopback interface # @param [Boolean] calc if +true+, call {#calc} on packet before sending it. # @param [Integer] number number of times to send the packets # @param [Integer,Float] interval time, in seconds, between sending 2 packets # @return [void] # @since 2.1.4 add `calc`, `number` and `interval` parameters # @since 3.0.0 +calc+ defaults to +true+ def to_w(iface=nil, calc: true, number: 1, interval: 1) iface ||= PacketGen.default_iface - if first_header.respond_to? :to_w + if first_header.respond_to?(:to_w) self.calc if calc number.times do first_header.to_w(iface) - sleep interval if number > 1 + sleep(interval) if number > 1 end else type = first_header.protocol_name raise WireError, "don't known how to send a #{type} packet on wire" end @@ -255,16 +265,16 @@ # Encapulate another packet in +self+ # @param [Packet] other # @param [Boolean] parsing set to +true+ to not update last current header field # from binding with first other's one. Use only when current header field # has its value set accordingly. - # @return [self] +self+ with new headers from +other+ + # @return [self] +self+ updated with new headers from +other+ # @raise [BindingError] do not known how to encapsulate # @since 1.1.0 def encapsulate(other, parsing: false) other.headers.each_with_index do |h, i| - add_header h, parsing: i.positive? || parsing + add_header(h, parsing: i.positive? || parsing) end end # Remove headers from +self+ # @param [Array<Header>] hdrs @@ -284,12 +294,12 @@ raise FormatError, e.message end # Parse a binary string and populate Packet from it. # @param [String] binary_str - # @param [String,nil] first_header First protocol header. +nil+ means discover it! - # @return [Packet] self + # @param [String,nil] first_header First protocol header name. +nil+ means discover it! + # @return [self] # @raise [ParseError] +first_header+ is an unknown header # @raise [BindingError] unknwon binding between some headers def parse(binary_str, first_header: nil) headers.clear @@ -297,11 +307,11 @@ # No decoding forced for first header. Have to guess it! first_header = guess_first_header(binary_str) raise ParseError, "cannot identify first header in string: #{binary_str.inspect}" if first_header.nil? end - add first_header + add(first_header) headers[-1, 1] = last_header.read(binary_str) # Decode upper headers recursively decode_bottom_up self @@ -315,31 +325,33 @@ str << header.inspect end str << Inspect.inspect_body(body) end + # Check equality at binary level # @param [Packet] other # @return [Boolean] def ==(other) to_s == other.to_s end + # +true+ is {#==} is +true+ with another packet, or if +other+ is a protocol name String, whose protocol is in Packet. # @param [Packet] other # @return [Boolean] # @since 3.1.2 def ===(other) case other when PacketGen::Packet self == other when String - is? other + is?(other) else false end end - # Invert all possible fields in packet to create a reply. + # Invert all possible attributes.in packet to create a reply. # @return [self] # @since 2.7.0 def reply! headers.each do |header| header.reply! if header.respond_to?(:reply!) @@ -361,11 +373,11 @@ # Dup +@headers+ instance variable. Internally used by +#dup+ and +#clone+ # @return [void] def initialize_copy(_other) @headers = headers.map(&:dup) headers.each do |header| - add_magic_header_method header + add_magic_header_method(header) end invalidate_header_cache end # Give first header of packet @@ -408,21 +420,24 @@ idx = header_index(hdr) (idx + 1) < headers.size ? headers[idx + 1] : nil end # @overload header(klass, layer=1) + # Get a header given its class and its layer (example: IP-in-IP encapsulation) # @param [Class] klass # @param [Integer] layer # @overload header(klass, options={}) + # Get a header given its class, and set some attributes # @param [String] klass - # @param [Hash] options - # @raise [ArgumentError] unknown option - # @return [Header::Base] + # @param [Hash] options attributes to set + # @raise [ArgumentError] unknown attribute + # @return [Header::Base,nil] def header(klass, arg) layer = arg.is_a?(Integer) ? arg : 1 header = find_header(klass, layer) - return header unless arg.is_a? Hash + return nil if header.nil? + return header unless arg.is_a?(Hash) arg.each do |key, value| raise ArgumentError, "unknown #{key} attribute for #{klass}" unless header.respond_to?(:"#{key}=") header.send(:"#{key}=", value) @@ -432,16 +447,18 @@ end # Get header from cache, or find it in packet # @param [Class] klass # @param [Integer] layer - # @return [Header::Base] + # @return [Header::Base,nil] def find_header(klass, layer) header = fetch_header_from_cache(klass, layer) return header if header - header = headers.select { |h| h.is_a? klass }[layer - 1] + header = headers.select { |h| h.is_a?(klass) }[layer - 1] + return nil if header.nil? + add_header_to_cache(header, klass, layer) header end # check if protocol is known @@ -466,13 +483,13 @@ add_to_previous_header(prev_header, header, parsing) if prev_header header.packet = self headers << header unless previous_header - return if respond_to? header.method_name + return if respond_to?(header.method_name) - add_magic_header_method header + add_magic_header_method(header) end # Bind +header+ to +prev_header+. # @param [Headerable] prev_header # @param [Headerable] header @@ -495,11 +512,11 @@ "header(#{header.class}, arg); end" end # Try to guess header from +binary_str+ # @param [String] binary_str - # @return [String] header/protocol name + # @return [String,nil] header/protocol name, or nil if cannot be guessed def guess_first_header(binary_str) first_header = nil Header.all.each do |hklass| hdr = hklass.new(packet: self) # #read may return another object (more specific class) @@ -527,10 +544,10 @@ nheader = nh.new(packet: self) nheader = nheader.read(last_known_hdr.body) next unless nheader.parse? - add_header nheader, parsing: true + add_header(nheader, parsing: true) break if last_header == last_known_hdr end end # Search a upper header for +hdr+