DIY-pcap 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. data/bin/pcap +2 -62
  2. data/bin/rpcap +2 -63
  3. data/lib/diy/command.rb +80 -0
  4. data/lib/diy/device_finder.rb +1 -1
  5. data/lib/diy/dig.rb +3 -1
  6. data/lib/diy/live.rb +5 -0
  7. data/lib/diy/parser/mu/fixnum_ext.rb +7 -0
  8. data/lib/diy/parser/mu/pcap/ethernet.rb +148 -0
  9. data/lib/diy/parser/mu/pcap/header.rb +75 -0
  10. data/lib/diy/parser/mu/pcap/io_pair.rb +67 -0
  11. data/lib/diy/parser/mu/pcap/io_wrapper.rb +76 -0
  12. data/lib/diy/parser/mu/pcap/ip.rb +61 -0
  13. data/lib/diy/parser/mu/pcap/ipv4.rb +257 -0
  14. data/lib/diy/parser/mu/pcap/ipv6.rb +148 -0
  15. data/lib/diy/parser/mu/pcap/packet.rb +104 -0
  16. data/lib/diy/parser/mu/pcap/pkthdr.rb +155 -0
  17. data/lib/diy/parser/mu/pcap/reader.rb +61 -0
  18. data/lib/diy/parser/mu/pcap/reader/http_family.rb +170 -0
  19. data/lib/diy/parser/mu/pcap/sctp.rb +367 -0
  20. data/lib/diy/parser/mu/pcap/sctp/chunk.rb +123 -0
  21. data/lib/diy/parser/mu/pcap/sctp/chunk/data.rb +134 -0
  22. data/lib/diy/parser/mu/pcap/sctp/chunk/init.rb +100 -0
  23. data/lib/diy/parser/mu/pcap/sctp/chunk/init_ack.rb +68 -0
  24. data/lib/diy/parser/mu/pcap/sctp/parameter.rb +110 -0
  25. data/lib/diy/parser/mu/pcap/sctp/parameter/ip_address.rb +48 -0
  26. data/lib/diy/parser/mu/pcap/stream_packetizer.rb +72 -0
  27. data/lib/diy/parser/mu/pcap/tcp.rb +505 -0
  28. data/lib/diy/parser/mu/pcap/udp.rb +69 -0
  29. data/lib/diy/parser/mu/scenario/pcap.rb +172 -0
  30. data/lib/diy/parser/mu/scenario/pcap/fields.rb +50 -0
  31. data/lib/diy/parser/mu/scenario/pcap/rtp.rb +71 -0
  32. data/lib/diy/parser/pcap.rb +113 -0
  33. data/lib/diy/parser/readme.md +72 -0
  34. data/lib/diy/utils.rb +9 -1
  35. data/lib/diy/version.rb +1 -1
  36. data/lib/diy/worker.rb +3 -2
  37. data/lib/diy/worker_keeper.rb +6 -0
  38. data/spec/helper/tcp.dat +0 -0
  39. data/spec/live_spec.rb +9 -0
  40. data/spec/mu_parser_spec.rb +12 -0
  41. data/spec/utils_spec.rb +1 -1
  42. metadata +34 -3
