DIY-pcap 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
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'