Class: Mu::Pcap::IPv4
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
Instance Attribute Summary collapse
-
#dscp ⇒ Object
Returns the value of attribute dscp.
-
#dst ⇒ Object
Returns the value of attribute dst.
-
#ip_id ⇒ Object
Returns the value of attribute ip_id.
-
#offset ⇒ Object
Returns the value of attribute offset.
-
#proto ⇒ Object
Returns the value of attribute proto.
-
#src ⇒ Object
Returns the value of attribute src.
-
#ttl ⇒ Object
Returns the value of attribute ttl.
Attributes inherited from Packet
Class Method Summary collapse
-
.check_options(options, label = 'IPv4') ⇒ Object
Check that IP or TCP options are valid.
- .from_bytes(bytes) ⇒ Object
-
.reassemble(packets) ⇒ Object
Reassemble fragmented IPv4 packets.
Instance Method Summary collapse
- #==(other) ⇒ Object
- #flow_id ⇒ Object
- #fragment? ⇒ Boolean
-
#initialize(src = nil, dst = nil, ip_id = 0, offset = 0, ttl = 64, proto = 0, dscp = 0) ⇒ IPv4
constructor
A new instance of IPv4.
- #pseudo_header(payload_length) ⇒ Object
- #to_s ⇒ Object
- #v4? ⇒ Boolean
- #write(io) ⇒ Object
Methods inherited from IP
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/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
#dscp ⇒ Object
Returns the value of attribute dscp.
18 19 20 |
# File 'lib/mu/pcap/ipv4.rb', line 18 def dscp @dscp end |
#dst ⇒ Object
Returns the value of attribute dst.
18 19 20 |
# File 'lib/mu/pcap/ipv4.rb', line 18 def dst @dst end |
#ip_id ⇒ Object
Returns the value of attribute ip_id.
18 19 20 |
# File 'lib/mu/pcap/ipv4.rb', line 18 def ip_id @ip_id end |
#offset ⇒ Object
Returns the value of attribute offset.
18 19 20 |
# File 'lib/mu/pcap/ipv4.rb', line 18 def offset @offset end |
#proto ⇒ Object
Returns the value of attribute proto.
18 19 20 |
# File 'lib/mu/pcap/ipv4.rb', line 18 def proto @proto end |
#src ⇒ Object
Returns the value of attribute src.
18 19 20 |
# File 'lib/mu/pcap/ipv4.rb', line 18 def src @src end |
#ttl ⇒ Object
Returns the value of attribute ttl.
18 19 20 |
# File 'lib/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.
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/mu/pcap/ipv4.rb', line 134 def self. , label='IPv4' while not .empty? type = .slice!(0, 1)[0].ord if type == 0 or type == 1 next end Pcap.assert !.empty?, "#{label} option #{type} is missing the length field" length = .slice!(0, 1)[0].ord Pcap.assert length >= 2, "#{label} option #{type} has invalid length: #{length}" Pcap.assert length - 2 <= .length, "#{label} option #{type} has truncated data" .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 |
# File 'lib/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. 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 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
154 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 |
# File 'lib/mu/pcap/ipv4.rb', line 154 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
246 247 248 249 250 251 252 253 |
# File 'lib/mu/pcap/ipv4.rb', line 246 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 ⇒ Object
35 36 37 38 39 40 41 |
# File 'lib/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
127 128 129 |
# File 'lib/mu/pcap/ipv4.rb', line 127 def fragment? return (@offset & (IP_OFFMASK | IP_MF) != 0) end |
#pseudo_header(payload_length) ⇒ Object
121 122 123 124 125 |
# File 'lib/mu/pcap/ipv4.rb', line 121 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 ⇒ Object
237 238 239 240 241 242 243 244 |
# File 'lib/mu/pcap/ipv4.rb', line 237 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
31 32 33 |
# File 'lib/mu/pcap/ipv4.rb', line 31 def v4? return true end |
#write(io) ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/mu/pcap/ipv4.rb', line 97 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 |