patronus_fati 1.3.3 → 1.3.4

Sign up to get free protection for your applications and to get access to all the features.
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