Class: Mu::Pcap::IPv4

Inherits:
IP show all
Defined in:
lib/diy/parser/mu/pcap/ipv4.rb

Defined Under Namespace

Classes: ReassembleState

Constant Summary collapse

IP_RF =

Reserved

0x8000
IP_DF =

Don’t fragment

0x4000
IP_MF =

More fragments

0x2000
IP_OFFMASK =
0x1fff
FMT_HEADER =
'CCnnnCCna4a4'
NTOP =

Network to human cache

{}
HTON =

Human to network cache

{}
FMT_PSEUDO_HEADER =
'a4a4CCn'

Constants inherited from IP

Mu::Pcap::IP::IPPROTO_AH, Mu::Pcap::IP::IPPROTO_DSTOPTS, Mu::Pcap::IP::IPPROTO_FRAGMENT, Mu::Pcap::IP::IPPROTO_HOPOPTS, Mu::Pcap::IP::IPPROTO_NONE, Mu::Pcap::IP::IPPROTO_ROUTING, Mu::Pcap::IP::IPPROTO_SCTP, Mu::Pcap::IP::IPPROTO_TCP, Mu::Pcap::IP::IPPROTO_UDP

Constants inherited from Packet

Packet::IGNORE_UDP_PORTS

Instance Attribute Summary collapse

Attributes inherited from Packet

#payload, #payload_raw

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from IP

checksum, #v6?

Methods inherited from Packet

#deepdup, isolate_l7, normalize, #payload_bytes, #to_bytes

Constructor Details

#initialize(src = nil, dst = nil, ip_id = 0, offset = 0, ttl = 64, proto = 0, dscp = 0) ⇒ IPv4

Returns a new instance of IPv4.



20
21
22
23
24
25
26
27
28
29
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 20

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

Instance Attribute Details

#dscpObject

Returns the value of attribute dscp.



18
19
20
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 18

def dscp
  @dscp
end

#dstObject

Returns the value of attribute dst.



18
19
20
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 18

def dst
  @dst
end

#ip_idObject

Returns the value of attribute ip_id.



18
19
20
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 18

def ip_id
  @ip_id
end

#offsetObject

Returns the value of attribute offset.



18
19
20
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 18

def offset
  @offset
end

#protoObject

Returns the value of attribute proto.



18
19
20
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 18

def proto
  @proto
end

#srcObject

Returns the value of attribute src.



18
19
20
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 18

def src
  @src
end

#ttlObject

Returns the value of attribute ttl.



18
19
20
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 18

def ttl
  @ttl
end

Class Method Details

.check_options(options, label = 'IPv4') ⇒ Object

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.



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 135

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) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 46

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

.reassemble(packets) ⇒ Object

Reassemble fragmented IPv4 packets



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 155

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

Instance Method Details

#==(other) ⇒ Object



247
248
249
250
251
252
253
254
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 247

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_idObject



35
36
37
38
39
40
41
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 35

def flow_id
    if not @payload or @payload.is_a? String
        return [:ipv4, @proto, @src, @dst]
    else
        return [:ipv4, @src, @dst, @payload.flow_id]
    end
end

#fragment?Boolean

Returns:

  • (Boolean)


128
129
130
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 128

def fragment?
    return (@offset & (IP_OFFMASK | IP_MF) != 0)
end

#pseudo_header(payload_length) ⇒ Object



122
123
124
125
126
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 122

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_sObject



238
239
240
241
242
243
244
245
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 238

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?Boolean

Returns:

  • (Boolean)


31
32
33
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 31

def v4?
    return true
end

#write(io) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/diy/parser/mu/pcap/ipv4.rb', line 98

def write io
    if @payload.is_a? String
        payload = @payload
    else
        string_io = StringIO.new
        @payload.write string_io, self
        payload = string_io.string
    end
    length = 20 + payload.length
    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