patronus_fati 1.3.3 → 1.3.4
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.
- checksums.yaml +4 -4
- data/lib/patronus_fati/consts.rb +4 -0
- data/lib/patronus_fati/data_models/access_point.rb +2 -1
- data/lib/patronus_fati/data_models/client.rb +21 -3
- data/lib/patronus_fati/message_models/client.rb +3 -2
- data/lib/patronus_fati/message_processor/bssid.rb +8 -2
- data/lib/patronus_fati/message_processor/client.rb +8 -2
- data/lib/patronus_fati/message_processor/ssid.rb +1 -1
- data/lib/patronus_fati/version.rb +1 -1
- data/spec/patronus_fati/data_models/client_spec.rb +21 -3
- data/tools/change_analysis.rb +91 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7cc61dae4e9a212fa42cdf646ff7520047c57f4
|
4
|
+
data.tar.gz: d5214c2c449b85771990ef81af25202f4b29c21c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d12dabffaca480b0bb9e412173d37d64ba72a9adfcbeda9b2d36eae11c7efcc0c0288be3ae791e81b441c87041ceb36d04a9ca79085e6295f0c2a6128f64727d
|
7
|
+
data.tar.gz: e04be294d9b180b8794e872df31c0ec0d6fa8486abfb1c69386d34fe09ad754791bd17b20e1e4fe89753b7b10b903c05a8070f7a85a84b0b8c449b9bac299e19
|
data/lib/patronus_fati/consts.rb
CHANGED
@@ -79,6 +79,10 @@ module PatronusFati
|
|
79
79
|
dirtyChildren: (1 << 3),
|
80
80
|
}.freeze
|
81
81
|
|
82
|
+
# The minimum signal threshold we'll use to decide whether or not to track a
|
83
|
+
# new access point or client. This help remove noise in the produced data.
|
84
|
+
SIGNAL_THRESHOLD = -86
|
85
|
+
|
82
86
|
# This is how many tracked intervals that need to be seen overlapping before
|
83
87
|
# we consider an access point as transmitting multiple SSIDs. The length of
|
84
88
|
# this is dependent on the length of presence intervals. The value of
|
@@ -3,7 +3,7 @@ module PatronusFati
|
|
3
3
|
class AccessPoint
|
4
4
|
include CommonState
|
5
5
|
|
6
|
-
attr_accessor :client_macs, :local_attributes, :ssids
|
6
|
+
attr_accessor :client_macs, :last_dbm, :local_attributes, :ssids
|
7
7
|
|
8
8
|
LOCAL_ATTRIBUTE_KEYS = [ :bssid, :channel, :type ].freeze
|
9
9
|
|
@@ -94,6 +94,7 @@ module PatronusFati
|
|
94
94
|
def diagnostic_data
|
95
95
|
dd = super
|
96
96
|
dd.merge!(ssids: Hash[ssids.map { |k, s| [k, s.diagnostic_data] }]) if ssids
|
97
|
+
dd[:last_dbm] = last_dbm if last_dbm
|
97
98
|
dd
|
98
99
|
end
|
99
100
|
|
@@ -3,7 +3,7 @@ module PatronusFati
|
|
3
3
|
class Client
|
4
4
|
include CommonState
|
5
5
|
|
6
|
-
attr_accessor :access_point_bssids, :local_attributes, :probes
|
6
|
+
attr_accessor :access_point_bssids, :last_dbm, :local_attributes, :probes
|
7
7
|
|
8
8
|
LOCAL_ATTRIBUTE_KEYS = [ :mac, :channel ].freeze
|
9
9
|
|
@@ -16,7 +16,7 @@ module PatronusFati
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def announce_changes
|
19
|
-
return unless dirty? && valid?
|
19
|
+
return unless dirty? && valid? && worth_syncing?
|
20
20
|
|
21
21
|
if active?
|
22
22
|
status = new? ? :new : :changed
|
@@ -51,6 +51,13 @@ module PatronusFati
|
|
51
51
|
probes.reject! { |_, pres| pres.dead? }
|
52
52
|
end
|
53
53
|
|
54
|
+
def diagnostic_data
|
55
|
+
dd = super
|
56
|
+
dd[:last_dbm] = last_dbm if last_dbm
|
57
|
+
dd[:visible_time] = presence.visible_time
|
58
|
+
dd
|
59
|
+
end
|
60
|
+
|
54
61
|
def full_state
|
55
62
|
{
|
56
63
|
active: active?,
|
@@ -65,7 +72,7 @@ module PatronusFati
|
|
65
72
|
def initialize(mac)
|
66
73
|
super
|
67
74
|
self.access_point_bssids = []
|
68
|
-
self.local_attributes = { mac: mac }
|
75
|
+
self.local_attributes = { channel: 0, mac: mac }
|
69
76
|
self.probes = {}
|
70
77
|
end
|
71
78
|
|
@@ -99,6 +106,17 @@ module PatronusFati
|
|
99
106
|
result = Louis.lookup(local_attributes[:mac])
|
100
107
|
result['long_vendor'] || result['short_vendor']
|
101
108
|
end
|
109
|
+
|
110
|
+
# This is a safety mechanism to check whether or not a client device is
|
111
|
+
# actually 'present'. This is intended to cut out the one time fake
|
112
|
+
# generated addresses from devices that generate random MAC addresses,
|
113
|
+
# probe quickly and disappear and requires us to either see a client
|
114
|
+
# connect to an access point, be visible for more than one interval, or
|
115
|
+
# have already been synced.
|
116
|
+
def worth_syncing?
|
117
|
+
access_point_bssids.any? || sync_flag?(:syncedOnline) ||
|
118
|
+
(presence && presence.visible_time && presence.visible_time > INTERVAL_DURATION)
|
119
|
+
end
|
102
120
|
end
|
103
121
|
end
|
104
122
|
end
|
@@ -30,11 +30,12 @@ module PatronusFati
|
|
30
30
|
:noise_rssi, :minsignal_rssi, :minnoise_rssi,
|
31
31
|
:maxsignal_rssi, :maxnoise_rssi, :bestlat, :bestlon,
|
32
32
|
:bestalt, :atype, :datasize, :maxseenrate,
|
33
|
-
:encodingset, :carrierset, :decrypted,
|
33
|
+
:encodingset, :carrierset, :decrypted,
|
34
34
|
:fragments, :retries, :newpackets) { |val| val.to_i }
|
35
|
+
Client.set_data_filter(:channel) { |val| val.to_i == 0 ? nil : val.to_i }
|
35
36
|
Client.set_data_filter(:gpsfixed) { |val| val.to_i == 1 }
|
36
37
|
|
37
|
-
Client.set_data_filter(:ip, :gatewayip) { |val| (val ==
|
38
|
+
Client.set_data_filter(:ip, :gatewayip) { |val| (val == '0.0.0.0') ? nil : val }
|
38
39
|
Client.set_data_filter(:dhcphost) { |val| (val || '').strip.empty? ? nil : val }
|
39
40
|
|
40
41
|
Client.set_data_filter(:freqmhz) do |val|
|
@@ -13,20 +13,26 @@ module PatronusFati::MessageProcessor::Bssid
|
|
13
13
|
# Ignore the initial flood of cached data and any objects that would have
|
14
14
|
# already expired
|
15
15
|
return unless PatronusFati.past_initial_flood? &&
|
16
|
-
obj
|
16
|
+
obj.lasttime >= PatronusFati::DataModels::AccessPoint.current_expiration_threshold
|
17
17
|
|
18
18
|
# Some messages from kismet come in corrupted with partial MACs. We care
|
19
19
|
# not for them, just drop the bad data.
|
20
|
-
return unless obj
|
20
|
+
return unless obj.bssid.match(/^([0-9a-f]{2}[:-]){5}[0-9a-f]{2}$/)
|
21
21
|
|
22
22
|
# Ignore probe requests as their BSSID information is useless (the ESSID
|
23
23
|
# isn't present and it's coming from a client).
|
24
24
|
return unless %w(infrastructure adhoc).include?(obj.type.to_s)
|
25
25
|
|
26
|
+
# Only create new access points if we're seeing it at a meaningful
|
27
|
+
# detection strength
|
28
|
+
return unless PatronusFati::DataModels::AccessPoint.exists?(obj.bssid) ||
|
29
|
+
obj.signal_dbm > PatronusFati::SIGNAL_THRESHOLD
|
30
|
+
|
26
31
|
ap_info = ap_data(obj.attributes)
|
27
32
|
|
28
33
|
access_point = PatronusFati::DataModels::AccessPoint[obj.bssid]
|
29
34
|
access_point.update(ap_info)
|
35
|
+
access_point.last_dbm = obj.signal_dbm if obj.signal_dbm
|
30
36
|
access_point.presence.mark_visible
|
31
37
|
access_point.announce_changes
|
32
38
|
|
@@ -3,8 +3,8 @@ module PatronusFati::MessageProcessor::Client
|
|
3
3
|
|
4
4
|
def self.client_data(attrs)
|
5
5
|
{
|
6
|
-
bssid:
|
7
|
-
channel:
|
6
|
+
bssid: attrs[:mac],
|
7
|
+
channel: attrs[:channel]
|
8
8
|
}.reject { |_, v| v.nil? }
|
9
9
|
end
|
10
10
|
|
@@ -36,10 +36,16 @@ module PatronusFati::MessageProcessor::Client
|
|
36
36
|
return if %w(unknown from_ds).include?(obj[:type]) &&
|
37
37
|
(!PatronusFati::DataModels::Client.exists?(obj[:mac]) || access_point.nil?)
|
38
38
|
|
39
|
+
# Only create new clients if we're seeing it at a meaningful detection
|
40
|
+
# strength
|
41
|
+
return unless PatronusFati::DataModels::Client.exists?(obj.bssid) ||
|
42
|
+
obj.signal_dbm > PatronusFati::SIGNAL_THRESHOLD
|
43
|
+
|
39
44
|
client_info = client_data(obj.attributes)
|
40
45
|
|
41
46
|
client = PatronusFati::DataModels::Client[obj[:mac]]
|
42
47
|
client.update(client_info)
|
48
|
+
client.last_dbm = obj.signal_dbm if obj.signal_dbm
|
43
49
|
client.presence.mark_visible
|
44
50
|
client.announce_changes
|
45
51
|
|
@@ -14,7 +14,7 @@ module PatronusFati::MessageProcessor::Ssid
|
|
14
14
|
access_point.track_ssid(ssid_info)
|
15
15
|
access_point.presence.mark_visible
|
16
16
|
access_point.announce_changes
|
17
|
-
elsif obj[:type] == 'probe_request'
|
17
|
+
elsif obj[:type] == 'probe_request' && !obj[:ssid].empty?
|
18
18
|
client = PatronusFati::DataModels::Client[obj[:mac]]
|
19
19
|
client.presence.mark_visible
|
20
20
|
client.track_probe(obj[:ssid])
|
@@ -38,6 +38,15 @@ RSpec.describe(PatronusFati::DataModels::Client) do
|
|
38
38
|
subject.announce_changes
|
39
39
|
end
|
40
40
|
|
41
|
+
it 'should emit no events when the client isn\'t worth sending up' do
|
42
|
+
expect(subject).to receive(:dirty?).and_return(true)
|
43
|
+
expect(subject).to receive(:valid?).and_return(true)
|
44
|
+
expect(subject).to receive(:worth_syncing?).and_return(false)
|
45
|
+
|
46
|
+
expect(PatronusFati.event_handler).to_not receive(:event)
|
47
|
+
subject.announce_changes
|
48
|
+
end
|
49
|
+
|
41
50
|
it 'should emit no events when the instance isn\'t dirty' do
|
42
51
|
expect(subject).to receive(:dirty?).and_return(false)
|
43
52
|
|
@@ -45,9 +54,10 @@ RSpec.describe(PatronusFati::DataModels::Client) do
|
|
45
54
|
subject.announce_changes
|
46
55
|
end
|
47
56
|
|
48
|
-
it 'should emit a new client event when dirty and
|
57
|
+
it 'should emit a new client event when dirty, unsynced and worth syncing' do
|
49
58
|
expect(subject).to receive(:dirty?).and_return(true)
|
50
59
|
expect(subject).to receive(:valid?).and_return(true)
|
60
|
+
expect(subject).to receive(:worth_syncing?).and_return(true)
|
51
61
|
subject.presence.mark_visible
|
52
62
|
|
53
63
|
expect(PatronusFati.event_handler)
|
@@ -61,6 +71,7 @@ RSpec.describe(PatronusFati::DataModels::Client) do
|
|
61
71
|
|
62
72
|
expect(subject).to receive(:dirty?).and_return(true)
|
63
73
|
expect(subject).to receive(:valid?).and_return(true)
|
74
|
+
expect(subject).to receive(:worth_syncing?).and_return(true)
|
64
75
|
|
65
76
|
expect(PatronusFati.event_handler)
|
66
77
|
.to receive(:event).with(:client, :changed, anything, anything)
|
@@ -74,6 +85,7 @@ RSpec.describe(PatronusFati::DataModels::Client) do
|
|
74
85
|
subject.presence.mark_visible
|
75
86
|
|
76
87
|
expect(subject).to receive(:valid?).and_return(true)
|
88
|
+
expect(subject).to receive(:worth_syncing?).and_return(true)
|
77
89
|
expect(PatronusFati.event_handler)
|
78
90
|
.to receive(:event).with(:client, :changed, anything, anything)
|
79
91
|
subject.announce_changes
|
@@ -85,6 +97,7 @@ RSpec.describe(PatronusFati::DataModels::Client) do
|
|
85
97
|
expect(subject.active?).to be_truthy
|
86
98
|
|
87
99
|
expect(subject).to receive(:valid?).and_return(true)
|
100
|
+
expect(subject).to receive(:worth_syncing?).and_return(true)
|
88
101
|
expect(subject).to receive(:active?).and_return(false).exactly(3).times
|
89
102
|
|
90
103
|
expect(PatronusFati.event_handler)
|
@@ -98,6 +111,7 @@ RSpec.describe(PatronusFati::DataModels::Client) do
|
|
98
111
|
expect(subject.active?).to be_truthy
|
99
112
|
|
100
113
|
expect(subject).to receive(:valid?).and_return(true)
|
114
|
+
expect(subject).to receive(:worth_syncing?).and_return(true)
|
101
115
|
expect(subject).to receive(:active?).and_return(false).exactly(3).times
|
102
116
|
|
103
117
|
expect { subject.announce_changes }
|
@@ -138,6 +152,7 @@ RSpec.describe(PatronusFati::DataModels::Client) do
|
|
138
152
|
|
139
153
|
it 'short not be dirty after being synced' do
|
140
154
|
expect(subject).to receive(:valid?).and_return(true)
|
155
|
+
expect(subject).to receive(:worth_syncing?).and_return(true)
|
141
156
|
subject.update(channel: 8)
|
142
157
|
|
143
158
|
expect { subject.announce_changes }.to change { subject.dirty? }.from(true).to(false)
|
@@ -151,6 +166,7 @@ RSpec.describe(PatronusFati::DataModels::Client) do
|
|
151
166
|
|
152
167
|
expect(subject).to receive(:dirty?).and_return(true)
|
153
168
|
expect(subject).to receive(:valid?).and_return(true)
|
169
|
+
expect(subject).to receive(:worth_syncing?).and_return(true)
|
154
170
|
expect(subject).to receive(:full_state).and_return(data_sample)
|
155
171
|
|
156
172
|
expect(PatronusFati.event_handler)
|
@@ -163,9 +179,10 @@ RSpec.describe(PatronusFati::DataModels::Client) do
|
|
163
179
|
subject.mark_synced
|
164
180
|
|
165
181
|
expect(subject).to receive(:valid?).and_return(true)
|
182
|
+
expect(subject).to receive(:worth_syncing?).and_return(true)
|
166
183
|
expect(subject).to receive(:active?).and_return(false).exactly(3).times
|
167
184
|
|
168
|
-
expect(subject.presence).to receive(:visible_time).and_return(1234)
|
185
|
+
expect(subject.presence).to receive(:visible_time).and_return(1234).twice
|
169
186
|
min_data = {
|
170
187
|
'bssid' => subject.local_attributes[:mac],
|
171
188
|
'uptime' => 1234
|
@@ -181,6 +198,7 @@ RSpec.describe(PatronusFati::DataModels::Client) do
|
|
181
198
|
|
182
199
|
expect(subject).to receive(:dirty?).and_return(true)
|
183
200
|
expect(subject).to receive(:valid?).and_return(true)
|
201
|
+
expect(subject).to receive(:worth_syncing?).and_return(true)
|
184
202
|
expect(subject).to receive(:diagnostic_data).and_return(sample_data)
|
185
203
|
|
186
204
|
expect(PatronusFati.event_handler)
|
@@ -235,7 +253,7 @@ RSpec.describe(PatronusFati::DataModels::Client) do
|
|
235
253
|
end
|
236
254
|
|
237
255
|
it 'should initialize the local attributes with the client\'s mac' do
|
238
|
-
expect(subject.local_attributes.keys).to eql([:mac])
|
256
|
+
expect(subject.local_attributes.keys).to eql([:channel, :mac])
|
239
257
|
expect(subject.local_attributes[:mac]).to_not be_nil
|
240
258
|
end
|
241
259
|
|
@@ -0,0 +1,91 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
def deep_diff(a, b)
|
6
|
+
(a.keys | b.keys).each_with_object({}) do |k, diff|
|
7
|
+
if a[k] != b[k]
|
8
|
+
if a[k].is_a?(Hash) && b[k].is_a?(Hash)
|
9
|
+
diff[k] = deep_diff(a[k], b[k])
|
10
|
+
else
|
11
|
+
diff[k] = [a[k], b[k]]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
diff
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
unless ARGV[0]
|
19
|
+
puts 'Must provide a file as the first argument...'
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
|
23
|
+
unless File.exists?(ARGV[0]) && File.readable?(ARGV[0])
|
24
|
+
puts 'Provided filename either doesn\'t exist or isn\'t readable.'
|
25
|
+
exit 2
|
26
|
+
end
|
27
|
+
|
28
|
+
message_breakdown = {
|
29
|
+
'access_point' => {},
|
30
|
+
'client' => {}
|
31
|
+
}
|
32
|
+
|
33
|
+
stats = {
|
34
|
+
relevant_messages: 0,
|
35
|
+
abberations: 0
|
36
|
+
}
|
37
|
+
|
38
|
+
file = File.open(ARGV[0])
|
39
|
+
file.each_line do |line|
|
40
|
+
msg = JSON.parse(line)
|
41
|
+
next if %w(alert both connection sync).include?(msg['asset_type'])
|
42
|
+
next if msg['event_type'] == 'sync'
|
43
|
+
|
44
|
+
stats[:relevant_messages] += 1
|
45
|
+
asset_type = msg['asset_type']
|
46
|
+
|
47
|
+
data = msg['data']
|
48
|
+
data['event_type'] = msg['event_type']
|
49
|
+
data['last_dbm'] = msg['diagnostics']['last_dbm']
|
50
|
+
#data['timestamp'] = msg['timestamp']
|
51
|
+
|
52
|
+
if data['ssids']
|
53
|
+
ssids = data.delete('ssids')
|
54
|
+
data['ssids'] = ssids.map do |s|
|
55
|
+
s.reject { |k, _| %w(last_visible).include?(k) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
bssid = data.delete('bssid')
|
60
|
+
|
61
|
+
message_breakdown[asset_type][bssid] ||= []
|
62
|
+
message_breakdown[asset_type][bssid] << data
|
63
|
+
end
|
64
|
+
file.close
|
65
|
+
|
66
|
+
changes = []
|
67
|
+
|
68
|
+
message_breakdown.each do |type, data|
|
69
|
+
data.each do |bssid, msgs|
|
70
|
+
next if msgs.count <= 2
|
71
|
+
stats[:abberations] += msgs.count
|
72
|
+
|
73
|
+
change_string = msgs.map { |m| m['event_type'][0] }.join
|
74
|
+
info = {
|
75
|
+
bssid: bssid,
|
76
|
+
msgs_count: msgs.count,
|
77
|
+
initial_state: msgs[0],
|
78
|
+
change_string: change_string,
|
79
|
+
deltas: []
|
80
|
+
}
|
81
|
+
|
82
|
+
1.upto(msgs.count - 1) do |i|
|
83
|
+
info[:deltas] << deep_diff(msgs[i - 1], msgs[i])
|
84
|
+
end
|
85
|
+
|
86
|
+
changes << info
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
puts JSON.pretty_generate(changes)
|
91
|
+
puts stats.inspect
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: patronus_fati
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Stelfox
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-02-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: louis
|
@@ -214,6 +214,7 @@ files:
|
|
214
214
|
- spec/patronus_fati_spec.rb
|
215
215
|
- spec/shared_examples/common_model_state.rb
|
216
216
|
- spec/spec_helper.rb
|
217
|
+
- tools/change_analysis.rb
|
217
218
|
homepage: ''
|
218
219
|
licenses:
|
219
220
|
- MIT
|