dorothy2 0.0.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,178 @@
|
|
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
|
+
# = Field
|
18
|
+
# Field represents a named packet field in the <b>xtractr</b> index. Each field
|
19
|
+
# contains tokenized terms along with the frequency at which they occur. A
|
20
|
+
# field is a queryable object and can be used to iterate over flows or packets
|
21
|
+
# that match the Field::Value. See Flow or Packet for more information on the
|
22
|
+
# fields that are stored in the index.
|
23
|
+
#
|
24
|
+
# xtractr.field('http.request.uri').each_packet { |packet ... }
|
25
|
+
#
|
26
|
+
# The fields are useful for quick analysis on top trending terms across the
|
27
|
+
# entire index. Here are the top HTTP fields that have NOP slides in them:
|
28
|
+
#
|
29
|
+
# xtractr.fields(/^http/).select do |field|
|
30
|
+
# not field.terms(/AAAAAA/i).empty?
|
31
|
+
# end
|
32
|
+
class Field
|
33
|
+
include Enumerable
|
34
|
+
|
35
|
+
attr_reader :xtractr # :nodoc:
|
36
|
+
|
37
|
+
# The name of the field.
|
38
|
+
attr_reader :name
|
39
|
+
|
40
|
+
# = Field::Value
|
41
|
+
# Field::Value represents an instance of a field with a concrete value that
|
42
|
+
# can further used for fine grained searches.
|
43
|
+
class Value
|
44
|
+
attr_reader :xtractr # :nodoc:
|
45
|
+
|
46
|
+
# Return the field object.
|
47
|
+
attr_reader :field
|
48
|
+
|
49
|
+
# Return the value of the field object.
|
50
|
+
attr_reader :value
|
51
|
+
|
52
|
+
def initialize xtractr, json # :nodoc:
|
53
|
+
@xtractr = xtractr
|
54
|
+
@field = Field.new(xtractr, json['key'])
|
55
|
+
@value = json['value']
|
56
|
+
end
|
57
|
+
|
58
|
+
def q # :nodoc:
|
59
|
+
"#{field.name}:\"#{value}\""
|
60
|
+
end
|
61
|
+
|
62
|
+
# Fetch the list of packets that contain this Field::Value. If the
|
63
|
+
# optional query is given, it's AND'd to the query that matches this
|
64
|
+
# Field::Value.
|
65
|
+
# value.packets.each { |pkt| ... }
|
66
|
+
# value.packets('dns.qry.name:apple').each { |pkt| ... }
|
67
|
+
def packets _q=nil
|
68
|
+
q2 = q
|
69
|
+
q2 << " #{_q}" if _q
|
70
|
+
Packets.new xtractr, :q => q2
|
71
|
+
end
|
72
|
+
|
73
|
+
# Iterate over each packet that contains this field value. This is
|
74
|
+
# a convenience function used primiarily in method chaining.
|
75
|
+
def each_packet(_q=nil, &blk) # :yields: packet
|
76
|
+
packets(_q).each(&blk)
|
77
|
+
return self
|
78
|
+
end
|
79
|
+
|
80
|
+
# Count the unique values of the specified field amongst all the packets
|
81
|
+
# that matched the query.
|
82
|
+
# value.count('http.request.method')
|
83
|
+
def count _field
|
84
|
+
which = field.name =~ /^flow\./ ? 'flows' : 'packets'
|
85
|
+
Views.count xtractr, _field, "/api/#{which}/report", :q => q
|
86
|
+
end
|
87
|
+
|
88
|
+
# Sum the unique numeric values of vfield, keyed by the unique values of
|
89
|
+
# kfield.
|
90
|
+
# value.sum('flow.src', 'flow.bytes')
|
91
|
+
def sum kfield, vfield
|
92
|
+
which = field.name =~ /^flow\./ ? 'flows' : 'packets'
|
93
|
+
Views.sum xtractr, kfield, vfield, "/api/#{which}/report", :q => q
|
94
|
+
end
|
95
|
+
|
96
|
+
def inspect # :nodoc:
|
97
|
+
"#<value:#{field.name} #{value}>"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize xtractr, name # :nodoc:
|
102
|
+
@xtractr = xtractr
|
103
|
+
@name = name
|
104
|
+
end
|
105
|
+
|
106
|
+
# Fetch the terms and their packet frequencies (in packets) for this field.
|
107
|
+
# If the optional start term is given, then the term enumeration starts
|
108
|
+
# from the specified term.
|
109
|
+
# field.each { |term| ... }
|
110
|
+
# field.each('mozilla') { |term| ... }
|
111
|
+
def each_term(start='') # :yields: term
|
112
|
+
opts = {}
|
113
|
+
opts[:start] = start
|
114
|
+
opts[:limit] = 101
|
115
|
+
|
116
|
+
while true
|
117
|
+
result = xtractr.json "api/field/#{name}/terms", opts
|
118
|
+
rows = result['rows']
|
119
|
+
break if rows.empty?
|
120
|
+
|
121
|
+
rows[0, 100].each do |row|
|
122
|
+
term = Term.new self, row
|
123
|
+
yield term
|
124
|
+
end
|
125
|
+
|
126
|
+
break if rows.size < 101
|
127
|
+
opts[:start] = rows[100]['key']
|
128
|
+
end
|
129
|
+
|
130
|
+
return self
|
131
|
+
end
|
132
|
+
|
133
|
+
# Fetch the list of <em>all</em> the unique terms for this field, sorted by the
|
134
|
+
# frequency of occurence in the packets. This can be used for some quick
|
135
|
+
# trend analysis to see which term of a given field appears most amongst
|
136
|
+
# all packets in the index. Here's an example to print out the top 10 terms
|
137
|
+
# of <em>http.request.uri</em>.
|
138
|
+
# p xtractr.field('http.request.uri').terms[0..10]
|
139
|
+
def terms regex=nil
|
140
|
+
regex = Regexp.new(regex, Regexp::IGNORECASE) if regex.is_a? String
|
141
|
+
t = regex ? entries.select { |name| name =~ regex } : entries
|
142
|
+
t.sort { |a, b| b.frequency <=> a.frequency }
|
143
|
+
end
|
144
|
+
|
145
|
+
# Find the term for this field which has the name and the packet frequency.
|
146
|
+
# field.term 'mozilla'
|
147
|
+
def [] which
|
148
|
+
result = xtractr.json "api/field/#{name}/terms", :start => which, :limit => 1
|
149
|
+
rows = result['rows']
|
150
|
+
if rows.empty? || rows[0]['key'] != which
|
151
|
+
raise ArgumentError, "Unknown term #{which} for field #{name}"
|
152
|
+
end
|
153
|
+
return Term.new(self, rows[0])
|
154
|
+
end
|
155
|
+
|
156
|
+
# Find out all the unique values of this field with an optional query.
|
157
|
+
# xtractr.field('http.user.agent').count('flow.src:192.168.1.1')
|
158
|
+
def count q='*'
|
159
|
+
Views.count xtractr, self, "api/flows/report", :q => q
|
160
|
+
end
|
161
|
+
|
162
|
+
# Return a list of Field::Value objects for this field, sorted by their
|
163
|
+
# frequency. This is a convenience method to use the resulting Field::Value
|
164
|
+
# objects in method chaining.
|
165
|
+
# xtractr.field('http.user.agent').values.first.packets.slice('foo.pcap')
|
166
|
+
def values q='*'
|
167
|
+
count(q).map { |c| c.object }
|
168
|
+
end
|
169
|
+
|
170
|
+
def inspect # :nodoc:
|
171
|
+
"#<field:#{name}>"
|
172
|
+
end
|
173
|
+
|
174
|
+
alias_method :each, :each_term
|
175
|
+
alias_method :term, :[]
|
176
|
+
end
|
177
|
+
end # Xtractr
|
178
|
+
end # Mu
|
@@ -0,0 +1,162 @@
|
|
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
|
+
# = Flow
|
18
|
+
# Flow represents a single conversation between two hosts. Depending on whether
|
19
|
+
# it's IP, UDP or TCP, xtractr uses different information from the IP header
|
20
|
+
# of the packets (src/dst addresses and ports) to logically group them
|
21
|
+
# together. Each flow also has the duration (difference in the timestamp between
|
22
|
+
# the first and last packet in the conversation), the total bytes that were
|
23
|
+
# exchanged as well as the logical messages that were exchanged. For example:
|
24
|
+
#
|
25
|
+
# <b>Identify hosts that performed TCP port scans</b>
|
26
|
+
#
|
27
|
+
# xtractr.flows('flow.proto:6 flow.cmsgs:0 flow.smsgs:0').count('flow.src')
|
28
|
+
#
|
29
|
+
# <b>Identify DNS queries with no response (possibly timed out)</b>
|
30
|
+
#
|
31
|
+
# xtractr.flows('flow.service:DNS flow.smsgs:0').count('dns.qry.name')
|
32
|
+
class Flow
|
33
|
+
include Enumerable
|
34
|
+
|
35
|
+
attr_reader :xtractr # :nodoc:
|
36
|
+
|
37
|
+
# The unique ID of the flow.
|
38
|
+
attr_reader :id
|
39
|
+
|
40
|
+
# The timestamp of the flow, determined by the first packet in the flow.
|
41
|
+
attr_reader :time
|
42
|
+
|
43
|
+
# The duration of the flow, determined by the first and last packet in the flow.
|
44
|
+
attr_reader :duration
|
45
|
+
|
46
|
+
# The source host of the flow.
|
47
|
+
attr_reader :src
|
48
|
+
|
49
|
+
# The destination host of the flow.
|
50
|
+
attr_reader :dst
|
51
|
+
|
52
|
+
# The IP protocol of the flow.
|
53
|
+
attr_reader :proto
|
54
|
+
|
55
|
+
# The source port of the flow (if applicable).
|
56
|
+
attr_reader :sport
|
57
|
+
|
58
|
+
# The destination port of the flow (if applicable).
|
59
|
+
attr_reader :dport
|
60
|
+
|
61
|
+
# The service of the flow (like DNS or HTTP).
|
62
|
+
attr_reader :service
|
63
|
+
|
64
|
+
# The title of the flow.
|
65
|
+
attr_reader :title
|
66
|
+
|
67
|
+
# The total ##packets in the flow.
|
68
|
+
attr_reader :packets
|
69
|
+
|
70
|
+
# The total ##bytes (request and response) in the flow.
|
71
|
+
attr_reader :bytes
|
72
|
+
|
73
|
+
# The logical client messages (payloads) in the flow.
|
74
|
+
attr_reader :cmsgs
|
75
|
+
|
76
|
+
# The logical server messages (payloads) in the flow.
|
77
|
+
attr_reader :smsgs
|
78
|
+
|
79
|
+
def initialize xtractr, json # :nodoc:
|
80
|
+
@xtractr = xtractr
|
81
|
+
@id = json['id']
|
82
|
+
@time = json['time']
|
83
|
+
@duration = json['duration']
|
84
|
+
@src = Host.new xtractr, json['src']
|
85
|
+
@dst = Host.new xtractr, json['dst']
|
86
|
+
@proto = json['proto']
|
87
|
+
@sport = json['sport']
|
88
|
+
@dport = json['dport']
|
89
|
+
@service = Service.new xtractr, json['service']
|
90
|
+
@title = json['title']
|
91
|
+
@packets = json['packets']
|
92
|
+
@bytes = json['bytes']
|
93
|
+
@cmsgs = json['cmsgs']
|
94
|
+
@smsgs = json['smsgs']
|
95
|
+
@first_id = json['first']
|
96
|
+
@last_id = json['last']
|
97
|
+
@iterator = Packets.new(xtractr, :q => "pkt.flow:#{id}")
|
98
|
+
end
|
99
|
+
|
100
|
+
# Return the first packet for this flow. Together the first and last
|
101
|
+
# packets make up the span of the flow. Read this
|
102
|
+
# blog[http://labs.mudynamics.com/2010/09/30/visualizing-application-flows-with-xtractr/]
|
103
|
+
# to see how these spans enable flow visualization.
|
104
|
+
# xtractr.flow(1).first.bytes
|
105
|
+
def first
|
106
|
+
@first ||= xtractr.packet @first_id
|
107
|
+
end
|
108
|
+
|
109
|
+
# Return the last packet for this flow. Together the first and last
|
110
|
+
# packets make up the span of the flow. Read this
|
111
|
+
# blog[http://labs.mudynamics.com/2010/09/30/visualizing-application-flows-with-xtractr/]
|
112
|
+
# to see how these spans enable flow visualization.
|
113
|
+
# xtractr.flow(2).last.bytes
|
114
|
+
def last
|
115
|
+
@last ||= xtractr.packet @last_id
|
116
|
+
end
|
117
|
+
|
118
|
+
# Iterate over each packet in this flow.
|
119
|
+
# flow.each { |pkt| ... }
|
120
|
+
def each_packet(&blk) # :yields: packet
|
121
|
+
@iterator.each(&blk)
|
122
|
+
return self
|
123
|
+
end
|
124
|
+
|
125
|
+
# Reassemble the TCP stream for this flow (assuming it's a TCP flow) and
|
126
|
+
# return the stream. This is the basis for doing content extraction from
|
127
|
+
# packets even if the packets span multiple pcaps.
|
128
|
+
# xtractr.service('HTTP').flows.first.stream
|
129
|
+
def stream
|
130
|
+
result = xtractr.json "api/flow/#{id}/stream"
|
131
|
+
return Stream.new(xtractr, self, result)
|
132
|
+
end
|
133
|
+
|
134
|
+
# A convenience method to fetch the stream for this flow, extract the
|
135
|
+
# content and then return an array of contents.
|
136
|
+
# xtractr.flows('flow.service:HTTP favicon.ico').each do |flow|
|
137
|
+
# flow.contents.each { |c| c.save }
|
138
|
+
# end
|
139
|
+
def contents
|
140
|
+
stream.contents
|
141
|
+
end
|
142
|
+
|
143
|
+
# Stich together a pcap made up of all packets containing this flow and
|
144
|
+
# save it to the filename. It's possible for the packets to span multiple
|
145
|
+
# pcaps, but xtractr makes it seamless.
|
146
|
+
# flow.save("foo.pcap")
|
147
|
+
def save filename
|
148
|
+
open(filename, "w") do |ios|
|
149
|
+
pcap = xtractr.get "api/packets/slice", :q => "pkt.flow:#{id}"
|
150
|
+
ios.write pcap
|
151
|
+
end
|
152
|
+
return self
|
153
|
+
end
|
154
|
+
|
155
|
+
def inspect # :nodoc:
|
156
|
+
"#<flow:#{id} #{service.name} #{src.address}:#{sport} > #{dst.address}:#{dport} #{title}"
|
157
|
+
end
|
158
|
+
|
159
|
+
alias_method :each, :each_packet
|
160
|
+
end
|
161
|
+
end # Xtractr
|
162
|
+
end # Mu
|
@@ -0,0 +1,118 @@
|
|
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
|
+
# Flows is an iterator for the flows in the index, based on a given query.
|
18
|
+
# The default query for this iterator is '*', implying that it will iterate
|
19
|
+
# over <em>all</em> the flows in the index.
|
20
|
+
class Flows
|
21
|
+
include Enumerable
|
22
|
+
|
23
|
+
attr_reader :xtractr # :nodoc:
|
24
|
+
|
25
|
+
MAX_PAGE_SIZE = 100 # :nodoc:
|
26
|
+
|
27
|
+
def initialize xtractr, opts # :nodoc:
|
28
|
+
@xtractr = xtractr
|
29
|
+
@opts = opts.dup
|
30
|
+
@opts[:q] ||= '*'
|
31
|
+
end
|
32
|
+
|
33
|
+
def q # :nodoc:
|
34
|
+
@opts[:q]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Iterate over each flow that matches the search criteria. It's always
|
38
|
+
# better to use this with a fine-grained query instead of flows.to_a
|
39
|
+
# because it's going to try and load *all* flows from the index.
|
40
|
+
# xtractr.flows("flow.src:192.168.1.1").each { |flow| ... }
|
41
|
+
def each_flow() # :yields: flow
|
42
|
+
_opts = @opts.dup
|
43
|
+
_opts[:start] ||= 1
|
44
|
+
_opts[:limit] ||= MAX_PAGE_SIZE
|
45
|
+
|
46
|
+
while true
|
47
|
+
result = xtractr.json "api/flows", _opts
|
48
|
+
rows = result['rows']
|
49
|
+
break if rows.empty?
|
50
|
+
|
51
|
+
rows[0, MAX_PAGE_SIZE-1].each do |row|
|
52
|
+
flow = Flow.new xtractr, row
|
53
|
+
yield flow
|
54
|
+
end
|
55
|
+
|
56
|
+
break if rows.size < MAX_PAGE_SIZE
|
57
|
+
_opts[:start] = rows[MAX_PAGE_SIZE-1]['id']
|
58
|
+
end
|
59
|
+
return self
|
60
|
+
end
|
61
|
+
|
62
|
+
# Fetch the first flow that matched the query. This is mainly used for
|
63
|
+
# unit testing, but useful within IRB to experiment with method chaining.
|
64
|
+
# flows.first.save("1.pcap")
|
65
|
+
def first
|
66
|
+
result = xtractr.json "api/flows", :start => 1, :limit => 1, :q => q
|
67
|
+
rows = result['rows']
|
68
|
+
rows.empty? ? nil : Flow.new(xtractr, rows[0])
|
69
|
+
end
|
70
|
+
|
71
|
+
# Count the unique values of the specified field amongst all the flows
|
72
|
+
# that matched the query.
|
73
|
+
# xtractr.flows('index.html').count('http.request.uri')
|
74
|
+
def count field
|
75
|
+
Views.count xtractr, field, '/api/flows/report', @opts
|
76
|
+
end
|
77
|
+
|
78
|
+
# Return a list of Field::Value objects for the specified field, sorted
|
79
|
+
# by their frequency. This is a convenience method used in method chaining.
|
80
|
+
# xtractr.flows('index.html').values('http.request.uri')
|
81
|
+
def values field
|
82
|
+
count(field).map { |c| c.object }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sum the numeric values of vfield, keyed by the unique values of
|
86
|
+
# kfield.
|
87
|
+
# xtractr.flows('index.html').sum('http.request.uri', 'flow.bytes')
|
88
|
+
def sum kfield, vfield
|
89
|
+
Views.sum xtractr, kfield, vfield, '/api/flows/report', @opts
|
90
|
+
end
|
91
|
+
|
92
|
+
# Save all the packets for this collection of flows into a pcap. It's
|
93
|
+
# possible that the packets for the flows might span multiple indexed
|
94
|
+
# pcaps.
|
95
|
+
# xtractr.flows('flow.service:DNS AAAA').save('dns.pcap')
|
96
|
+
def save filename
|
97
|
+
flow_ids = []
|
98
|
+
each_flow do |flow|
|
99
|
+
flow_ids << flow.id.to_s
|
100
|
+
break if flow_ids.size >= 1024
|
101
|
+
end
|
102
|
+
|
103
|
+
_q = "pkt.flow:(" << flow_ids.join('||') << ')'
|
104
|
+
open(filename, "w") do |ios|
|
105
|
+
pcap = xtractr.get "api/packets/slice", :q => _q
|
106
|
+
ios.write pcap
|
107
|
+
end
|
108
|
+
return self
|
109
|
+
end
|
110
|
+
|
111
|
+
def inspect # :nodoc:
|
112
|
+
"#<flows:#{@opts[:q]}>"
|
113
|
+
end
|
114
|
+
|
115
|
+
alias_method :each, :each_flow
|
116
|
+
end
|
117
|
+
end # Xtractr
|
118
|
+
end # Mu
|
@@ -0,0 +1,87 @@
|
|
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
|
+
# = Host
|
18
|
+
# Host is a generic entity representing MAC, IPv4 and IPv6 addresses. You can
|
19
|
+
# get a list of all the unique hosts in the index using the root xtractr instance.
|
20
|
+
# xtractr.hosts
|
21
|
+
class Host
|
22
|
+
attr_reader :xtractr # :nodoc:
|
23
|
+
|
24
|
+
# Returns the address of this host.
|
25
|
+
attr_reader :address
|
26
|
+
|
27
|
+
def initialize xtractr, address # :nodoc:
|
28
|
+
@xtractr = xtractr
|
29
|
+
@address = address
|
30
|
+
end
|
31
|
+
|
32
|
+
# Use the host as a server and get a unique list of its clients.
|
33
|
+
# xtractr.hosts(/192.168/).first.clients
|
34
|
+
def clients q=nil
|
35
|
+
_q = role2q :server, 'flow', q
|
36
|
+
Flows.new(xtractr, :q => _q).count('flow.src')
|
37
|
+
end
|
38
|
+
|
39
|
+
# Use the host as a client and get a unique list its servers.
|
40
|
+
# xtractr.hosts(/192.168/).first.servers
|
41
|
+
def servers q=nil
|
42
|
+
_q = role2q :client, 'flow', q
|
43
|
+
Flows.new(xtractr, :q => _q).count('flow.dst')
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get a unique list of the host's services. <em>role</em> can be one of
|
47
|
+
# :any, :client or :server to specify the role.
|
48
|
+
# host.services :server
|
49
|
+
def services role =:any, q=nil
|
50
|
+
_q = role2q role, 'flow', q
|
51
|
+
Flows.new(xtractr, :q => _q).count('flow.service')
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return a flow iterator to iterate over the various flows that contain
|
55
|
+
# this host in the specified role.
|
56
|
+
# host.flows :client
|
57
|
+
def flows role =:any, q=nil
|
58
|
+
_q = role2q role, 'flow', q
|
59
|
+
Flows.new(xtractr, :q => _q)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Return a packet iterator to iterate over the various packets that contain
|
63
|
+
# this host in the specified role.
|
64
|
+
# host.packets :server
|
65
|
+
def packets role =:any, q=nil
|
66
|
+
_q = role2q role, 'pkt', q
|
67
|
+
Packets.new(xtractr, :q => _q)
|
68
|
+
end
|
69
|
+
|
70
|
+
def inspect # :nodoc:
|
71
|
+
"#<host:#{address}>"
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def role2q role, forp, q=nil # :nodoc:
|
76
|
+
_q = case role
|
77
|
+
when :any: "#{forp}.src|#{forp}.dst:\"#{address}\""
|
78
|
+
when :client: "#{forp}.src:\"#{address}\""
|
79
|
+
when :server: "#{forp}.dst:\"#{address}\""
|
80
|
+
else raise ArgumentError, "Unknown role #{role}"
|
81
|
+
end
|
82
|
+
_q << " #{q}" if q
|
83
|
+
return _q
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end # Xtractr
|
87
|
+
end # Mu
|
@@ -0,0 +1,138 @@
|
|
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
|
+
# = Packet
|
18
|
+
# A class that represents a single packet in the xtractr index. You can
|
19
|
+
# iterate over all the packets in the index like this:
|
20
|
+
# xtractr.packets('blah').each { |pkt| ... }
|
21
|
+
class Packet
|
22
|
+
include Enumerable
|
23
|
+
|
24
|
+
attr_reader :xtractr # :nodoc:
|
25
|
+
|
26
|
+
# The unique ID of the packet.
|
27
|
+
attr_reader :id
|
28
|
+
|
29
|
+
# The file offset of the packet within the pcap.
|
30
|
+
attr_reader :offset
|
31
|
+
|
32
|
+
# The length of the packet (the entire frame).
|
33
|
+
attr_reader :length
|
34
|
+
|
35
|
+
# The relative timestamp of the packet.
|
36
|
+
attr_reader :time
|
37
|
+
|
38
|
+
# The direction of the packet (if it belongs to a flow).
|
39
|
+
attr_reader :dir
|
40
|
+
|
41
|
+
# The source host of the packet.
|
42
|
+
attr_reader :src
|
43
|
+
|
44
|
+
# The destination host of the packet.
|
45
|
+
attr_reader :dst
|
46
|
+
|
47
|
+
# The service of the packet.
|
48
|
+
attr_reader :service
|
49
|
+
|
50
|
+
# The title of the packet.
|
51
|
+
attr_reader :title
|
52
|
+
|
53
|
+
def initialize xtractr, json # :nodoc:
|
54
|
+
@xtractr = xtractr
|
55
|
+
@id = json['id']
|
56
|
+
@offset = json['offset']
|
57
|
+
@length = json['length']
|
58
|
+
@pcap_id = json['pcap']
|
59
|
+
@flow_id = json['flow']
|
60
|
+
@time = json['time']
|
61
|
+
@dir = json['dir']
|
62
|
+
@src = Host.new xtractr, json['src']
|
63
|
+
@dst = Host.new xtractr, json['dst']
|
64
|
+
@service = Service.new xtractr, json['service']
|
65
|
+
@title = json['title']
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the flow (if any) that this packet belongs to.
|
69
|
+
# xtractr.packets('index.html').first.flow
|
70
|
+
def flow
|
71
|
+
return nil if @flow_id.zero?
|
72
|
+
@flow ||= xtractr.flow @flow_id
|
73
|
+
end
|
74
|
+
|
75
|
+
# Fetch the actual packet data from the index. The return value is a
|
76
|
+
# String (that might contain null characters).
|
77
|
+
# xtractr.packets('index.html').first.bytes
|
78
|
+
def bytes
|
79
|
+
result = xtractr.json "/api/packet/#{id}/bytes"
|
80
|
+
result['bytes'].map { |b| b.chr }.join('')
|
81
|
+
end
|
82
|
+
|
83
|
+
# For UDP/TCP (both IPv4 and IPv6) packets, fetch just the layer4 payload.
|
84
|
+
# Returns an empty string for all other types of packet.
|
85
|
+
# xtractr.packets('http.request.method:GET').each do |pkt|
|
86
|
+
# puts pkt.payload
|
87
|
+
# end
|
88
|
+
def payload
|
89
|
+
result = xtractr.json "/api/packet/#{id}/bytes"
|
90
|
+
bytes = result['bytes']
|
91
|
+
l4size = result['l4size'] || 0
|
92
|
+
bytes[-l4size, l4size].map { |b| b.chr }.join('')
|
93
|
+
end
|
94
|
+
|
95
|
+
# Iterate over each Field::Value in the packet. The various packet fields
|
96
|
+
# are only available if the indexing was done with <em>--mode forensics</em>.
|
97
|
+
# packet.each('ip.ttl') { |fv| ... }
|
98
|
+
def each_field(regex=nil) # :yields: value
|
99
|
+
regex = Regexp.new(regex) if regex.is_a? String
|
100
|
+
result = xtractr.json "/api/packet/#{id}/fields"
|
101
|
+
rows = result['rows']
|
102
|
+
rows = rows.select { |row| row['key'] =~ regex } if regex
|
103
|
+
rows.each do |row|
|
104
|
+
value = Field::Value.new(xtractr, row)
|
105
|
+
yield value
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Fetch the values of the specified field for this packet. Even if there's
|
110
|
+
# only a single value for the field, it's returned as an array of 1 element
|
111
|
+
# packet.field('ip.ttl').each { |ttl| ... }
|
112
|
+
def [] name
|
113
|
+
result = xtractr.json "/api/packet/#{id}/field/#{name}"
|
114
|
+
return result['rows']
|
115
|
+
end
|
116
|
+
|
117
|
+
# Extract just this packet and save it to the specified file as a pcap.
|
118
|
+
# You can also save a collection of packets using Packets#save or a
|
119
|
+
# collection of flows using Flows#save.
|
120
|
+
# packet.save("foo.pcap")
|
121
|
+
def save filename
|
122
|
+
open(filename, "w") do |ios|
|
123
|
+
pcap = xtractr.get "api/packet/#{id}/pcap"
|
124
|
+
ios.write pcap
|
125
|
+
end
|
126
|
+
return self
|
127
|
+
end
|
128
|
+
|
129
|
+
def inspect # :nodoc:
|
130
|
+
"#<pkt:#{id} #{src.address} > #{dst.address} #{service.name} #{title}"
|
131
|
+
end
|
132
|
+
|
133
|
+
alias_method :each, :each_field
|
134
|
+
alias_method :fields, :entries
|
135
|
+
alias_method :field, :[]
|
136
|
+
end
|
137
|
+
end # Xtractr
|
138
|
+
end # Mu
|