class Mu::Pcap::IPv4
Constants
- FMT_HEADER
- FMT_PSEUDO_HEADER
- HTON
- IP_DF
- IP_MF
- IP_OFFMASK
- IP_RF
- NTOP
- ReassembleState
Attributes
dscp[RW]
dst[RW]
ip_id[RW]
offset[RW]
proto[RW]
src[RW]
ttl[RW]
Public Class Methods
build_pkt(lv2_pkt,src=nil, dst=nil, ip_id=0, offset=0, ttl=64, proto=0, dscp=0)
click to toggle source
# File lib/woolen_common/pcap/mu/pcap/ipv4.rb, line 20 def build_pkt(lv2_pkt,src=nil, dst=nil, ip_id=0, offset=0, ttl=64, proto=0, dscp=0) if lv2_pkt && lv2_pkt.is_a?(Packet) the_ip_pkt = self.new(src, dst, ip_id, offset, ttl, proto, dscp) lv2_pkt.payload = the_ip_pkt the_ip_pkt else raise 'can not build udp pkt with not Packet' end end
check_options(options, label='IPv4')
click to toggle source
Check that IP
or TCP
options are valid. Do nothing if they are valid. Both IP
and TCP
options are 8-bit TLVs with an inclusive length. Both have one byte options 0 and 1.
# File lib/woolen_common/pcap/mu/pcap/ipv4.rb, line 150 def self.check_options options, label='IPv4' while not options.empty? type = options.slice!(0, 1)[0].ord if type == 0 or type == 1 next end Pcap.assert !options.empty?, "#{label} option #{type} is missing the length field" length = options.slice!(0, 1)[0].ord Pcap.assert length >= 2, "#{label} option #{type} has invalid length: #{length}" Pcap.assert length - 2 <= options.length, "#{label} option #{type} has truncated data" options.slice! 0, length - 2 end end
from_bytes(bytes)
click to toggle source
# File lib/woolen_common/pcap/mu/pcap/ipv4.rb, line 59 def self.from_bytes bytes bytes.length >= 20 or raise ParseError, "Truncated IPv4 header: expected at least 20 bytes, got #{bytes.length} bytes" vhl, tos, length, id, offset, ttl, proto, checksum, src, dst = bytes[0, 20].unpack FMT_HEADER version = vhl >> 4 hl = (vhl & 0b1111) * 4 version == 4 or raise ParseError, "Wrong IPv4 version: got (#{version})" hl >= 20 or raise ParseError, "Bad IPv4 header length: expected at least 20 bytes raise ParseError, got #{hl} bytes" bytes.length >= hl or raise ParseError, "Truncated IPv4 header: expected #{hl} bytes raise ParseError, got #{bytes.length} bytes" length >= 20 or raise ParseError, "Bad IPv4 packet length: expected at least 20 bytes raise ParseError, got #{length} bytes" bytes.length >= length or raise ParseError, "Truncated IPv4 packet: expected #{length} bytes raise ParseError, got #{bytes.length} bytes" if hl != 20 IPv4.check_options bytes[20, hl-20] end src = NTOP[src] ||= IPAddr.ntop(src) dst = NTOP[dst] ||= IPAddr.ntop(dst) dscp = tos >> 2 ipv4 = IPv4.new(src, dst, id, offset, ttl, proto, dscp) ipv4.payload_raw = bytes[hl..-1] payload = bytes[hl...length] if offset & (IP_OFFMASK | IP_MF) == 0 begin case proto when IPPROTO_TCP ipv4.payload = TCP.from_bytes payload when IPPROTO_UDP ipv4.payload = UDP.from_bytes payload when IPPROTO_SCTP #ipv4.payload = SCTP.from_bytes payload ipv4.payload = payload else ipv4.payload = payload end rescue ParseError => e Pcap.warning e end else ipv4.payload = payload end return ipv4 end
new(src=nil, dst=nil, ip_id=0, offset=0, ttl=64, proto=0, dscp=0)
click to toggle source
Calls superclass method
Mu::Pcap::IP::new
# File lib/woolen_common/pcap/mu/pcap/ipv4.rb, line 33 def initialize src=nil, dst=nil, ip_id=0, offset=0, ttl=64, proto=0, dscp=0 super() @ip_id = ip_id @offset = offset @ttl = ttl @proto = proto @src = src @dst = dst @dscp = dscp end
reassemble(packets)
click to toggle source
Reassemble fragmented IPv4
packets
# File lib/woolen_common/pcap/mu/pcap/ipv4.rb, line 170 def self.reassemble packets reassembled_packets = [] flow_id_to_state = {} packets.each do |packet| if not packet.is_a?(Ethernet) or not packet.payload.is_a?(IPv4) # Ignore non-IPv4 packet elsif not packet.payload.fragment? # Ignore non-fragments else # Get reassembly state ip = packet.payload flow_id = [ip.ip_id, ip.proto, ip.src, ip.dst] state = flow_id_to_state[flow_id] if not state state = ReassembleState.new [], [], true, false flow_id_to_state[flow_id] = state end state.packets << packet # Clear the more-fragments flag if no more fragments if ip.offset & IP_MF == 0 state.mf = false end # Add the bytes start = (ip.offset & IP_OFFMASK) * 8 finish = start + ip.payload.length state.bytes.fill nil, start, finish - start start.upto(finish-1) do |i| if not state.bytes[i] byte = ip.payload[i - start].chr state.bytes[i] = byte elsif not state.overlap name = "%s:%s:%d" % [ip.src, ip.dst, ip.proto] Pcap.warning \ "IPv4 flow #{name} contains overlapping fragements" state.overlap = true end end # We're done if we've received a fragment without the # more-fragments flag and all the bytes in the buffer have been # set. if not state.mf and state.bytes.all? # Remove fragments from reassembled_packets state.packets.each do |packet| reassembled_packets.delete_if do |reassembled_packet| packet.object_id == reassembled_packet.object_id end end # Remove state flow_id_to_state.delete flow_id # Create new packet packet = state.packets[0].deepdup ipv4 = packet.payload ipv4.offset = 0 ipv4.payload = state.bytes.join # Decode begin case ipv4.proto when IPPROTO_TCP ipv4.payload = TCP.from_bytes ipv4.payload when IPPROTO_UDP ipv4.payload = UDP.from_bytes ipv4.payload when IPPROTO_SCTP ipv4.payload = SCTP.from_bytes ipv4.payload end rescue ParseError => e Pcap.warning e end end end reassembled_packets << packet end if !flow_id_to_state.empty? Pcap.warning \ "#{flow_id_to_state.length} flow(s) have IPv4 fragments " \ "that can't be reassembled" end return reassembled_packets end
Public Instance Methods
==(other)
click to toggle source
Calls superclass method
Mu::Pcap::IP#==
# File lib/woolen_common/pcap/mu/pcap/ipv4.rb, line 262 def == other return super && self.proto == other.proto && self.ip_id == other.ip_id && self.offset == other.offset && self.ttl == other.ttl && self.dscp == other.dscp end
flow_id()
click to toggle source
# File lib/woolen_common/pcap/mu/pcap/ipv4.rb, line 48 def flow_id if not @payload or @payload.is_a? String [:ipv4, @proto, @src, @dst] else [:ipv4, @src, @dst, @payload.flow_id] end end
fragment?()
click to toggle source
# File lib/woolen_common/pcap/mu/pcap/ipv4.rb, line 143 def fragment? return (@offset & (IP_OFFMASK | IP_MF) != 0) end
pseudo_header(payload_length)
click to toggle source
# File lib/woolen_common/pcap/mu/pcap/ipv4.rb, line 137 def pseudo_header payload_length src = HTON[@src] ||= IPAddr.new(@src).hton dst = HTON[@dst] ||= IPAddr.new(@dst).hton return [src, dst, 0, @proto, payload_length].pack(FMT_PSEUDO_HEADER) end
to_s()
click to toggle source
# File lib/woolen_common/pcap/mu/pcap/ipv4.rb, line 253 def to_s if @payload.is_a? String payload = @payload.inspect else payload = @payload.to_s end return "ipv4(%d, %s, %s, %s)" % [@proto, @src, @dst, payload] end
v4?()
click to toggle source
# File lib/woolen_common/pcap/mu/pcap/ipv4.rb, line 44 def v4? true end
write(io)
click to toggle source
# File lib/woolen_common/pcap/mu/pcap/ipv4.rb, line 111 def write io if @payload.is_a? String payload = @payload else string_io = StringIO.new @payload.write string_io, self string_io.close payload = string_io.string end length = 20 + payload.bytesize if length > 65535 Pcap.warning "IPv4 payload is too large" end src = HTON[@src] ||= IPAddr.new(@src).hton dst = HTON[@dst] ||= IPAddr.new(@dst).hton fields = [0x45, @dscp << 2, length, @ip_id, @offset, @ttl, @proto, 0, src, dst] header = fields.pack(FMT_HEADER) fields[7] = IP.checksum(header) header = fields.pack(FMT_HEADER) io.write header io.write payload end