dorothy2 0.0.3 → 1.0.0
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/README.md +30 -6
- data/TODO +21 -0
- data/bin/dorothy_start +7 -6
- data/bin/dparser_start +13 -1
- data/etc/ddl/dorothive.ddl +2 -31
- data/lib/doroParser.rb +30 -23
- data/lib/dorothy2/BFM.rb +5 -13
- data/lib/dorothy2/do-utils.rb +4 -5
- data/lib/dorothy2/version.rb +1 -1
- data/lib/dorothy2.rb +5 -8
- data/lib/mu/xtractr/about.rb +57 -0
- data/lib/mu/xtractr/content.rb +68 -0
- data/lib/mu/xtractr/field.rb +178 -0
- data/lib/mu/xtractr/flow.rb +162 -0
- data/lib/mu/xtractr/flows.rb +118 -0
- data/lib/mu/xtractr/host.rb +87 -0
- data/lib/mu/xtractr/packet.rb +138 -0
- data/lib/mu/xtractr/packets.rb +122 -0
- data/lib/mu/xtractr/service.rb +77 -0
- data/lib/mu/xtractr/stream/http.rb +103 -0
- data/lib/mu/xtractr/stream.rb +132 -0
- data/lib/mu/xtractr/term.rb +73 -0
- data/lib/mu/xtractr/test/stream/tc_http.rb +53 -0
- data/lib/mu/xtractr/test/tc_field.rb +140 -0
- data/lib/mu/xtractr/test/tc_flow.rb +79 -0
- data/lib/mu/xtractr/test/tc_flows.rb +94 -0
- data/lib/mu/xtractr/test/tc_host.rb +116 -0
- data/lib/mu/xtractr/test/tc_packet.rb +110 -0
- data/lib/mu/xtractr/test/tc_packets.rb +84 -0
- data/lib/mu/xtractr/test/tc_service.rb +66 -0
- data/lib/mu/xtractr/test/tc_stream.rb +56 -0
- data/lib/mu/xtractr/test/tc_term.rb +59 -0
- data/lib/mu/xtractr/test/tc_views.rb +118 -0
- data/lib/mu/xtractr/test/tc_xtractr.rb +151 -0
- data/lib/mu/xtractr/test/test.rb +19 -0
- data/lib/mu/xtractr/views.rb +204 -0
- data/lib/mu/xtractr.rb +257 -0
- metadata +32 -4
@@ -0,0 +1,122 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
module Mu
|
16
|
+
class Xtractr
|
17
|
+
# = Packets
|
18
|
+
# Packets is an iterator on a collection of packets determined by the search
|
19
|
+
# query. This class can also be used to report on the collection of packets
|
20
|
+
# in addition to slicing those packets into a new pcap.
|
21
|
+
#
|
22
|
+
# xtractr.packets('http.request.method:GET pkt.src:192.168.1.1').save('foo.pcap')
|
23
|
+
#
|
24
|
+
# <b>Find the top URL's in HTTP packets</b>
|
25
|
+
#
|
26
|
+
# xtractr.packets('pkt.service:HTTP').count('http.request.method')
|
27
|
+
#
|
28
|
+
# <b>Find the packet size distribution for DNS packets</b>
|
29
|
+
#
|
30
|
+
# xtractr.packets('pkt.service:DNS').count('pkt.length')
|
31
|
+
class Packets
|
32
|
+
include Enumerable
|
33
|
+
|
34
|
+
attr_reader :xtractr # :nodoc:
|
35
|
+
|
36
|
+
MAX_PAGE_SIZE = 100 # :nodoc:
|
37
|
+
|
38
|
+
def initialize xtractr, opts # :nodoc:
|
39
|
+
@xtractr = xtractr
|
40
|
+
@opts = opts
|
41
|
+
@opts[:q] ||= '*'
|
42
|
+
end
|
43
|
+
|
44
|
+
def q # :nodoc:
|
45
|
+
@opts[:q]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Iterate over each packet that matches the search criteria. It's always
|
49
|
+
# better to use this with a fine-grained query instead of packets.to_a
|
50
|
+
# because it's going to try and load <em>all</em> packets from the index.
|
51
|
+
# xtractr.packets("pkt.src:192.168.1.1").each do |pkt|
|
52
|
+
# ...
|
53
|
+
# end
|
54
|
+
def each_packet() # :yields: packet
|
55
|
+
_opts = @opts.dup
|
56
|
+
_opts[:start] ||= 1
|
57
|
+
_opts[:limit] ||= MAX_PAGE_SIZE
|
58
|
+
|
59
|
+
while true
|
60
|
+
result = xtractr.json "api/packets", _opts
|
61
|
+
rows = result['rows']
|
62
|
+
break if rows.empty?
|
63
|
+
|
64
|
+
rows[0, MAX_PAGE_SIZE-1].each do |row|
|
65
|
+
packet = Packet.new xtractr, row
|
66
|
+
yield packet
|
67
|
+
end
|
68
|
+
|
69
|
+
break if rows.size < MAX_PAGE_SIZE
|
70
|
+
_opts[:start] = rows[MAX_PAGE_SIZE-1]['id']
|
71
|
+
end
|
72
|
+
return self
|
73
|
+
end
|
74
|
+
|
75
|
+
# Fetch the first packet that matched the query. Mostly used for unit
|
76
|
+
# testing.
|
77
|
+
def first
|
78
|
+
result = xtractr.json "api/packets", :start => 1, :limit => 1, :q => q
|
79
|
+
rows = result['rows']
|
80
|
+
rows.empty? ? nil : Packet.new(xtractr, rows[0])
|
81
|
+
end
|
82
|
+
|
83
|
+
# Count the unique values of the specified field amongst all the packets
|
84
|
+
# that matched the query.
|
85
|
+
# xtractr.packets('mozilla').count('http.request.uri')
|
86
|
+
def count field
|
87
|
+
Views.count xtractr, field, '/api/packets/report', @opts
|
88
|
+
end
|
89
|
+
|
90
|
+
# Return a list of Field::Value objects for the specified field, sorted
|
91
|
+
# by their frequency. This is a convenience method used in method chaining.
|
92
|
+
# xtractr.packets('index.html').values('http.request.uri')
|
93
|
+
def values field
|
94
|
+
count(field).map { |c| c.object }
|
95
|
+
end
|
96
|
+
|
97
|
+
# Sum the numeric values of vfield, keyed by the unique values of
|
98
|
+
# kfield.
|
99
|
+
# xtractr.packets('mozilla').sum('http.request.uri', 'pkt.length')
|
100
|
+
def sum kfield, vfield
|
101
|
+
Views.sum xtractr, kfield, vfield, '/api/packets/report', @opts
|
102
|
+
end
|
103
|
+
|
104
|
+
# Stich together a pcap made up of all packets that matched the query
|
105
|
+
# and save it to the filename.
|
106
|
+
# xtractr.packets('pkt.service:DNS pkt.length:>64').save('foo.pcap')
|
107
|
+
def save filename
|
108
|
+
open(filename, "w") do |ios|
|
109
|
+
pcap = xtractr.get "api/packets/slice", :q => @opts[:q]
|
110
|
+
ios.write pcap
|
111
|
+
end
|
112
|
+
return self
|
113
|
+
end
|
114
|
+
|
115
|
+
def inspect # :nodoc:
|
116
|
+
"#<packets:#{@opts[:q]}>"
|
117
|
+
end
|
118
|
+
|
119
|
+
alias_method :each, :each_packet
|
120
|
+
end
|
121
|
+
end # Xtractr
|
122
|
+
end # Mu
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
module Mu
|
16
|
+
class Xtractr
|
17
|
+
# = Service
|
18
|
+
# The Service class represents the Wireshark-assigned protocol that's somewhat
|
19
|
+
# higher level than the IP protocol. You can get a list of all the unique
|
20
|
+
# services in your index through the xtractr's methods:
|
21
|
+
#
|
22
|
+
# xtractr.services(/http/).each { |service| ... }
|
23
|
+
# xtractr.services.each { |service| ... }
|
24
|
+
#
|
25
|
+
# Some services like HTTP have counterparts in both flows and packets, while
|
26
|
+
# others like ARP (all non-IP, layer2 services) are only available in packets.
|
27
|
+
class Service
|
28
|
+
attr_reader :xtractr # :nodoc:
|
29
|
+
|
30
|
+
# Return the name of the service
|
31
|
+
attr_reader :name
|
32
|
+
|
33
|
+
def initialize xtractr, name # :nodoc:
|
34
|
+
@xtractr = xtractr
|
35
|
+
@name = name
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get a unique list of clients for this service
|
39
|
+
# xtractr.service('http').clients
|
40
|
+
def clients q=nil
|
41
|
+
_q = "flow.service:\"#{name}\""
|
42
|
+
_q << " #{q}" if q
|
43
|
+
Flows.new(xtractr, :q => _q).count('flow.src')
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get a unique list of servers for this service
|
47
|
+
# xtractr.service('http').servers
|
48
|
+
def servers q=nil
|
49
|
+
_q = "flow.service:\"#{name}\""
|
50
|
+
_q << " #{q}" if q
|
51
|
+
Flows.new(xtractr, :q => _q).count('flow.dst')
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return an iterator that can yield all packets that have this service and
|
55
|
+
# matches the query
|
56
|
+
# xtractr.service("DNS").packets("mu").each { |pkt| ... }
|
57
|
+
def packets q=nil
|
58
|
+
_q = "pkt.service:\"#{name}\""
|
59
|
+
_q << " #{q}" if q
|
60
|
+
return Packets.new(xtractr, :q => _q)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Return an iterator that can yield all flows that have this service and
|
64
|
+
# matches the query
|
65
|
+
# xtractr.service("DNS").flows("AAAA").each { |flow| ... }
|
66
|
+
def flows q=nil
|
67
|
+
_q = "flow.service:\"#{name}\""
|
68
|
+
_q << " #{q}" if q
|
69
|
+
return Flows.new(xtractr, :q => _q)
|
70
|
+
end
|
71
|
+
|
72
|
+
def inspect # :nodoc:
|
73
|
+
"#<service:#{name}>"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end # Xtractr
|
77
|
+
end # Mu
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
require 'stringio'
|
16
|
+
require 'zlib'
|
17
|
+
|
18
|
+
module Mu
|
19
|
+
class Xtractr
|
20
|
+
class Stream
|
21
|
+
class HTTP < Processor # :nodoc:
|
22
|
+
# Check to see if this is a HTTP stream.
|
23
|
+
def self.matches? stream
|
24
|
+
not stream.messages.select { |message| message.bytes =~ /HTTP\/1\./ }.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Pull out the response body from the messages in the stream and convert
|
28
|
+
# them into contents on the stream.
|
29
|
+
def self.extract stream
|
30
|
+
stream.messages.each { |message| process stream, message }
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.process stream, message
|
34
|
+
content = Content.new message
|
35
|
+
chunked = false
|
36
|
+
length = nil
|
37
|
+
ios = StringIO.new message.bytes
|
38
|
+
while true
|
39
|
+
line = ios.readline.chomp rescue nil
|
40
|
+
break if not line or line.empty?
|
41
|
+
|
42
|
+
case line
|
43
|
+
when /Content-Length:\s*(\d+)/i
|
44
|
+
length = $1.to_i
|
45
|
+
when /Content-Type:\s*(.*)/i
|
46
|
+
content.type = $1
|
47
|
+
when /Content-Encoding:\s*(.*)/i
|
48
|
+
content.encoding = $1
|
49
|
+
when /Transfer-Encoding:\s*chunked/i
|
50
|
+
chunked = true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Read the content
|
55
|
+
bytes = ios.read(length)
|
56
|
+
return if not bytes or bytes.empty?
|
57
|
+
|
58
|
+
# Handle chunked encoding, if necessary
|
59
|
+
bytes = dechunk(bytes) if chunked
|
60
|
+
|
61
|
+
# And then decompress, if necessary
|
62
|
+
if ['gzip','deflate'].member? content.encoding
|
63
|
+
bytes = decompress(bytes, content.encoding)
|
64
|
+
end
|
65
|
+
|
66
|
+
if bytes
|
67
|
+
content.body = bytes
|
68
|
+
stream.contents << content
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.dechunk text
|
73
|
+
ios = StringIO.new text
|
74
|
+
body = ''
|
75
|
+
while true
|
76
|
+
line = ios.readline rescue nil
|
77
|
+
break if not line or line.empty?
|
78
|
+
|
79
|
+
chunksz = line.to_i(16)
|
80
|
+
break if chunksz.zero?
|
81
|
+
|
82
|
+
body << ios.read(chunksz)
|
83
|
+
end
|
84
|
+
return body
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.decompress text, method
|
88
|
+
if method == 'gzip'
|
89
|
+
ios = StringIO.new text
|
90
|
+
reader = Zlib::GzipReader.new ios
|
91
|
+
begin
|
92
|
+
return reader.read
|
93
|
+
ensure
|
94
|
+
reader.close
|
95
|
+
end
|
96
|
+
elsif method == 'deflate'
|
97
|
+
return Zlib::Inflate.inflate(text)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end # Stream
|
102
|
+
end # Xtractr
|
103
|
+
end # Mu
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
module Mu
|
16
|
+
class Xtractr
|
17
|
+
# = Stream
|
18
|
+
# Represents a logical TCP stream made of messages (potentially spanning
|
19
|
+
# multiple packets across multiple pcaps). A given message in the stream is
|
20
|
+
# potentially stitched together from multiple packets. Streams form the basis
|
21
|
+
# of doing content-analysis with xtractr. xtractr does all the work of
|
22
|
+
# reassembling the packets and pulling out the appropriate payload from
|
23
|
+
# each of the packets.
|
24
|
+
#
|
25
|
+
# xtractr.flows('flow.service:http').first.stream.find do |message|
|
26
|
+
# m =~ /xml/
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Each stream also has a set of content processors that are invoked at
|
30
|
+
# creation time which pull out attachments, images, etc from the reassembled
|
31
|
+
# stream.
|
32
|
+
#
|
33
|
+
# xtractr.flows('flow.service:http').first.stream.contents.each do |content|
|
34
|
+
# content.save if content.type == 'image/jpeg'
|
35
|
+
# end
|
36
|
+
class Stream
|
37
|
+
include Enumerable
|
38
|
+
|
39
|
+
# A list of stream processors that can pull out content from messages
|
40
|
+
class Processor # :nodoc:
|
41
|
+
def self.inherited klass
|
42
|
+
@processors ||= [] << klass
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.processors
|
46
|
+
@processors ||= []
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :xtractr # :nodoc:
|
51
|
+
|
52
|
+
# Return the flow that this stream represents.
|
53
|
+
attr_reader :flow
|
54
|
+
|
55
|
+
# Return a list of Messages in this stream.
|
56
|
+
attr_reader :messages
|
57
|
+
|
58
|
+
# Return a list of extracted content from the messages.
|
59
|
+
attr_reader :contents
|
60
|
+
|
61
|
+
# = Message
|
62
|
+
# Represents a single logical TCP message that has been potentially
|
63
|
+
# reassembled from across multiple packets spanning multiple pcaps. Each
|
64
|
+
# message contains the stream to which it belongs in addition to whether
|
65
|
+
# this message was sent from the client or the server.
|
66
|
+
class Message
|
67
|
+
# Returns the stream to which this message belongs to.
|
68
|
+
attr_reader :stream
|
69
|
+
|
70
|
+
# Returns the index within the stream
|
71
|
+
attr_reader :index
|
72
|
+
|
73
|
+
# Returns the direction of the message (request/response).
|
74
|
+
attr_reader :dir
|
75
|
+
|
76
|
+
# Returns the actual bytes of the message.
|
77
|
+
attr_reader :bytes
|
78
|
+
|
79
|
+
def initialize stream, index, dir, bytes # :nodoc:
|
80
|
+
@stream = stream
|
81
|
+
@index = index
|
82
|
+
@dir = dir
|
83
|
+
@bytes = bytes
|
84
|
+
end
|
85
|
+
|
86
|
+
def inspect # :nodoc:
|
87
|
+
preview = bytes[0..32]
|
88
|
+
preview << "..." if bytes.size > 32
|
89
|
+
return "#<message:#{index} flow-#{stream.flow.id} #{preview.inspect}>"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def initialize xtractr, flow, json # :nodoc:
|
94
|
+
@xtractr = xtractr
|
95
|
+
@flow = flow
|
96
|
+
@messages = []
|
97
|
+
@contents = []
|
98
|
+
|
99
|
+
json['packets'].each do |pkt|
|
100
|
+
bytes = (pkt['b'] || []).map { |b| b.chr }.join('')
|
101
|
+
if messages.empty? or messages[-1].dir != pkt['d']
|
102
|
+
messages << Message.new(self, messages.size, pkt['d'], '')
|
103
|
+
end
|
104
|
+
messages[-1].bytes << bytes
|
105
|
+
end
|
106
|
+
|
107
|
+
# Run the stream/messages through each registered processor to pull
|
108
|
+
# out attachments, files, etc
|
109
|
+
Processor.processors.each do |processor|
|
110
|
+
if processor.matches? self
|
111
|
+
processor.extract self
|
112
|
+
break
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Iterate over each message in this stream
|
118
|
+
def each_message &blk
|
119
|
+
messages.each(&blk)
|
120
|
+
return self
|
121
|
+
end
|
122
|
+
|
123
|
+
def inspect # :nodoc:
|
124
|
+
return "#<stream:#{flow.id} ##{messages.size} messages>"
|
125
|
+
end
|
126
|
+
|
127
|
+
alias_method :each, :each_message
|
128
|
+
end
|
129
|
+
end # Xtractr
|
130
|
+
end # Mu
|
131
|
+
|
132
|
+
require 'mu/xtractr/stream/http'
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
module Mu
|
16
|
+
class Xtractr
|
17
|
+
# = Term
|
18
|
+
# A term represents a tokenized value of a field that also contains the
|
19
|
+
# frequency of occurence across all of the packets in a given index. Terms
|
20
|
+
# are useful to get quick snapshots of the index as well as in trend analysis.
|
21
|
+
# For example the following shows the top HTTP request methods as well as the
|
22
|
+
# frequency of those methods across all of the packets.
|
23
|
+
#
|
24
|
+
# xtractr.field('http.request.method').terms.each do |term|
|
25
|
+
# p [ term.value, term.frequency ]
|
26
|
+
# end
|
27
|
+
class Term
|
28
|
+
include Enumerable
|
29
|
+
|
30
|
+
attr_reader :xtractr # :nodoc:
|
31
|
+
|
32
|
+
# Return the field containing this term.
|
33
|
+
attr_reader :field
|
34
|
+
|
35
|
+
# Return the value of this term.
|
36
|
+
attr_reader :value
|
37
|
+
|
38
|
+
# Return the (packet) frequency of this term.
|
39
|
+
attr_reader :frequency
|
40
|
+
|
41
|
+
def initialize field, json # :nodoc:
|
42
|
+
@field = field
|
43
|
+
@xtractr = field.xtractr
|
44
|
+
@value = json['key']
|
45
|
+
@frequency = json['value']
|
46
|
+
end
|
47
|
+
|
48
|
+
# Fetch each packet from the index that has this term in this field. If
|
49
|
+
# the optional q is specified, the search query is AND'd with the term's
|
50
|
+
# own search query
|
51
|
+
# field.term('mozilla').each { |pkt| ... }
|
52
|
+
def each_packet(q=nil, &blk) # :yields: packet
|
53
|
+
_q = "#{field.name}:#{value}"
|
54
|
+
_q << " #{q}" if q
|
55
|
+
return Packets.new(xtractr, :q => _q).each(&blk)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Return an instance of Packets that serves as an iterator for all packets
|
59
|
+
# containing this term.
|
60
|
+
def packets q=nil
|
61
|
+
_q = "#{field.name}:#{value}"
|
62
|
+
_q << " #{q}" if q
|
63
|
+
return Packets.new(xtractr, :q => _q)
|
64
|
+
end
|
65
|
+
|
66
|
+
def inspect # :nodoc:
|
67
|
+
"#<term:#{field.name} #{value} #{frequency}>"
|
68
|
+
end
|
69
|
+
|
70
|
+
alias_method :each, :each_packet
|
71
|
+
end
|
72
|
+
end # Xtractr
|
73
|
+
end # Mu
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
require 'mu/xtractr'
|
16
|
+
require 'test/unit'
|
17
|
+
|
18
|
+
module Mu
|
19
|
+
class Xtractr
|
20
|
+
class Stream
|
21
|
+
class HTTP
|
22
|
+
class Test < Test::Unit::TestCase
|
23
|
+
attr_reader :xtractr
|
24
|
+
attr_reader :flow
|
25
|
+
|
26
|
+
def setup
|
27
|
+
@xtractr = Xtractr.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_extract
|
31
|
+
stream = xtractr.packets('http.content.type:gif').first.flow.stream
|
32
|
+
assert_equal(1, stream.contents.size)
|
33
|
+
content = stream.contents.first
|
34
|
+
assert_nothing_raised { content.inspect }
|
35
|
+
assert_equal('image/gif', content.type)
|
36
|
+
assert_nil(content.encoding)
|
37
|
+
assert_equal('content.4.1', content.name)
|
38
|
+
assert_equal(content.message.__id__, stream.messages[1].__id__)
|
39
|
+
|
40
|
+
stream = xtractr.packets('http.content.type:xml').first.flow.stream
|
41
|
+
assert_equal(1, stream.contents.size)
|
42
|
+
content = stream.contents.first
|
43
|
+
assert_nothing_raised { content.inspect }
|
44
|
+
assert_equal('text/xml', content.type)
|
45
|
+
assert_equal('gzip', content.encoding)
|
46
|
+
assert_equal('content.2.1', content.name)
|
47
|
+
assert_equal(content.message.__id__, stream.messages[1].__id__)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end # HTTP
|
51
|
+
end # Stream
|
52
|
+
end # Xtractr
|
53
|
+
end # Mu
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
require 'mu/xtractr'
|
16
|
+
require 'test/unit'
|
17
|
+
|
18
|
+
module Mu
|
19
|
+
class Xtractr
|
20
|
+
class Field
|
21
|
+
class Test < Test::Unit::TestCase
|
22
|
+
attr_reader :xtractr
|
23
|
+
|
24
|
+
def setup
|
25
|
+
@xtractr = Xtractr.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_Field
|
29
|
+
assert(Field.ancestors.include?(Enumerable), "Field doesn't mixin Enumerable")
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_each
|
33
|
+
field = xtractr.field('pkt.src')
|
34
|
+
terms = field.terms
|
35
|
+
assert_equal(11, terms.size)
|
36
|
+
terms.each { |t| assert_kind_of(Term, t) }
|
37
|
+
|
38
|
+
val = field.each_term { |t| assert_kind_of(Term, t) }
|
39
|
+
assert_equal(field, val)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_terms
|
43
|
+
field = xtractr.field('dns.qry.name')
|
44
|
+
terms = field.terms
|
45
|
+
assert_equal(12, terms.size)
|
46
|
+
assert_equal(true, terms.first.frequency > terms.last.frequency)
|
47
|
+
|
48
|
+
field.terms(/itunes/).each do |term|
|
49
|
+
assert_match(/itunes/, term.value)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_term
|
54
|
+
field = xtractr.field('dns.qry.name')
|
55
|
+
|
56
|
+
[ :[], :term ].each do |method|
|
57
|
+
term = field.send method, 'itunes'
|
58
|
+
assert_kind_of(Term, term)
|
59
|
+
assert_equal('itunes', term.value)
|
60
|
+
assert_equal(3, term.frequency)
|
61
|
+
end
|
62
|
+
|
63
|
+
assert_raise(ArgumentError) { field['foo'] }
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_count
|
67
|
+
field = xtractr.field('dns.qry.name')
|
68
|
+
counts = field.count
|
69
|
+
assert_equal(7, counts.size)
|
70
|
+
counts.each { |v| assert_kind_of(Views::Count, v) }
|
71
|
+
|
72
|
+
counts = field.count('flow.dst:4.2.2.1')
|
73
|
+
assert_equal(4, counts.size)
|
74
|
+
counts.each { |v| assert_kind_of(Views::Count, v) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_values
|
78
|
+
field = xtractr.field('dns.qry.name')
|
79
|
+
values = field.values
|
80
|
+
assert_equal(7, values.size)
|
81
|
+
values.each { |v| assert_kind_of(Field::Value, v) }
|
82
|
+
|
83
|
+
values = field.values('flow.dst:4.2.2.1')
|
84
|
+
assert_equal(4, values.size)
|
85
|
+
values.each { |v| assert_kind_of(Field::Value, v) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_inspect
|
89
|
+
field = xtractr.field('dns.qry.name')
|
90
|
+
assert_nothing_raised { field.inspect }
|
91
|
+
end
|
92
|
+
|
93
|
+
class Value
|
94
|
+
class Test < ::Test::Unit::TestCase
|
95
|
+
attr_reader :xtractr
|
96
|
+
attr_reader :value
|
97
|
+
|
98
|
+
def setup
|
99
|
+
@xtractr = Xtractr.new
|
100
|
+
@value = xtractr.field('dns.qry.name').values.first
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_q
|
104
|
+
assert_equal('dns.qry.name:"ax.search.itunes.apple.com"', value.q)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_packets
|
108
|
+
packets = value.packets
|
109
|
+
assert_kind_of(Packets, packets)
|
110
|
+
assert_equal('dns.qry.name:"ax.search.itunes.apple.com"', packets.q)
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_each_packet
|
114
|
+
v = value.each_packet { |pkt| assert_kind_of(Packet, pkt) }
|
115
|
+
assert_equal(value, v)
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_count
|
119
|
+
value.count('pkt.src').each do |c|
|
120
|
+
assert_kind_of(Views::Count, c)
|
121
|
+
assert_equal('pkt.src', c.field.name)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_sum
|
126
|
+
value.sum('pkt.src', 'pkt.length').each do |s|
|
127
|
+
assert_kind_of(Views::Sum, s)
|
128
|
+
assert_equal('pkt.src', s.field.name)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_inspect
|
133
|
+
assert_nothing_raised { value.inspect }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end # Field
|
139
|
+
end # Xtractr
|
140
|
+
end # Mu
|