module PacketFu # Implements the Explict Congestion Notification for TCPHeader. # # ==== Header Definition # # # Fixnum (1 bit) :n # Fixnum (1 bit) :c # Fixnum (1 bit) :e class TcpEcn < Struct.new(:n, :c, :e) include StructFu def initialize(args={}) super(args[:n], args[:c], args[:e]) if args end # Returns the TcpEcn field as an integer... even though it's going # to be split across a byte boundary. def to_i (n.to_i << 2) + (c.to_i << 1) + e.to_i end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? || str.size < 2 if 1.respond_to? :ord byte1 = str[0].ord byte2 = str[1].ord else byte1 = str[0] byte2 = str[1] end self[:n] = byte1 & 0b00000001 == 0b00000001 ? 1 : 0 self[:c] = byte2 & 0b10000000 == 0b10000000 ? 1 : 0 self[:e] = byte2 & 0b01000000 == 0b01000000 ? 1 : 0 self end end # Implements the Header Length for TCPHeader. # # ==== Header Definition # # Fixnum (4 bits) :hlen class TcpHlen < Struct.new(:hlen) include StructFu def initialize(args={}) super(args[:hlen]) end # Returns the TcpHlen field as an integer. Note these will become the high # bits at the TCP header's offset, even though the lower 4 bits # will be further chopped up. def to_i hlen.to_i & 0b1111 end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? || str.size.zero? if 1.respond_to? :ord self[:hlen] = (str[0].ord & 0b11110000) >> 4 else self[:hlen] = (str[0] & 0b11110000) >> 4 end self end # Returns the object in string form. def to_s [self.to_i].pack("C") end end # Implements the Reserved bits for TCPHeader. # # ==== Header Definition # # # Fixnum (1 bit) :r1 # Fixnum (1 bit) :r2 # Fixnum (1 bit) :r3 class TcpReserved < Struct.new(:r1, :r2, :r3) include StructFu def initialize(args={}) super( args[:r1] || 0, args[:r2] || 0, args[:r3] || 0) if args.kind_of? Hash end # Returns the Reserved field as an integer. def to_i (r1.to_i << 2) + (r2.to_i << 1) + r3.to_i end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? || str.size.zero? if 1.respond_to? :ord byte = str[0].ord else byte = str[0] end self[:r1] = byte & 0b00000100 == 0b00000100 ? 1 : 0 self[:r2] = byte & 0b00000010 == 0b00000010 ? 1 : 0 self[:r3] = byte & 0b00000001 == 0b00000001 ? 1 : 0 self end end # Implements flags for TCPHeader. # # ==== Header Definition # # Fixnum (1 bit) :urg # Fixnum (1 bit) :ack # Fixnum (1 bit) :psh # Fixnum (1 bit) :rst # Fixnum (1 bit) :syn # Fixnum (1 bit) :fin # # Flags can typically be set by setting them either to 1 or 0, or to true or false. class TcpFlags < Struct.new(:urg, :ack, :psh, :rst, :syn, :fin) include StructFu def initialize(args={}) # This technique attemts to ensure that flags are always 0 (off) # or 1 (on). Statements like nil and false shouldn't be lurking in here. if args.nil? || args.size.zero? super( 0, 0, 0, 0, 0, 0) else super( (args[:urg] ? 1 : 0), (args[:ack] ? 1 : 0), (args[:psh] ? 1 : 0), (args[:rst] ? 1 : 0), (args[:syn] ? 1 : 0), (args[:fin] ? 1 : 0) ) end end # Returns the TcpFlags as an integer. # Also not a great candidate for to_s due to the short bitspace. def to_i (urg.to_i << 5) + (ack.to_i << 4) + (psh.to_i << 3) + (rst.to_i << 2) + (syn.to_i << 1) + fin.to_i end # Helper to determine if this flag is a 1 or a 0. def zero_or_one(i=0) if i == 0 || i == false || i == nil 0 else 1 end end # Setter for the Urgent flag. def urg=(i); self[:urg] = zero_or_one(i); end # Setter for the Acknowlege flag. def ack=(i); self[:ack] = zero_or_one(i); end # Setter for the Push flag. def psh=(i); self[:psh] = zero_or_one(i); end # Setter for the Reset flag. def rst=(i); self[:rst] = zero_or_one(i); end # Setter for the Synchronize flag. def syn=(i); self[:syn] = zero_or_one(i); end # Setter for the Finish flag. def fin=(i); self[:fin] = zero_or_one(i); end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? if 1.respond_to? :ord byte = str[0].ord else byte = str[0] end self[:urg] = byte & 0b00100000 == 0b00100000 ? 1 : 0 self[:ack] = byte & 0b00010000 == 0b00010000 ? 1 : 0 self[:psh] = byte & 0b00001000 == 0b00001000 ? 1 : 0 self[:rst] = byte & 0b00000100 == 0b00000100 ? 1 : 0 self[:syn] = byte & 0b00000010 == 0b00000010 ? 1 : 0 self[:fin] = byte & 0b00000001 == 0b00000001 ? 1 : 0 self end end end module PacketFu # TcpOption is the base class for all TCP options. Note that TcpOption#len # returns the size of the entire option, while TcpOption#optlen is the struct # for the TCP Option Length field. # # Subclassed options should set the correct TcpOption#kind by redefining # initialize. They should also deal with various value types there by setting # them explicitly with an accompanying StructFu#typecast for the setter. # # By default, values are presumed to be strings, unless they are Numeric, in # which case a guess is made to the width of the Numeric based on the given # optlen. # # Note that normally, optlen is /not/ enforced for directly setting values, # so the user is perfectly capable of setting incorrect lengths. class TcpOption < Struct.new(:kind, :optlen, :value) include StructFu def initialize(args={}) super( Int8.new(args[:kind]), Int8.new(args[:optlen]) ) if args[:value].kind_of? Numeric self[:value] = case args[:optlen] when 3; Int8.new(args[:value]) when 4; Int16.new(args[:value]) when 6; Int32.new(args[:value]) else; StructFu::String.new.read(args[:value]) end else self[:value] = StructFu::String.new.read(args[:value]) end end # Returns the object in string form. def to_s self[:kind].to_s + (self[:optlen].value.nil? ? nil : self[:optlen]).to_s + (self[:value].nil? ? nil : self[:value]).to_s end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? self[:kind].read(str[0,1]) if str[1,1] self[:optlen].read(str[1,1]) if str[2,1] && optlen.value > 2 self[:value].read(str[2,optlen.value-2]) end end self end # The default decode for an unknown option. Known options should redefine this. def decode unk = "unk-#{self.kind.to_i}" (self[:optlen].to_i > 2 && self[:value].to_s.size > 1) ? [unk,self[:value]].join(":") : unk end # Setter for the "kind" byte of this option. def kind=(i); typecast i; end # Setter for the "option length" byte for this option. def optlen=(i); typecast i; end # Setter for the value of this option. def value=(i) if i.kind_of? Numeric typecast i elsif i.respond_to? :to_s self[:value] = i else self[:value] = '' end end # Generally, encoding a value is going to be just a read. Some # options will treat things a little differently; TS for example, # takes two values and concatenates them. def encode(str) self[:value] = self.class.new(:value => str).value end # Returns true if this option has an optlen. Some don't. def has_optlen? (kind.value && kind.value < 2) ? false : true end # Returns true if this option has a value. Some don't. def has_value? (value.respond_to? :to_s && value.to_s.size > 0) ? false : true end # End of Line option. Usually used to terminate a string of options. # # http://www.networksorcery.com/enp/protocol/tcp/option000.htm class EOL < TcpOption def initialize(args={}) super( args.merge(:kind => 0) ) end def decode "EOL" end end # No Operation option. Usually used to pad out options to fit a 4-byte alignment. # # http://www.networksorcery.com/enp/protocol/tcp/option001.htm class NOP < TcpOption def initialize(args={}) super( args.merge(:kind => 1) ) end def decode "NOP" end end # Maximum Segment Size option. # # http://www.networksorcery.com/enp/protocol/tcp/option002.htm class MSS < TcpOption def initialize(args={}) super( args.merge(:kind => 2, :optlen => 4 ) ) self[:value] = Int16.new(args[:value]) end def value=(i); typecast i; end # MSS options with lengths other than 4 are malformed. def decode if self[:optlen].to_i == 4 "MSS:#{self[:value].to_i}" else "MSS-bad:#{self[:value]}" end end end # Window Size option. # # http://www.networksorcery.com/enp/protocol/tcp/option003.htm class WS < TcpOption def initialize(args={}) super( args.merge(:kind => 3, :optlen => 3 ) ) self[:value] = Int8.new(args[:value]) end def value=(i); typecast i; end # WS options with lengths other than 3 are malformed. def decode if self[:optlen].to_i == 3 "WS:#{self[:value].to_i}" else "WS-bad:#{self[:value]}" end end end # Selective Acknowlegment OK option. # # http://www.networksorcery.com/enp/protocol/tcp/option004.htm class SACKOK < TcpOption def initialize(args={}) super( args.merge(:kind => 4, :optlen => 2) ) end # SACKOK options with sizes other than 2 are malformed. def decode if self[:optlen].to_i == 2 "SACKOK" else "SACKOK-bad:#{self[:value]}" end end end # Selective Acknowledgement option. # # http://www.networksorcery.com/enp/protocol/tcp/option004.htm # # Note that SACK always takes its optlen from the size of the string. class SACK < TcpOption def initialize(args={}) super( args.merge(:kind => 5, :optlen => ((args[:value] || "").size + 2) ) ) end def optlen=(i); typecast i; end def value=(i) self[:optlen] = Int8.new(i.to_s.size + 2) self[:value] = StructFu::String.new(i) end def decode "SACK:#{self[:value]}" end def encode(str) temp_obj = self.class.new(:value => str) self[:value] = temp_obj.value self[:optlen] = temp_obj.optlen.value self end end # Echo option. # # http://www.networksorcery.com/enp/protocol/tcp/option006.htm class ECHO < TcpOption def initialize(args={}) super( args.merge(:kind => 6, :optlen => 6 ) ) end # ECHO options with lengths other than 6 are malformed. def decode if self[:optlen].to_i == 6 "ECHO:#{self[:value]}" else "ECHO-bad:#{self[:value]}" end end end # Echo Reply option. # # http://www.networksorcery.com/enp/protocol/tcp/option007.htm class ECHOREPLY < TcpOption def initialize(args={}) super( args.merge(:kind => 7, :optlen => 6 ) ) end # ECHOREPLY options with lengths other than 6 are malformed. def decode if self[:optlen].to_i == 6 "ECHOREPLY:#{self[:value]}" else "ECHOREPLY-bad:#{self[:value]}" end end end # Timestamp option # # http://www.networksorcery.com/enp/protocol/tcp/option008.htm class TS < TcpOption def initialize(args={}) super( args.merge(:kind => 8, :optlen => 10 ) ) self[:value] = StructFu::String.new.read(args[:value] || "\x00" * 8) end # TS options with lengths other than 10 are malformed. def decode if self[:optlen].to_i == 10 val1,val2 = self[:value].unpack("NN") "TS:#{val1};#{val2}" else "TS-bad:#{self[:value]}" end end # TS options are in the format of "TS:[timestamp value];[timestamp secret]" Both # should be written as decimal numbers. def encode(str) if str =~ /^([0-9]+);([0-9]+)$/ tsval,tsecr = str.split(";").map {|x| x.to_i} if tsval <= 0xffffffff && tsecr <= 0xffffffff self[:value] = StructFu::String.new([tsval,tsecr].pack("NN")) else self[:value] = StructFu::String.new(str) end else self[:value] = StructFu::String.new(str) end end end end class TcpOptions < Array include StructFu # If args[:pad] is set, the options line is automatically padded out # with NOPs. def to_s(args={}) opts = self.map {|x| x.to_s}.join if args[:pad] unless (opts.size % 4).zero? (4 - (opts.size % 4)).times { opts << "\x01" } end end opts end def force_binary(str) str.force_encoding "binary" if str.respond_to? :force_encoding end # Reads a string to populate the object. def read(str) self.clear if self.size > 0 force_binary(str) return self if(!str.respond_to? :to_s || str.nil?) i = 0 while i < str.to_s.size this_opt = case str[i,1].unpack("C").first when 0; TcpOption::EOL.new when 1; TcpOption::NOP.new when 2; TcpOption::MSS.new when 3; TcpOption::WS.new when 4; TcpOption::SACKOK.new when 5; TcpOption::SACK.new when 6; TcpOption::ECHO.new when 7; TcpOption::ECHOREPLY.new when 8; TcpOption::TS.new else; TcpOption.new end this_opt.read str[i,str.size] unless this_opt.has_optlen? this_opt.value = nil this_opt.optlen = nil end self << this_opt i += this_opt.sz end self end # Decode parses the TcpOptions object's member options, and produces a # human-readable string by iterating over each element's decode() function. # If TcpOptions elements were not initially created as TcpOptions, an # attempt will be made to convert them. # # The output of decode is suitable as input for TcpOptions#encode. def decode decoded = self.map do |x| if x.kind_of? TcpOption x.decode else x = TcpOptions.new.read(x).decode end end decoded.join(",") end # Encode takes a human-readable string and appends the corresponding # binary options to the TcpOptions object. To completely replace the contents # of the object, use TcpOptions#encode! instead. # # Options are comma-delimited, and are identical to the output of the # TcpOptions#decode function. Note that the syntax can be unforgiving, so # it may be easier to create the subclassed TcpOptions themselves directly, # but this method can be less typing if you know what you're doing. # # Note that by using TcpOptions#encode, strings supplied as values which # can be converted to numbers will be converted first. # # == Example # # t = TcpOptions.new # t.encode("MS:1460,WS:6") # t.to_s # => "\002\004\005\264\002\003\006" # t.encode("NOP") # t.to_s # => "\002\004\005\264\002\003\006\001" def encode(str) opts = str.split(/[\s]*,[\s]*/) opts.each do |o| kind,value = o.split(/[\s]*:[\s]*/) klass = TcpOption.const_get(kind.upcase) value = value.to_i if value =~ /^[0-9]+$/ this_opt = klass.new this_opt.encode(value) self << this_opt end self end # Like TcpOption#encode, except the entire contents are replaced. def encode!(str) self.clear if self.size > 0 encode(str) end end end module PacketFu # TCPHeader is a complete TCP struct, used in TCPPacket. Most IP traffic is TCP-based, by # volume. # # For more on TCP packets, see http://www.networksorcery.com/enp/protocol/tcp.htm # # ==== Header Definition # # Int16 :tcp_src Default: random # Int16 :tcp_dst # Int32 :tcp_seq Default: random # Int32 :tcp_ack # TcpHlen :tcp_hlen Default: 5 # Must recalc as options are set. # TcpReserved :tcp_reserved Default: 0 # TcpEcn :tcp_ecn # TcpFlags :tcp_flags # Int16 :tcp_win, Default: 0 # WinXP's default syn packet # Int16 :tcp_sum, Default: calculated # Must set this upon generation. # Int16 :tcp_urg # TcpOptions :tcp_opts # String :body # # See also TcpHlen, TcpReserved, TcpEcn, TcpFlags, TcpOpts class TCPHeader < Struct.new(:tcp_src, :tcp_dst, :tcp_seq, :tcp_ack, :tcp_hlen, :tcp_reserved, :tcp_ecn, :tcp_flags, :tcp_win, :tcp_sum, :tcp_urg, :tcp_opts, :body) include StructFu def initialize(args={}) @random_seq = rand(0xffffffff) @random_src = rand_port super( Int16.new(args[:tcp_src] || tcp_calc_src), Int16.new(args[:tcp_dst]), Int32.new(args[:tcp_seq] || tcp_calc_seq), Int32.new(args[:tcp_ack]), TcpHlen.new(:hlen => (args[:tcp_hlen] || 5)), TcpReserved.new(args[:tcp_reserved] || 0), TcpEcn.new(args[:tcp_ecn]), TcpFlags.new(args[:tcp_flags]), Int16.new(args[:tcp_win] || 0x4000), Int16.new(args[:tcp_sum] || 0), Int16.new(args[:tcp_urg]), TcpOptions.new.read(args[:tcp_opts]), StructFu::String.new.read(args[:body]) ) end attr_accessor :flavor # Helper function to create the string for Hlen, Reserved, ECN, and Flags. def bits_to_s bytes = [] bytes[0] = (self[:tcp_hlen].to_i << 4) + (self[:tcp_reserved].to_i << 1) + self[:tcp_ecn].n.to_i bytes[1] = (self[:tcp_ecn].c.to_i << 7) + (self[:tcp_ecn].e.to_i << 6) + self[:tcp_flags].to_i bytes.pack("CC") end # Returns the object in string form. def to_s hdr = self.to_a.map do |x| if x.kind_of? TcpHlen bits_to_s elsif x.kind_of? TcpReserved next elsif x.kind_of? TcpEcn next elsif x.kind_of? TcpFlags next else x.to_s end end hdr.flatten.join end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? self[:tcp_src].read(str[0,2]) self[:tcp_dst].read(str[2,2]) self[:tcp_seq].read(str[4,4]) self[:tcp_ack].read(str[8,4]) self[:tcp_hlen].read(str[12,1]) self[:tcp_reserved].read(str[12,1]) self[:tcp_ecn].read(str[12,2]) self[:tcp_flags].read(str[13,1]) self[:tcp_win].read(str[14,2]) self[:tcp_sum].read(str[16,2]) self[:tcp_urg].read(str[18,2]) self[:tcp_opts].read(str[20,((self[:tcp_hlen].to_i * 4) - 20)]) self[:body].read(str[(self[:tcp_hlen].to_i * 4),str.size]) self end # Setter for the TCP source port. def tcp_src=(i); typecast i; end # Getter for the TCP source port. def tcp_src; self[:tcp_src].to_i; end # Setter for the TCP destination port. def tcp_dst=(i); typecast i; end # Getter for the TCP destination port. def tcp_dst; self[:tcp_dst].to_i; end # Setter for the TCP sequence number. def tcp_seq=(i); typecast i; end # Getter for the TCP sequence number. def tcp_seq; self[:tcp_seq].to_i; end # Setter for the TCP ackowlegement number. def tcp_ack=(i); typecast i; end # Getter for the TCP ackowlegement number. def tcp_ack; self[:tcp_ack].to_i; end # Setter for the TCP window size number. def tcp_win=(i); typecast i; end # Getter for the TCP window size number. def tcp_win; self[:tcp_win].to_i; end # Setter for the TCP checksum. def tcp_sum=(i); typecast i; end # Getter for the TCP checksum. def tcp_sum; self[:tcp_sum].to_i; end # Setter for the TCP urgent field. def tcp_urg=(i); typecast i; end # Getter for the TCP urgent field. def tcp_urg; self[:tcp_urg].to_i; end # Getter for the TCP Header Length value. def tcp_hlen; self[:tcp_hlen].to_i; end # Setter for the TCP Header Length value. def tcp_hlen=(i) if i.kind_of? PacketFu::TcpHlen self[:tcp_hlen]=i else self[:tcp_hlen].read(i) end end # Getter for the TCP Reserved field. def tcp_reserved; self[:tcp_reserved].to_i; end # Setter for the TCP Reserved field. def tcp_reserved=(i) if i.kind_of? PacketFu::TcpReserved self[:tcp_reserved]=i else self[:tcp_reserved].read(i) end end # Getter for the ECN bits. def tcp_ecn; self[:tcp_ecn].to_i; end # Setter for the ECN bits. def tcp_ecn=(i) if i.kind_of? PacketFu::TcpEcn self[:tcp_ecn]=i else self[:tcp_ecn].read(i) end end # Getter for TCP Options. def tcp_opts; self[:tcp_opts].to_s; end # Setter for TCP Options. def tcp_opts=(i) if i.kind_of? PacketFu::TcpOptions self[:tcp_opts]=i else self[:tcp_opts].read(i) end end # Resets the sequence number to a new random number. def tcp_calc_seq; @random_seq; end # Resets the source port to a new random number. def tcp_calc_src; @random_src; end # Returns the actual length of the TCP options. def tcp_opts_len self[:tcp_opts].to_s.size end # Sets and returns the true length of the TCP Header. # TODO: Think about making all the option stuff safer. def tcp_calc_hlen self[:tcp_hlen] = TcpHlen.new(:hlen => ((20 + tcp_opts_len) / 4)) end # Generates a random high port. This is affected by packet flavor. def rand_port rand(0xffff - 1025) + 1025 end # Gets a more readable option list. def tcp_options self[:tcp_opts].decode end # Sets a more readable option list. def tcp_options=(arg) self[:tcp_opts].encode arg end # Equivalent to tcp_src. def tcp_sport self.tcp_src.to_i end # Equivalent to tcp_src=. def tcp_sport=(arg) self.tcp_src=(arg) end # Equivalent to tcp_dst. def tcp_dport self.tcp_dst.to_i end # Equivalent to tcp_dst=. def tcp_dport=(arg) self.tcp_dst=(arg) end # Recalculates calculated fields for TCP (except checksum which is at the Packet level). def tcp_recalc(arg=:all) case arg when :tcp_hlen tcp_calc_hlen when :tcp_src @random_tcp_src = rand_port when :tcp_sport @random_tcp_src = rand_port when :tcp_seq @random_tcp_seq = rand(0xffffffff) when :all tcp_calc_hlen @random_tcp_src = rand_port @random_tcp_seq = rand(0xffffffff) else raise ArgumentError, "No such field `#{arg}'" end end end # TCPPacket is used to construct TCP packets. They contain an EthHeader, an IPHeader, and a TCPHeader. # # == Example # # tcp_pkt = PacketFu::TCPPacket.new # tcp_pkt.tcp_flags.syn=1 # tcp_pkt.tcp_dst=80 # tcp_pkt.tcp_win=5840 # tcp_pkt.tcp_options="mss:1460,sack.ok,ts:#{rand(0xffffffff)};0,nop,ws:7" # # tcp_pkt.ip_saddr=[rand(0xff),rand(0xff),rand(0xff),rand(0xff)].join('.') # tcp_pkt.ip_daddr=[rand(0xff),rand(0xff),rand(0xff),rand(0xff)].join('.') # # tcp_pkt.recalc # tcp_pkt.to_f('/tmp/tcp.pcap') # # == Parameters # :eth # A pre-generated EthHeader object. # :ip # A pre-generated IPHeader object. # :flavor # TODO: Sets the "flavor" of the TCP packet. This will include TCP options and the initial window # size, per stack. There is a lot of variety here, and it's one of the most useful methods to # remotely fingerprint devices. :flavor will span both ip and tcp for consistency. # :type # TODO: Set up particular types of packets (syn, psh_ack, rst, etc). This can change the initial flavor. # :config # A hash of return address details, often the output of Utils.whoami? class TCPPacket < Packet attr_accessor :eth_header, :ip_header, :tcp_header, :headers def initialize(args={}) @eth_header = (args[:eth] || EthHeader.new) @ip_header = (args[:ip] || IPHeader.new) @tcp_header = (args[:tcp] || TCPHeader.new) @tcp_header.flavor = args[:flavor].to_s.downcase @ip_header.body = @tcp_header @eth_header.body = @ip_header @headers = [@eth_header, @ip_header, @tcp_header] @ip_header.ip_proto=0x06 super if args[:flavor] tcp_calc_flavor(@tcp_header.flavor) else tcp_calc_sum end end # Sets the correct flavor for TCP Packets. Recognized flavors are: # windows, linux, freebsd def tcp_calc_flavor(str) ts_val = Time.now.to_i + rand(0x4fffffff) ts_sec = rand(0xffffff) case @tcp_header.flavor = str.to_s.downcase when "windows" # WinXP's default syn @tcp_header.tcp_win = 0x4000 @tcp_header.tcp_options="MSS:1460,NOP,NOP,SACKOK" @tcp_header.tcp_src = rand(5000 - 1026) + 1026 @ip_header.ip_ttl = 64 when "linux" # Ubuntu Linux 2.6.24-19-generic default syn @tcp_header.tcp_win = 5840 @tcp_header.tcp_options="MSS:1460,SACKOK,TS:#{ts_val};0,NOP,WS:7" @tcp_header.tcp_src = rand(61_000 - 32_000) + 32_000 @ip_header.ip_ttl = 64 when "freebsd" # Freebsd @tcp_header.tcp_win = 0xffff @tcp_header.tcp_options="MSS:1460,NOP,WS:3,NOP,NOP,TS:#{ts_val};#{ts_sec},SACKOK,EOL,EOL" @ip_header.ip_ttl = 64 else @tcp_header.tcp_options="MSS:1460,NOP,NOP,SACKOK" end tcp_calc_sum end # tcp_calc_sum() computes the TCP checksum, and is called upon intialization. It usually # should be called just prior to dropping packets to a file or on the wire. #-- # This is /not/ delegated down to @tcp_header since we need info # from the IP header, too. #++ def tcp_calc_sum checksum = (ip_src.to_i >> 16) checksum += (ip_src.to_i & 0xffff) checksum += (ip_dst.to_i >> 16) checksum += (ip_dst.to_i & 0xffff) checksum += 0x06 # TCP Protocol. checksum += (ip_len.to_i - ((ip_hl.to_i) * 4)) checksum += tcp_src checksum += tcp_dst checksum += (tcp_seq.to_i >> 16) checksum += (tcp_seq.to_i & 0xffff) checksum += (tcp_ack.to_i >> 16) checksum += (tcp_ack.to_i & 0xffff) checksum += ((tcp_hlen << 12) + (tcp_reserved << 9) + (tcp_ecn.to_i << 6) + tcp_flags.to_i ) checksum += tcp_win checksum += tcp_urg chk_tcp_opts = (tcp_opts.to_s.size % 2 == 0 ? tcp_opts.to_s : tcp_opts.to_s + "\x00") chk_tcp_opts.unpack("n*").each {|x| checksum = checksum + x } if (ip_len - ((ip_hl + tcp_hlen) * 4)) >= 0 real_tcp_payload = payload[0,( ip_len - ((ip_hl + tcp_hlen) * 4) )] # Can't forget those pesky FCSes! else real_tcp_payload = payload # Something's amiss here so don't bother figuring out where the real payload is. end chk_payload = (real_tcp_payload.size % 2 == 0 ? real_tcp_payload : real_tcp_payload + "\x00") # Null pad if it's odd. chk_payload.unpack("n*").each {|x| checksum = checksum+x } checksum = checksum % 0xffff checksum = 0xffff - checksum checksum == 0 ? 0xffff : checksum @tcp_header.tcp_sum = checksum end # Recalculates various fields of the TCP packet. # # ==== Parameters # # :all # Recomputes all calculated fields. # :tcp_sum # Recomputes the TCP checksum. # :tcp_hlen # Recomputes the TCP header length. Useful after options are added. def tcp_recalc(arg=:all) case arg when :tcp_sum tcp_calc_sum when :tcp_hlen @tcp_header.tcp_recalc :tcp_hlen when :all @tcp_header.tcp_recalc :all tcp_calc_sum else raise ArgumentError, "No such field `#{arg}'" end end # Peek provides summary data on packet contents. def peek(args={}) peek_data = ["T "] peek_data << "%-5d" % self.to_s.size peek_data << "%-21s" % "#{self.ip_saddr}:#{self.tcp_src}" peek_data << "->" peek_data << "%21s" % "#{self.ip_daddr}:#{self.tcp_dst}" flags = ' [' flags << (self.tcp_flags.urg.zero? ? "." : "U") flags << (self.tcp_flags.ack.zero? ? "." : "A") flags << (self.tcp_flags.psh.zero? ? "." : "P") flags << (self.tcp_flags.rst.zero? ? "." : "R") flags << (self.tcp_flags.syn.zero? ? "." : "S") flags << (self.tcp_flags.fin.zero? ? "." : "F") flags << '] ' peek_data << flags peek_data << "S:" peek_data << "%08x" % self.tcp_seq peek_data << "|I:" peek_data << "%04x" % self.ip_id peek_data.join end end end