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