pcapr-local 0.1.10
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.
- data/.document +5 -0
- data/LICENSE.txt +20 -0
- data/README.md +64 -0
- data/Rakefile +57 -0
- data/VERSION +1 -0
- data/bin/pcap2par +49 -0
- data/bin/startpcapr +40 -0
- data/bin/stoppcapr +33 -0
- data/bin/xtractr +5 -0
- data/lib/environment.rb +106 -0
- data/lib/exe/xtractr +0 -0
- data/lib/mu/pcap.rb +110 -0
- data/lib/mu/pcap/ethernet.rb +148 -0
- data/lib/mu/pcap/header.rb +75 -0
- data/lib/mu/pcap/io_pair.rb +67 -0
- data/lib/mu/pcap/io_wrapper.rb +76 -0
- data/lib/mu/pcap/ip.rb +61 -0
- data/lib/mu/pcap/ipv4.rb +257 -0
- data/lib/mu/pcap/ipv6.rb +148 -0
- data/lib/mu/pcap/packet.rb +104 -0
- data/lib/mu/pcap/pkthdr.rb +155 -0
- data/lib/mu/pcap/reader.rb +61 -0
- data/lib/mu/pcap/reader/http_family.rb +170 -0
- data/lib/mu/pcap/sctp.rb +367 -0
- data/lib/mu/pcap/sctp/chunk.rb +123 -0
- data/lib/mu/pcap/sctp/chunk/data.rb +134 -0
- data/lib/mu/pcap/sctp/chunk/init.rb +100 -0
- data/lib/mu/pcap/sctp/chunk/init_ack.rb +68 -0
- data/lib/mu/pcap/sctp/parameter.rb +110 -0
- data/lib/mu/pcap/sctp/parameter/ip_address.rb +48 -0
- data/lib/mu/pcap/stream_packetizer.rb +72 -0
- data/lib/mu/pcap/tcp.rb +505 -0
- data/lib/mu/pcap/udp.rb +69 -0
- data/lib/mu/scenario/pcap.rb +164 -0
- data/lib/mu/scenario/pcap/fields.rb +50 -0
- data/lib/mu/scenario/pcap/rtp.rb +71 -0
- data/lib/pcapr_local.rb +159 -0
- data/lib/pcapr_local/config.rb +336 -0
- data/lib/pcapr_local/db.rb +197 -0
- data/lib/pcapr_local/scanner.rb +250 -0
- data/lib/pcapr_local/server.rb +178 -0
- data/lib/pcapr_local/www/favicon.ico +0 -0
- data/lib/pcapr_local/www/favicon.png +0 -0
- data/lib/pcapr_local/www/home/index.html +138 -0
- data/lib/pcapr_local/www/static/image/16x16/Cancel.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Cancel.png.1 +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Download.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Folder3.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Full Size.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Minus.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Plus.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Search.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/User.png +0 -0
- data/lib/pcapr_local/www/static/image/48x48/Phone.png +0 -0
- data/lib/pcapr_local/www/static/image/48x48/Video.png +0 -0
- data/lib/pcapr_local/www/static/image/bar-orange.gif +0 -0
- data/lib/pcapr_local/www/static/image/beta.png +0 -0
- data/lib/pcapr_local/www/static/image/bg.png +0 -0
- data/lib/pcapr_local/www/static/image/blockquote.png +0 -0
- data/lib/pcapr_local/www/static/image/body-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl1-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl1-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl1-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl2-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl2-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl2-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl3-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl3-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl3-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl4-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl4-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl4-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl5-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl6-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl7-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl8-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/bottom-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/bottom-l.png +0 -0
- data/lib/pcapr_local/www/static/image/bottom-r.png +0 -0
- data/lib/pcapr_local/www/static/image/btn-search.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-1.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-2.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-3.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-4.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-5.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-6.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-7.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-hl1.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-hl2.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-hl3.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-hl4.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-pathway.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-section1.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-section2.png +0 -0
- data/lib/pcapr_local/www/static/image/collapsed.gif +0 -0
- data/lib/pcapr_local/www/static/image/crosslink.png +0 -0
- data/lib/pcapr_local/www/static/image/expanded.gif +0 -0
- data/lib/pcapr_local/www/static/image/favicon.ico +0 -0
- data/lib/pcapr_local/www/static/image/favicon.png +0 -0
- data/lib/pcapr_local/www/static/image/icon-author.png +0 -0
- data/lib/pcapr_local/www/static/image/icon-created.png +0 -0
- data/lib/pcapr_local/www/static/image/p-expand.gif +0 -0
- data/lib/pcapr_local/www/static/image/pcapr-logo.png +0 -0
- data/lib/pcapr_local/www/static/image/powered-by.png +0 -0
- data/lib/pcapr_local/www/static/image/section1-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/section1-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/section1-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/section2-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/section2-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/section2-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/status-alert.png +0 -0
- data/lib/pcapr_local/www/static/image/status-download.png +0 -0
- data/lib/pcapr_local/www/static/image/status-info.png +0 -0
- data/lib/pcapr_local/www/static/image/status-note.png +0 -0
- data/lib/pcapr_local/www/static/image/tab-round.png +0 -0
- data/lib/pcapr_local/www/static/image/throbber.gif +0 -0
- data/lib/pcapr_local/www/static/image/user.jpg +0 -0
- data/lib/pcapr_local/www/static/script/closet/async.js +421 -0
- data/lib/pcapr_local/www/static/script/closet/closet.api.js +241 -0
- data/lib/pcapr_local/www/static/script/closet/closet.folders.js +94 -0
- data/lib/pcapr_local/www/static/script/closet/closet.js +187 -0
- data/lib/pcapr_local/www/static/script/closet/closet.mr.js +219 -0
- data/lib/pcapr_local/www/static/script/closet/closet.options.js +359 -0
- data/lib/pcapr_local/www/static/script/closet/closet.quantity.js +73 -0
- data/lib/pcapr_local/www/static/script/closet/closet.render.js +205 -0
- data/lib/pcapr_local/www/static/script/closet/closet.report.js +86 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.http.js +135 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.overview.js +163 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.sip.js +159 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.tcp.js +72 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.visualize.js +263 -0
- data/lib/pcapr_local/www/static/script/closet/closet.util.js +40 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery-1.4.2.min.js +154 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery-ui.js +10921 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.flot.js +2123 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.flot.selection.js +184 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.flot.stack.js +184 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.form.js +643 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.jsonp.min.js +3 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.menu.js +142 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.suggest.js +308 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.ui.core.js +203 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.ui.slider.js +629 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.ui.sortable.js +1055 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.ui.widget.js +236 -0
- data/lib/pcapr_local/www/static/script/json2.js +481 -0
- data/lib/pcapr_local/www/static/script/sammy/plugins/sammy.cache.js +115 -0
- data/lib/pcapr_local/www/static/script/sammy/plugins/sammy.template.js +117 -0
- data/lib/pcapr_local/www/static/script/sammy/sammy.js +1696 -0
- data/lib/pcapr_local/www/static/script/tipsy/jquery.tipsy.js +104 -0
- data/lib/pcapr_local/www/static/style/c3p0.css +116 -0
- data/lib/pcapr_local/www/static/style/jquery.suggest.css +27 -0
- data/lib/pcapr_local/www/static/style/page.css +1113 -0
- data/lib/pcapr_local/www/static/style/tipsy.css +7 -0
- data/lib/pcapr_local/www/templates/browse.services.template +10 -0
- data/lib/pcapr_local/www/templates/browse.template +77 -0
- data/lib/pcapr_local/www/templates/flows.template +38 -0
- data/lib/pcapr_local/www/templates/pcap.template +63 -0
- data/lib/pcapr_local/www/templates/sip.calls.template +35 -0
- data/lib/pcapr_local/www/templates/statistics.template +6 -0
- data/lib/pcapr_local/xtractr.rb +179 -0
- data/lib/pcapr_local/xtractr/instance.rb +172 -0
- data/pcapr-local.gemspec +297 -0
- data/test/mu/pcap/reader/tc_http_family.rb +251 -0
- data/test/mu/pcap/tc_ethernet.rb +71 -0
- data/test/mu/pcap/tc_header.rb +56 -0
- data/test/mu/pcap/tc_ipv4.rb +103 -0
- data/test/mu/pcap/tc_ipv6.rb +83 -0
- data/test/mu/pcap/tc_packet.rb +44 -0
- data/test/mu/pcap/tc_pair.rb +58 -0
- data/test/mu/pcap/tc_pkthdr.rb +33 -0
- data/test/mu/pcap/tc_reader.rb +76 -0
- data/test/mu/pcap/tc_tcp.rb +426 -0
- data/test/mu/pcap/tc_udp.rb +33 -0
- data/test/mu/pcap/tc_wrapper.rb +80 -0
- data/test/mu/scenario/pcap/tc_fields.rb +67 -0
- data/test/mu/scenario/pcap/tc_rtp.rb +135 -0
- data/test/mu/scenario/sip_signalled_call_1.pcap +0 -0
- data/test/mu/scenario/tc_pcap.rb +190 -0
- data/test/mu/scenario/test_data/arp.pcap +0 -0
- data/test/mu/scenario/test_data/dns.pcap +0 -0
- data/test/mu/scenario/test_data/http-v6.pcap +0 -0
- data/test/mu/scenario/test_data/http.pcap +0 -0
- data/test/mu/scenario/test_data/http_chunked.pcap +0 -0
- data/test/mu/scenario/test_data/http_deflate.pcap +0 -0
- data/test/mu/scenario/test_data/httpauth3.pcap +0 -0
- data/test/mu/scenario/test_data/icmp.pcap +0 -0
- data/test/mu/scenario/test_data/sip_signalled_call_1.pcap +0 -0
- data/test/mu/tc_pcap.rb +39 -0
- data/test/mu/testcase.rb +86 -0
- data/test/pcapr_local/arp.pcap +0 -0
- data/test/pcapr_local/data.js +3 -0
- data/test/pcapr_local/http_chunked.pcap +0 -0
- data/test/pcapr_local/tc_api.rb +181 -0
- data/test/pcapr_local/test.tgz +0 -0
- data/test/pcapr_local/test_scanner.rb +241 -0
- data/test/pcapr_local/test_xtractr.rb +219 -0
- data/test/pcapr_local/testcase.rb +107 -0
- data/test/test_export_to_scenario.sh +25 -0
- data/test/test_pcapr_local.rb +29 -0
- metadata +450 -0
data/lib/mu/pcap/udp.rb
ADDED
|
@@ -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,164 @@
|
|
|
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_SIZE_OPTS = "-n -o 'column.format: cum_size, \"%B\"'"
|
|
19
|
+
TSHARK_PSML_OPTS = %Q{#{TSHARK_OPTS} -o 'column.format: "Protocol", "%p", "Info", "%i"'}
|
|
20
|
+
|
|
21
|
+
MAX_PCAP_SIZE = 102400 # 100KB
|
|
22
|
+
MAX_RAW_PCAP_SIZE_MB = 25
|
|
23
|
+
MAX_RAW_PCAP_SIZE = MAX_RAW_PCAP_SIZE_MB * 1024 * 1000
|
|
24
|
+
EXCLUDE_FROM_SIZE_CHECK = ['rtp'].freeze
|
|
25
|
+
|
|
26
|
+
class PcapTooLarge < StandardError; end
|
|
27
|
+
|
|
28
|
+
def self.validate_pcap_size(path)
|
|
29
|
+
tshark_filter = EXCLUDE_FROM_SIZE_CHECK.map{ |proto| "not #{proto}" }.join " and "
|
|
30
|
+
io = ::IO.popen "tshark #{TSHARK_SIZE_OPTS} -r #{path} -R '#{tshark_filter}' | tail -1"
|
|
31
|
+
if ::IO.select [ io ], nil, nil, TSHARK_READ_TIMEOUT
|
|
32
|
+
if io.eof?
|
|
33
|
+
size = 0
|
|
34
|
+
else
|
|
35
|
+
last_line = io.readline
|
|
36
|
+
size = last_line.to_i
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if size.nil? or size == 0
|
|
41
|
+
size = File.size(path)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if size > MAX_PCAP_SIZE
|
|
45
|
+
raise PcapTooLarge, "Selected packets have a size of #{size} bytes which " +
|
|
46
|
+
"exceeds the #{MAX_PCAP_SIZE} byte maximum."
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if size > MAX_RAW_PCAP_SIZE
|
|
50
|
+
raise PcapTooLarge, "Selected packets have a raw size of #{size} bytes which " +
|
|
51
|
+
"exceeds the #{MAX_RAW_PCAP_SIZE_MB}MB maximum."
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
return size
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
PAR_VERSION = 1
|
|
58
|
+
def self.export_to_par pcap_path, opts=nil
|
|
59
|
+
opts ||= {}
|
|
60
|
+
|
|
61
|
+
# Open pcap file
|
|
62
|
+
File.exist?(pcap_path) or raise "Cannot open file '#{pcap_path}'."
|
|
63
|
+
validate_pcap_size pcap_path
|
|
64
|
+
pcap = open pcap_path, 'rb'
|
|
65
|
+
|
|
66
|
+
# Get Mu::Pcap::Packets
|
|
67
|
+
packets = to_pcap_packets pcap, opts[:isolate_l7]
|
|
68
|
+
|
|
69
|
+
# Write normalized packets to tempfile
|
|
70
|
+
tmpdir = Dir.mktmpdir
|
|
71
|
+
norm_pcap = File.open "#{tmpdir}/normalized.pcap", 'wb'
|
|
72
|
+
pcap = Mu::Pcap.from_packets packets
|
|
73
|
+
pcap.write norm_pcap
|
|
74
|
+
norm_pcap.close
|
|
75
|
+
|
|
76
|
+
# Get wireshark dissected field values for all packets.
|
|
77
|
+
`tshark -T fields #{TSHARK_OPTS} #{Fields::TSHARK_OPTS} -Eseparator='\xff' -r #{norm_pcap.path} > #{tmpdir}/fields`
|
|
78
|
+
fields = open "#{tmpdir}/fields", 'rb'
|
|
79
|
+
|
|
80
|
+
# Get wireshark dissected field values for all packets.
|
|
81
|
+
fields_array = []
|
|
82
|
+
if fields
|
|
83
|
+
packets.each do |packet|
|
|
84
|
+
fields_array << Fields.next_from_io(fields)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Protocol specific preprocessing, packets may be deleted.
|
|
89
|
+
Rtp.preprocess packets, fields_array
|
|
90
|
+
|
|
91
|
+
File.open "#{tmpdir}/packets.dump", 'wb' do |f|
|
|
92
|
+
Marshal.dump packets, f
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Create a second pcap with packets removed.
|
|
96
|
+
norm_pcap = File.open "#{tmpdir}/normalized.pcap", 'wb'
|
|
97
|
+
pcap = Mu::Pcap.from_packets packets
|
|
98
|
+
pcap.write norm_pcap
|
|
99
|
+
norm_pcap.close
|
|
100
|
+
|
|
101
|
+
# Dump PSML to file.
|
|
102
|
+
# (The no-op filter sometimes produces slighty more verbose descriptions.)
|
|
103
|
+
`tshark -T psml #{TSHARK_PSML_OPTS} -r #{norm_pcap.path} -R 'rtp or not rtp' > #{tmpdir}/psml`
|
|
104
|
+
|
|
105
|
+
# Dump PDML io file.
|
|
106
|
+
`tshark -T pdml #{TSHARK_OPTS} -r #{norm_pcap.path} > #{tmpdir}/pdml`
|
|
107
|
+
pdml = open "#{tmpdir}/pdml", 'rb'
|
|
108
|
+
|
|
109
|
+
# Create about
|
|
110
|
+
open "#{tmpdir}/about", 'w' do |about|
|
|
111
|
+
about.puts({:par_version => PAR_VERSION}.to_json)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Create zip
|
|
115
|
+
Dir.chdir tmpdir do
|
|
116
|
+
system "zip -q dissected.zip about pdml psml fields packets.dump normalized.pcap"
|
|
117
|
+
return open("dissected.zip")
|
|
118
|
+
end
|
|
119
|
+
ensure
|
|
120
|
+
if tmpdir
|
|
121
|
+
FileUtils.rm_rf tmpdir
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def self.to_pcap_packets io, isolate_l7=true
|
|
126
|
+
packets = []
|
|
127
|
+
|
|
128
|
+
# Read Pcap packets from Pcap
|
|
129
|
+
Mu::Pcap.each_pkthdr io do |pkthdr|
|
|
130
|
+
if pkthdr.len != pkthdr.caplen
|
|
131
|
+
raise Mu::Pcap::ParseError, "Error: Capture contains truncated packets. " +
|
|
132
|
+
"Try recapturing with an increased snapshot length."
|
|
133
|
+
end
|
|
134
|
+
if not pkthdr.pkt.is_a? Mu::Pcap::Ethernet
|
|
135
|
+
warning 'Unable to parse packet, skipping.'
|
|
136
|
+
end
|
|
137
|
+
packets << pkthdr.pkt
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
if (packets.length == 0)
|
|
141
|
+
raise Mu::Pcap::ParseError, "No valid packets found!"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
packets = Mu::Pcap::IPv4.reassemble packets
|
|
145
|
+
|
|
146
|
+
if isolate_l7
|
|
147
|
+
packets = Mu::Pcap::Packet.isolate_l7 packets
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
packets = Mu::Pcap::Packet.normalize packets
|
|
151
|
+
packets = Mu::Pcap::TCP.split packets
|
|
152
|
+
|
|
153
|
+
packets
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def self.warning msg
|
|
157
|
+
$stderr.puts "WARNING: #{msg}"#, caller, $!
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
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
|
data/lib/pcapr_local.rb
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# http://www.mudynamics.com
|
|
2
|
+
# http://labs.mudynamics.com
|
|
3
|
+
# http://www.pcapr.net
|
|
4
|
+
|
|
5
|
+
require 'rubygems'
|
|
6
|
+
require 'environment'
|
|
7
|
+
require 'pcapr_local/config'
|
|
8
|
+
require 'pcapr_local/scanner'
|
|
9
|
+
require 'pcapr_local/server'
|
|
10
|
+
require 'pcapr_local/xtractr'
|
|
11
|
+
require 'logger'
|
|
12
|
+
|
|
13
|
+
module PcaprLocal
|
|
14
|
+
START_SCRIPT = File.join(ROOT, 'bin/pcapr_start.rb')
|
|
15
|
+
|
|
16
|
+
# We share a single Logger across all of pcapr.Local.
|
|
17
|
+
Logger = Logger.new(STDOUT)
|
|
18
|
+
|
|
19
|
+
# Recreate logger using configured log location.
|
|
20
|
+
LOGFILE = "server.log"
|
|
21
|
+
def self.start_logging log_dir
|
|
22
|
+
if log_dir and not log_dir.empty?
|
|
23
|
+
if const_defined? :Logger
|
|
24
|
+
remove_const :Logger
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
logfile = File.join(log_dir, LOGFILE)
|
|
28
|
+
FileUtils.mkdir_p log_dir
|
|
29
|
+
const_set :Logger, Logger.new(logfile, 5)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Start xtractr instance manager.
|
|
34
|
+
def self.start_xtractr config
|
|
35
|
+
xtractr_config = config['xtractr'].merge(
|
|
36
|
+
"index_dir" => config.fetch("index_dir"),
|
|
37
|
+
"pcap_dir" => config.fetch("pcap_dir")
|
|
38
|
+
)
|
|
39
|
+
Xtractr.new xtractr_config
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Start file system scanner.
|
|
43
|
+
def self.start_scanner config, db, xtractr
|
|
44
|
+
scanner_config = config.fetch('scanner').merge(
|
|
45
|
+
"index_dir" => config.fetch("index_dir"),
|
|
46
|
+
"pcap_dir" => config.fetch("pcap_dir"),
|
|
47
|
+
"db" => db,
|
|
48
|
+
"xtractr" => xtractr
|
|
49
|
+
)
|
|
50
|
+
PcaprLocal::Scanner.start scanner_config
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Start webserver UI/API
|
|
54
|
+
def self.start_app config, db, scanner, xtractr
|
|
55
|
+
app_config = config.fetch "app"
|
|
56
|
+
root = File.expand_path(File.dirname(__FILE__))
|
|
57
|
+
app_file = File.join(root, "pcapr_local/server.rb")
|
|
58
|
+
PcaprLocal::Server.run! \
|
|
59
|
+
:app_file => app_file,
|
|
60
|
+
:dump_errors => true,
|
|
61
|
+
:logging => true,
|
|
62
|
+
:port => app_config.fetch("port"),
|
|
63
|
+
:bind => app_config.fetch("host"),
|
|
64
|
+
:db => db,
|
|
65
|
+
:scanner => scanner,
|
|
66
|
+
:xtractr => xtractr
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.get_db config
|
|
70
|
+
PcaprLocal::DB.get_db config.fetch("couch")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.start config=nil
|
|
74
|
+
config ||= PcaprLocal::Config.config
|
|
75
|
+
|
|
76
|
+
# Check that server is not already running.
|
|
77
|
+
check_pid_file config['pidfile']
|
|
78
|
+
|
|
79
|
+
# Start logging.
|
|
80
|
+
if config["log_dir"]
|
|
81
|
+
start_logging config['log_dir']
|
|
82
|
+
end
|
|
83
|
+
start_msg = "Starting server at #{config['app']['host']}:#{config['app']['port']}"
|
|
84
|
+
Logger.info start_msg
|
|
85
|
+
puts start_msg
|
|
86
|
+
puts "Log is at #{config['log_dir']}/#{LOGFILE}"
|
|
87
|
+
|
|
88
|
+
# Deamonize
|
|
89
|
+
unless config['debug_mode']
|
|
90
|
+
puts "Moving server process to the background. Run 'stoppcapr' to stop the server."
|
|
91
|
+
Process.daemon
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Create pid file that will be deleted when we shutdown.
|
|
95
|
+
create_pid_file config['pidfile']
|
|
96
|
+
|
|
97
|
+
# Get database instance
|
|
98
|
+
db = get_db config
|
|
99
|
+
|
|
100
|
+
# Xtractr manager
|
|
101
|
+
xtractr = start_xtractr config
|
|
102
|
+
|
|
103
|
+
# Start scanner thread
|
|
104
|
+
scanner = start_scanner config, db, xtractr
|
|
105
|
+
|
|
106
|
+
# Start application server
|
|
107
|
+
start_app config, db, scanner, xtractr
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def self.check_pid_file file
|
|
111
|
+
if File.exist? file
|
|
112
|
+
# If we get Errno::ESRCH then process does not exist and
|
|
113
|
+
# we can safely cleanup the pid file.
|
|
114
|
+
pid = File.read(file).to_i
|
|
115
|
+
begin
|
|
116
|
+
Process.kill(0, pid)
|
|
117
|
+
rescue Errno::ESRCH
|
|
118
|
+
stale_pid = true
|
|
119
|
+
rescue
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
unless stale_pid
|
|
123
|
+
puts "Server is already running (pid=#{pid})"
|
|
124
|
+
exit
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def self.create_pid_file file
|
|
130
|
+
File.open(file, "w") { |f| f.puts Process.pid }
|
|
131
|
+
|
|
132
|
+
# Remove pid file during shutdown
|
|
133
|
+
at_exit do
|
|
134
|
+
Logger.info "Shutting down." rescue nil
|
|
135
|
+
if File.exist? file
|
|
136
|
+
File.unlink file
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Sends SIGTERM to process in pidfile. Server should trap this
|
|
142
|
+
# and shutdown cleanly.
|
|
143
|
+
def self.stop config=nil
|
|
144
|
+
user_config = Config.user_config_path
|
|
145
|
+
if File.exist?(user_config)
|
|
146
|
+
config = PcaprLocal::Config.config user_config
|
|
147
|
+
pid_file = config["pidfile"]
|
|
148
|
+
if pid_file and File.exist? pid_file
|
|
149
|
+
pid = Integer(File.read(pid_file))
|
|
150
|
+
Process.kill -15, -pid
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
if __FILE__ == $0
|
|
157
|
+
PcaprLocal.start
|
|
158
|
+
end
|
|
159
|
+
|