@@ -0,0 +1,69 @@
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ module Mu
6
+ class Pcap
7
+
8
+ class UDP < Packet
9
+ attr_accessor :src_port, :dst_port
10
+
11
+ def initialize src_port=0, dst_port=0
12
+ super()
13
+ @src_port = src_port
14
+ @dst_port = dst_port
15
+ end
16
+
17
+ def flow_id
18
+ return [:udp, @src_port, @dst_port]
19
+ end
20
+
21
+ FMT_nnnn = 'nnnn'
22
+ def self.from_bytes bytes
23
+ bytes_length = bytes.length
24
+ bytes_length >= 8 or
25
+ raise ParseError, "Truncated UDP header: expected 8 bytes, got #{bytes_length} bytes"
26
+ sport, dport, length, checksum = bytes.unpack(FMT_nnnn)
27
+ bytes_length >= length or
28
+ raise ParseError, "Truncated UDP packet: expected #{length} bytes, got #{bytes_length} bytes"
29
+ udp = UDP.new sport, dport
30
+ udp.payload_raw = bytes[8..-1]
31
+ udp.payload = bytes[8..length]
32
+ return udp
33
+ end
34
+
35
+ def write io, ip
36
+ length = @payload.length
37
+ length_8 = length + 8
38
+ if length_8 > 65535
39
+ Pcap.warning "UDP payload is too large"
40
+ end
41
+ pseudo_header = ip.pseudo_header length_8
42
+ header = [@src_port, @dst_port, length_8, 0] \
43
+ .pack FMT_nnnn
44
+ checksum = IP.checksum(pseudo_header + header + @payload)
45
+ header = [@src_port, @dst_port, length_8, checksum] \
46
+ .pack FMT_nnnn
47
+ io.write header
48
+ io.write @payload
49
+ end
50
+
51
+ def self.udp? packet
52
+ return packet.is_a?(Ethernet) &&
53
+ packet.payload.is_a?(IP) &&
54
+ packet.payload.payload.is_a?(UDP)
55
+ end
56
+
57
+ def to_s
58
+ return "udp(%d, %d, %s)" % [@src_port, @dst_port, @payload.inspect]
59
+ end
60
+
61
+ def == other
62
+ return super &&
63
+ self.src_port == other.src_port &&
64
+ self.dst_port == other.dst_port
65
+ end
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,172 @@
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ require 'tempfile'
6
+ require 'fileutils'
7
+ require 'mu/scenario/pcap/fields'
8
+ require 'mu/pcap'
9
+ require 'json'
10
+
11
+ module Mu
12
+ class Scenario
13
+
14
+ module Pcap
15
+ TSHARK_READ_TIMEOUT = 10.0 # seconds
16
+ TSHARK_LINES_PER_PACKET = 16384
17
+ TSHARK_OPTS = "-n -o tcp.desegment_tcp_streams:false"
18
+ TSHARK_OPTS_SUFFIX = TSHARK_OPTS
19
+ TSHARK_SIZE_OPTS = "-n -o 'column.format: cum_size, \"%B\"'"
20
+ TSHARK_PSML_OPTS = %Q{#{TSHARK_OPTS} -o 'column.format: "Protocol", "%p", "Info", "%i"'}
21
+
22
+ MAX_PCAP_SIZE = 102400 # 100KB
23
+ MAX_RAW_PCAP_SIZE_MB = 25
24
+ MAX_RAW_PCAP_SIZE = MAX_RAW_PCAP_SIZE_MB * 1024 * 1000
25
+ EXCLUDE_FROM_SIZE_CHECK = ['rtp'].freeze
26
+
27
+ class PcapTooLarge < StandardError; end
28
+
29
+ def self.reset_options options
30
+ return unless options
31
+ tshark_opts = options << ' ' << TSHARK_OPTS_SUFFIX
32
+ remove_const(:TSHARK_OPTS) if const_defined?(:TSHARK_OPTS)
33
+ const_set(:TSHARK_OPTS, tshark_opts)
34
+ end
35
+
36
+ def self.validate_pcap_size(path)
37
+ tshark_filter = EXCLUDE_FROM_SIZE_CHECK.map{ |proto| "not #{proto}" }.join " and "
38
+ io = ::IO.popen "tshark #{TSHARK_SIZE_OPTS} -r #{path} -R '#{tshark_filter}' | tail -1"
39
+ if ::IO.select [ io ], nil, nil, TSHARK_READ_TIMEOUT
40
+ if io.eof?
41
+ size = 0
42
+ else
43
+ last_line = io.readline
44
+ size = last_line.to_i
45
+ end
46
+ end
47
+
48
+ if size.nil? or size == 0
49
+ size = File.size(path)
50
+ end
51
+
52
+ if size > MAX_PCAP_SIZE
53
+ raise PcapTooLarge, "Selected packets have a size of #{size} bytes which " +
54
+ "exceeds the #{MAX_PCAP_SIZE} byte maximum."
55
+ end
56
+
57
+ if size > MAX_RAW_PCAP_SIZE
58
+ raise PcapTooLarge, "Selected packets have a raw size of #{size} bytes which " +
59
+ "exceeds the #{MAX_RAW_PCAP_SIZE_MB}MB maximum."
60
+ end
61
+
62
+ return size
63
+ end
64
+
65
+ PAR_VERSION = 1
66
+ def self.export_to_par pcap_path, opts=nil
67
+ opts ||= {}
68
+
69
+ # Open pcap file
70
+ File.exist?(pcap_path) or raise "Cannot open file '#{pcap_path}'."
71
+ validate_pcap_size pcap_path
72
+ pcap = open pcap_path, 'rb'
73
+
74
+ # Get Mu::Pcap::Packets
75
+ packets = to_pcap_packets pcap, opts[:isolate_l7]
76
+
77
+ # Write normalized packets to tempfile
78
+ tmpdir = Dir.mktmpdir
79
+ norm_pcap = File.open "#{tmpdir}/normalized.pcap", 'wb'
80
+ pcap = Mu::Pcap.from_packets packets
81
+ pcap.write norm_pcap
82
+ norm_pcap.close
83
+
84
+ # Get wireshark dissected field values for all packets.
85
+ `tshark -T fields #{TSHARK_OPTS} #{Fields::TSHARK_OPTS} -Eseparator='\xff' -r #{norm_pcap.path} > #{tmpdir}/fields`
86
+ fields = open "#{tmpdir}/fields", 'rb'
87
+
88
+ # Get wireshark dissected field values for all packets.
89
+ fields_array = []
90
+ if fields
91
+ packets.each do |packet|
92
+ fields_array << Fields.next_from_io(fields)
93
+ end
94
+ end
95
+
96
+ # Protocol specific preprocessing, packets may be deleted.
97
+ Rtp.preprocess packets, fields_array
98
+
99
+ File.open "#{tmpdir}/packets.dump", 'wb' do |f|
100
+ Marshal.dump packets, f
101
+ end
102
+
103
+ # Create a second pcap with packets removed.
104
+ norm_pcap = File.open "#{tmpdir}/normalized.pcap", 'wb'
105
+ pcap = Mu::Pcap.from_packets packets
106
+ pcap.write norm_pcap
107
+ norm_pcap.close
108
+
109
+ # Dump PSML to file.
110
+ # (The no-op filter sometimes produces slighty more verbose descriptions.)
111
+ `tshark -T psml #{TSHARK_PSML_OPTS} -r #{norm_pcap.path} -R 'rtp or not rtp' > #{tmpdir}/psml`
112
+
113
+ # Dump PDML io file.
114
+ `tshark -T pdml #{TSHARK_OPTS} -r #{norm_pcap.path} > #{tmpdir}/pdml`
115
+ pdml = open "#{tmpdir}/pdml", 'rb'
116
+
117
+ # Create about
118
+ open "#{tmpdir}/about", 'w' do |about|
119
+ about.puts({:par_version => PAR_VERSION}.to_json)
120
+ end
121
+
122
+ # Create zip
123
+ Dir.chdir tmpdir do
124
+ system "zip -q dissected.zip about pdml psml fields packets.dump normalized.pcap"
125
+ return open("dissected.zip")
126
+ end
127
+ ensure
128
+ if tmpdir
129
+ FileUtils.rm_rf tmpdir
130
+ end
131
+ end
132
+
133
+ def self.to_pcap_packets io, isolate_l7=true
134
+ packets = []
135
+
136
+ # Read Pcap packets from Pcap
137
+ Mu::Pcap.each_pkthdr io do |pkthdr|
138
+ if pkthdr.len != pkthdr.caplen
139
+ raise Mu::Pcap::ParseError, "Error: Capture contains truncated packets. " +
140
+ "Try recapturing with an increased snapshot length."
141
+ end
142
+ if not pkthdr.pkt.is_a? Mu::Pcap::Ethernet
143
+ warning 'Unable to parse packet, skipping.'
144
+ end
145
+ packets << pkthdr.pkt
146
+ end
147
+
148
+ if (packets.length == 0)
149
+ raise Mu::Pcap::ParseError, "No valid packets found!"
150
+ end
151
+
152
+ packets = Mu::Pcap::IPv4.reassemble packets
153
+
154
+ if isolate_l7
155
+ packets = Mu::Pcap::Packet.isolate_l7 packets
156
+ end
157
+
158
+ packets = Mu::Pcap::Packet.normalize packets
159
+ packets = Mu::Pcap::TCP.split packets
160
+
161
+ packets
162
+ end
163
+
164
+ def self.warning msg
165
+ $stderr.puts "WARNING: #{msg}"#, caller, $!
166
+ end
167
+ end
168
+
169
+ end
170
+ end
171
+
172
+ require 'mu/scenario/pcap/rtp'
@@ -0,0 +1,50 @@
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ require 'mu/scenario/pcap'
6
+
7
+ module Mu
8
+ class Scenario
9
+ module Pcap
10
+
11
+ class Fields
12
+ FIELDS = [
13
+ :rtp,
14
+ :"rtp.setup-frame"
15
+ ].freeze
16
+ FIELD_COUNT = FIELDS.length
17
+ SEPARATOR = "\xff".freeze
18
+ TSHARK_OPTS = "-Eseparator='#{SEPARATOR}'"
19
+ FIELDS.each do |field|
20
+ TSHARK_OPTS << " -e #{field}"
21
+ end
22
+ TSHARK_OPTS.freeze
23
+
24
+ def self.readline io
25
+ if ::IO.select [ io ], nil, nil, Pcap::TSHARK_READ_TIMEOUT
26
+ return io.readline.chomp
27
+ end
28
+
29
+ raise Errno::ETIMEDOUT, "read timed out"
30
+ end
31
+
32
+ def self.next_from_io io
33
+ if line = readline(io)
34
+ fields = line.split SEPARATOR, FIELD_COUNT
35
+ hash = {}
36
+ FIELDS.each do |key|
37
+ val = fields.shift
38
+ hash[key] = val.empty? ? nil : val
39
+ end
40
+ return hash
41
+ end
42
+ rescue Exception => e
43
+ Pcap.warning e.message
44
+ return nil
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,71 @@
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ module Mu
6
+ class Scenario
7
+ module Pcap
8
+ module Rtp
9
+
10
+ TRUNC_COUNT = 5
11
+
12
+ def self.preprocess packets, fields_per_packet
13
+ signaled_by = []
14
+ prev_signal_frame = {}
15
+ packets.each_with_index do |packet, idx|
16
+ fields = fields_per_packet[idx]
17
+ if fields and fields[:rtp]
18
+ flow_id = packet.flow_id
19
+ if frame = fields[:"rtp.setup-frame"]
20
+ prev_signal_frame[flow_id] = frame
21
+ else
22
+ if frame = prev_signal_frame[flow_id]
23
+ fields[:"rtp.setup-frame"] = frame
24
+ else
25
+ packets[idx] = nil
26
+ fields_per_packet[idx] = nil
27
+ next
28
+ end
29
+ end
30
+ sig_idx = frame.to_i
31
+ signaled_by[idx] = sig_idx
32
+ end
33
+ end
34
+
35
+ flow_to_count = Hash.new 0
36
+ prev_setup_frame = {}
37
+ keep_frames = []
38
+ packets.each_with_index do |packet, idx|
39
+ if setup_frame = signaled_by[idx]
40
+ flow = packet.flow_id
41
+ count = flow_to_count[flow]
42
+ if setup_frame != prev_setup_frame[flow]
43
+ prev_setup_frame[flow] = setup_frame
44
+ count = 1
45
+ else
46
+ count += 1
47
+ end
48
+ if count <= TRUNC_COUNT
49
+ keep_frames << idx + 1
50
+ else
51
+ packets[idx] = nil
52
+ fields_per_packet[idx] = nil
53
+ end
54
+ flow_to_count[flow] = count
55
+ end
56
+ end
57
+
58
+ packets.reject! {|p| not p }
59
+ fields_per_packet.reject! {|p| not p }
60
+
61
+ filter = "not rtp"
62
+ keep_frames.each do |frame|
63
+ filter << " or frame.number == #{frame}"
64
+ end
65
+
66
+ return filter
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,113 @@
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ $LOAD_PATH.unshift File.dirname(__FILE__)
6
+
7
+ require 'socket'
8
+ require 'stringio'
9
+ require 'mu/fixnum_ext'
10
+
11
+ module Mu
12
+
13
+ class Pcap
14
+ class ParseError < StandardError ; end
15
+
16
+ LITTLE_ENDIAN = 0xd4c3b2a1
17
+ BIG_ENDIAN = 0xa1b2c3d4
18
+
19
+ DLT_NULL = 0
20
+ DLT_EN10MB = 1
21
+ DLT_RAW = 12 # DLT_LOOP in OpenBSD
22
+ DLT_LINUX_SLL = 113
23
+
24
+ attr_accessor :header, :pkthdrs
25
+
26
+ def initialize
27
+ @header = Header.new
28
+ @pkthdrs = []
29
+ end
30
+
31
+ # Read PCAP file from IO and return Mu::Pcap. If decode is true, also
32
+ # decode the Pkthdr packet contents to Mu::Pcap objects.
33
+ def self.read io, decode=true
34
+ pcap = Pcap.new
35
+ pcap.header = each_pkthdr(io, decode) do |pkthdr|
36
+ pcap.pkthdrs << pkthdr
37
+ end
38
+ return pcap
39
+ end
40
+
41
+ # Create PCAP from list of packets.
42
+ def self.from_packets packets
43
+ pcap = Pcap.new
44
+ packets.each do |packet|
45
+ pkthdr = Mu::Pcap::Pkthdr.new
46
+ pkthdr.pkt = packet
47
+ pcap.pkthdrs << pkthdr
48
+ end
49
+ return pcap
50
+ end
51
+
52
+ # Write PCAP file to IO. Uses big-endian and linktype EN10MB.
53
+ def write io
54
+ @header.write io
55
+ @pkthdrs.each do |pkthdr|
56
+ pkthdr.write io
57
+ end
58
+ end
59
+
60
+ # Read PCAP packet headers from IO and return Mu::Pcap::Header. If decode
61
+ # is true, also decode the Pkthdr packet contents to Mu::Pcap objects. Use
62
+ # this for large files when each packet header can processed independently
63
+ # - it will perform better.
64
+ def self.each_pkthdr io, decode=true
65
+ header = Header.read io
66
+ while not io.eof?
67
+ pkthdr = Pkthdr.read io, header.magic
68
+ if decode
69
+ pkthdr.decode! header.magic, header.linktype
70
+ end
71
+ yield pkthdr
72
+ end
73
+ return header
74
+ end
75
+
76
+ # Read packets from PCAP
77
+ def self.read_packets io, decode=true
78
+ packets = []
79
+ each_pkthdr(io) { |pkthdr| packets << pkthdr.pkt }
80
+ return packets
81
+ end
82
+
83
+ # Assertion used during Pcap parsing
84
+ def self.assert cond, msg
85
+ if not cond
86
+ raise ParseError, msg
87
+ end
88
+ end
89
+
90
+ # Warnings from Pcap parsing are printed using this method.
91
+ def self.warning msg
92
+ $stderr.puts "WARNING: #{msg}"
93
+ end
94
+
95
+ def == other
96
+ return self.class == other.class &&
97
+ self.header == other.header &&
98
+ self.pkthdrs == other.pkthdrs
99
+ end
100
+ end
101
+
102
+ end
103
+
104
+ require 'mu/pcap/header'
105
+ require 'mu/pcap/pkthdr'
106
+ require 'mu/pcap/packet'
107
+ require 'mu/pcap/ethernet'
108
+ require 'mu/pcap/ip'
109
+ require 'mu/pcap/ipv4'
110
+ require 'mu/pcap/ipv6'
111
+ require 'mu/pcap/tcp'
112
+ require 'mu/pcap/udp'
113
+ require 'mu/pcap/sctp'