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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0decb36cf31bb39312c3aafa0b363d0c445d62f9
4
- data.tar.gz: 28c87e8a494ccce26639b41a1a00b2cb1a9f9000
3
+ metadata.gz: d7cc61dae4e9a212fa42cdf646ff7520047c57f4
4
+ data.tar.gz: d5214c2c449b85771990ef81af25202f4b29c21c
5
5
  SHA512:
6
- metadata.gz: e92db4e69fb9cde5927add5c1f64b967a9bfe2984e3cf649d6557521fbbd4b91771d1fcdc8e68ff2b52367915a32aca16237f777e451cc7328c58bf6771dbe97
7
- data.tar.gz: d70418fb785ca6f3270c16dc470fa374e0dbe521992cc538fc1af6dabd8b22b2d6fc196107f61fafc1e98e2ee05205cd909e9994ff64e5b3633b4eb5e74349e3
6
+ metadata.gz: d12dabffaca480b0bb9e412173d37d64ba72a9adfcbeda9b2d36eae11c7efcc0c0288be3ae791e81b441c87041ceb36d04a9ca79085e6295f0c2a6128f64727d
7
+ data.tar.gz: e04be294d9b180b8794e872df31c0ec0d6fa8486abfb1c69386d34fe09ad754791bd17b20e1e4fe89753b7b10b903c05a8070f7a85a84b0b8c449b9bac299e19
@@ -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, :channel,
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 == "0.0.0.0") ? nil : 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[:lasttime] >= PatronusFati::DataModels::AccessPoint.current_expiration_threshold
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[:bssid].match(/^([0-9a-f]{2}[:-]){5}[0-9a-f]{2}$/)
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: attrs[:mac],
7
- channel: attrs[: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])
@@ -1,3 +1,3 @@
1
1
  module PatronusFati
2
- VERSION = '1.3.3'
2
+ VERSION = '1.3.4'
3
3
  end
@@ -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 unsynced' do
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.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: 2017-12-05 00:00:00.000000000 Z
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