panopticon 0.1.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.
@@ -0,0 +1,10 @@
1
+ require "panopticon/version"
2
+ require "panopticon/api"
3
+ require "panopticon/log"
4
+ require "panopticon/wlan_capture"
5
+ require "panopticon/wlan_control"
6
+ require "panopticon/wlan_utilization"
7
+ require "panopticon/command/panopticond"
8
+
9
+ module Panopticon
10
+ end
@@ -0,0 +1,59 @@
1
+ module Panopticon
2
+ require "sinatra/base"
3
+ require "json"
4
+
5
+ require "panopticon/wlan_control"
6
+
7
+ class APIServer < Sinatra::Base
8
+ DEFAULT_PORT=8080
9
+
10
+ set :port, DEFAULT_PORT
11
+ set :public_folder, File.dirname(__FILE__) + "/../../extra/public"
12
+ enable :logging
13
+
14
+ def self.run! arg={}
15
+ @arg = arg
16
+
17
+ @port = arg[:port] || DEFAULT_PORT
18
+ set :port, @port
19
+
20
+ @@wlan_control = Panopticon::WlanControl.new(arg)
21
+ super
22
+ end
23
+
24
+ get "/" do
25
+ redirect to ("/index.html")
26
+ end
27
+
28
+ get "/api/v1/status" do
29
+ JSON.dump(@@wlan_control.get_status)
30
+ end
31
+
32
+ post "/api/v1/start" do
33
+ begin
34
+ data = request.body.read
35
+ json = JSON.parse(data)
36
+
37
+ @@wlan_control.start_capture(json)
38
+ status 200
39
+ "success"
40
+ rescue => e
41
+ $log.err("/start failed => #{e}")
42
+ status 500
43
+ "failed (#{e})"
44
+ end
45
+ end
46
+
47
+ post "/api/v1/stop" do
48
+ begin
49
+ @@wlan_control.stop_capture
50
+ status 200
51
+ "success"
52
+ rescue => e
53
+ $log.err("/start failed => #{e}")
54
+ status 500
55
+ "failed (#{e})"
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,47 @@
1
+ module Panopticon
2
+ require "panopticon/log"
3
+
4
+ class Daemon
5
+ DEFAULT_CONFIG_PATH="/etc/panopticon.conf"
6
+
7
+ DEFAULT_API_PORT=8080
8
+
9
+ DEFAULT_IFNAME="wlan0"
10
+ DEFAULT_CAPTURE_PATH="/cap"
11
+ DEFAULT_LOG_FILE="/var/log/panopticond.log"
12
+
13
+ def self.default_options
14
+ {
15
+ # config file (exclusive)
16
+ :config_file => DEFAULT_CONFIG_PATH,
17
+
18
+ # daemon parameters
19
+ :port => DEFAULT_API_PORT,
20
+
21
+ # capture parameters
22
+ :ifname => DEFAULT_IFNAME,
23
+ :capture_path => DEFAULT_CAPTURE_PATH,
24
+
25
+ # log
26
+ :log_file => DEFAULT_LOG_FILE,
27
+ }
28
+ end
29
+
30
+ def initialize arg={}
31
+ @arg = arg
32
+
33
+ @config_file = arg[:config_file]
34
+
35
+ @arg = read_config(@config_file)
36
+ end
37
+
38
+ def run
39
+ Panopticon::APIServer.run!(@arg)
40
+ end
41
+
42
+ def read_config path
43
+ # notimp
44
+ @arg
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ class Log
2
+ require "logger"
3
+ def initialize opts={}
4
+ @debug_mode = opts[:debug_mode] || false
5
+ @output = opts[:output] || STDOUT
6
+
7
+ case @output
8
+ when "STDOUT"
9
+ @output = STDOUT
10
+ when "STDERR"
11
+ @output = STDERR
12
+ end
13
+ @logger = Logger.new(@output)
14
+
15
+ @logger.datetime_format = "%Y%m%d%H%m%S"
16
+ @logger.formatter = proc { |severity, datetime, progname, msg|
17
+ "[#{datetime}] #{progname}\t#{severity}: #{msg}\n"
18
+ }
19
+ end
20
+
21
+ def warn str
22
+ @logger.warn(str)
23
+ end
24
+
25
+ def err str
26
+ @logger.error(str)
27
+ end
28
+
29
+ def info str
30
+ @logger.info(str)
31
+ end
32
+
33
+ def debug str
34
+ @logger.debug(str)
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module Panopticon
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,135 @@
1
+ module Panopticon
2
+ require "open3"
3
+ require "panopticon/wlan_utilization"
4
+
5
+ class WlanCapture
6
+ attr_reader :duration, :current_channel, :filesize, :channel_walk, :utilization, :utilization_channel
7
+ CHAN = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
8
+ 34, 36, 38, 40, 42, 44, 46, 48,
9
+ 52, 56, 60, 64,
10
+ 100, 104, 108, 112, 116,
11
+ 120, 124, 128, 132, 136, 140,
12
+ 149, 153, 157, 161, 165]
13
+ LIMIT_IDX=CHAN.length
14
+
15
+ DEFAULT_IFNAME="wlan0"
16
+ DEFAULT_INTERVAL=1
17
+
18
+ def initialize ifname=DEFAULT_IFNAME, channels=CHAN, interval = DEFAULT_INTERVAL
19
+ @th_shark = nil
20
+ @th_capture = nil
21
+ @black_list = []
22
+
23
+ @ifname = ifname || DEFAULT_IFNAME
24
+ @channels = channels || CHAN
25
+ @interval = interval || DEFAULT_INTERVAL
26
+
27
+ init_status()
28
+
29
+ @wlan_utilization = Panopticon::WlanUtilization.new(@ifname)
30
+ end
31
+
32
+ def run_capture fpath
33
+ init_device()
34
+ init_status()
35
+ start_time = Time.now.to_i
36
+
37
+ stdin, stdout, stderr, @th_tshark = *Open3.popen3(
38
+ "tshark -i #{@ifname} -F pcapng -w #{fpath}")
39
+
40
+ while @th_tshark.alive?
41
+ sleep 1
42
+
43
+ # update status
44
+ @duration = Time.now.to_i - start_time
45
+ @filesize = File.size?(fpath) || 0
46
+
47
+ # do something here to run before channel transition
48
+ ary = @wlan_utilization.current_data()
49
+ @utilization_channel = ary[0]
50
+ @utilization = ary[3]
51
+
52
+ prev_channel = @current_channel
53
+ @current_chanenl = move_channel(@current_channel, @channels)
54
+ @channel_walk += 1
55
+ $log.debug("channel moved to #{@current_channel} from #{prev_channel} (dur=#{@duration}, size=#{@filesize}, walk=#{@channel_walk}, utilization=#{@utilization} uch=#{@utilization_channel})")
56
+ end
57
+ rescue => e
58
+ $log.warn("run_capture detected unknown error (#{e})")
59
+ end
60
+
61
+ def stop_capture
62
+ if @th_tshark == nil
63
+ $log.err("tried to kill tshark, but it's not executed? (or already dead?)")
64
+ return
65
+ end
66
+
67
+ Process.kill("INT", @th_tshark.pid)
68
+ end
69
+
70
+ def modify_channels channels
71
+ @channels = channels
72
+ end
73
+
74
+ private
75
+ def init_status
76
+ @duration = 0
77
+ @current_channel = @channels[0] || 1
78
+ @filesize = 0
79
+ @channel_walk = 0
80
+ end
81
+
82
+ def init_device
83
+ unless @ifname.match(/^wlan\d+$/)
84
+ $log.debug("non-wlan device skips device initialization")
85
+ return
86
+ end
87
+
88
+ unless system("ip link set #{@ifname} down")
89
+ raise "failed to turn down #{@ifname}"
90
+ end
91
+ unless system("iw #{@ifname} set monitor fcsfail otherbss control")
92
+ raise "failed to set #{@ifname} to monitor mode"
93
+ end
94
+ unless system("ip link set #{@ifname} up")
95
+ raise "failed to turn up #{@ifname}"
96
+ end
97
+ unless system("iw wlan0 set channel #{@current_channel}")
98
+ raise "failed to set channel #{@current_channel} on #{@ifname}"
99
+ end
100
+ end
101
+
102
+ def move_channel current, channels
103
+ unless @ifname.match(/^wlan\d+$/)
104
+ $log.debug("non-wlan device skips channel transition")
105
+ return
106
+ end
107
+
108
+ next_channel = pick_channel(current, channels)
109
+
110
+ while !system("iw #{@ifname} set channel #{next_channel}")
111
+ # what if we got unplugged ifname? => should we die?
112
+ $log.debug("channel transition failed, added to black list (channel=#{next_channel})")
113
+ @black_list << next_channel
114
+ sleep 1
115
+ next_channel = pick_channel(next_channel, channels)
116
+ end
117
+
118
+ @current_channel = next_channel
119
+ end
120
+
121
+ def pick_channel current, channels
122
+ idx = channels.index(current)
123
+ idx += 1
124
+ next_channel = channels[idx % channels.length] || 1
125
+
126
+ # we have black list
127
+ while @black_list.include?(next_channel)
128
+ idx += 1
129
+ next_channel = channels[idx % channels.length] || 1
130
+ end
131
+
132
+ return next_channel
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,275 @@
1
+ module Panopticon
2
+ require "json"
3
+ require "thread"
4
+
5
+ require "panopticon/log"
6
+ require "panopticon/wlan_capture"
7
+
8
+ class WlanControl
9
+ DEFAULT_IFNAME = "wlan0"
10
+ DEFAULT_CAPTURE_PATH = "/cap"
11
+
12
+ STATE_INIT = :INIT
13
+ STATE_RUNNING = :RUNNING
14
+ STATE_STOP = :STOP
15
+
16
+ def self.default_options
17
+ return {
18
+ :ifname => DEFAULT_IFNAME,
19
+ :capture_path => DEFAULT_CAPTURE_PATH,
20
+ }
21
+ end
22
+
23
+ def initialize args={}
24
+ @th_capture = nil
25
+ @wlan_capture = nil
26
+ @state = STATE_INIT
27
+
28
+ @ifname = args[:ifname] || DEFAULT_IFNAME
29
+ $log.debug("hogeeee #{@ifname} #{args[:ifname]}")
30
+ @capture_path = args[:capture_path] || DEFAULT_CAPTURE_PATH
31
+
32
+ @hostname = `hostname`
33
+ end
34
+
35
+ def disk_info
36
+ line = `df`.split("\n")[1].split # assumes first line to be "/"
37
+ size = line[1].to_i * 512
38
+ used = line[2].to_i * 512
39
+
40
+ return {:size => size, :used => used }
41
+ end
42
+
43
+ def memory_info
44
+ if File.exists?("/proc/meminfo")
45
+ # linux or bsd
46
+ return memory_info_from_proc
47
+ else
48
+ # mac os x ?
49
+ return memory_info_from_mac
50
+ end
51
+ end
52
+
53
+ def memory_info_from_proc
54
+ total = 0
55
+ free = 0
56
+ used = 0
57
+ File.open("/proc/meminfo") do |file|
58
+ total = file.gets.split[1].to_i
59
+ free = file.gets.split[1].to_i
60
+ used = total - free
61
+ end
62
+
63
+ return {:size => total, :used => used}
64
+ end
65
+
66
+ def memory_info_from_mac
67
+ _, total = `sysctl hw.memsize`.split.map{|n| n.to_i}
68
+
69
+ used = 0
70
+ app_mem = 0
71
+ comp_mem = 0
72
+ file_backed_mem = 0
73
+ `vm_stat`.split("\n").each do |line|
74
+ match = line.match(/^Anonymous pages: *(\d+)\.$/)
75
+ if match
76
+ app_mem = match[1].to_i * 4096
77
+ end
78
+
79
+ match = line.match(/^Pages occupied by compressor: *(\d+)\.$/)
80
+ if match
81
+ comp_mem = match[1].to_i * 4096
82
+ end
83
+
84
+ match = line.match(/^File-backed pages: *(\d+)\.$/)
85
+ if match
86
+ file_backed_mem = match[1].to_i * 4096
87
+ end
88
+ end
89
+
90
+ used = (app_mem + comp_mem + file_backed_mem)
91
+
92
+ return {:size => total, :used => used}
93
+ end
94
+
95
+ def cpu_info
96
+ if File.exists?("/proc/stat")
97
+ return cpu_info_from_proc
98
+ else
99
+ return cpu_info_from_mac
100
+ end
101
+ end
102
+
103
+ def cpu_info_from_proc
104
+ used = 0
105
+
106
+ current_cpu_info = []
107
+ File.open("/proc/stat") do |file|
108
+ current_cpu_info = file.gets.split[1..4].map{|elm| elm.to_i}
109
+ end
110
+
111
+ if @prev_cpu_info == nil
112
+ @prev_cpu_info = current_cpu_info
113
+ return 0
114
+ end
115
+
116
+ usage_sub = current_cpu_info[0..2].inject(0){|sum, elm| sum += elm} -
117
+ @prev_cpu_info[0..2].inject(0){|sum, elm| sum += elm}
118
+ total_sub = current_cpu_info.inject(0){|sum, elm| sum += elm} -
119
+ @prev_cpu_info.inject(0){|sum, elm| sum += elm}
120
+
121
+ @prev_cpu_info = current_cpu_info
122
+
123
+ used = ((usage_sub.to_f * 100) / total_sub)
124
+
125
+ return used
126
+ end
127
+
128
+ def cpu_info_from_mac
129
+ line = `top | head -4 | grep CPU`
130
+ match = line.match(/^CPU usage: *(\d+\.\d+)% user, *(\d+\.\d+)% sys, *(\d+\.\d+)% idle $/)
131
+ return 0.0 unless match
132
+
133
+ user = match[1].to_f
134
+ sys = match[2].to_f
135
+ idle = match[3].to_f
136
+ used = 100 - idle
137
+
138
+ return used
139
+ end
140
+
141
+ def capture_file_info
142
+ filesize = 0
143
+ duration = 0
144
+
145
+ if @wlan_capture
146
+ filesize = @wlan_capture.filesize
147
+ duration = @wlan_capture.duration
148
+ end
149
+
150
+ return {
151
+ :filename => @filename,
152
+ :filesize => filesize,
153
+ :duration => duration,
154
+ }
155
+ end
156
+
157
+ def wlan_info
158
+ info = {
159
+ :current_channel => 0,
160
+ :channel_walk => 0,
161
+ :utilization => 0,
162
+ :utilization_channel => 0,
163
+ }
164
+
165
+ unless @state == STATE_RUNNING
166
+ return info
167
+ end
168
+
169
+ if @wlan_capture.nil?
170
+ $log.err("state mismatch (state = #{@state} <=> capture is running");
171
+ return
172
+ end
173
+
174
+ info[:current_channel] = @wlan_capture.current_channel
175
+ info[:channel_walk] = @wlan_capture.channel_walk
176
+ info[:utilization] = @wlan_capture.utilization
177
+ info[:utilization_channel] = @wlan_capture.utilization_channel
178
+
179
+ return info
180
+ end
181
+
182
+ def generate_filename
183
+ return "#{Time.now.strftime("%Y%m%d%H%m%S")}_#{@ifname}_#{$$}.pcapng"
184
+ end
185
+
186
+ # request handler for API
187
+ def start_capture arg
188
+ $log.info("starting new capture")
189
+
190
+ if @state == STATE_RUNNING
191
+ $log.warn("WlanCapture instance is already running")
192
+ return
193
+ end
194
+
195
+ if @th_capture
196
+ $log.warn("capture thread is already running, terminate it first")
197
+ return
198
+ end
199
+
200
+ if @wlan_capture
201
+ $log.warn("capture instance is running, teminate it first")
202
+ return
203
+ end
204
+
205
+ $log.info("starting new capture instance")
206
+ @wlan_capture = Panopticon::WlanCapture.new(@ifname)
207
+
208
+ # start capturing
209
+ filename = generate_filename()
210
+ @th_capture = Thread.new do
211
+ @wlan_capture.run_capture("#{@capture_path}/#{filename}")
212
+ end
213
+ $log.info("started new capture at #{filename}")
214
+
215
+ # move state
216
+ @state = STATE_RUNNING
217
+ @filename = filename
218
+ end
219
+
220
+ def stop_capture
221
+ if @state == STATE_INIT or @state == STATE_STOP
222
+ $log.err("stopping not runnning capture instance")
223
+ return
224
+ end
225
+
226
+ if @wlan_capture
227
+ @wlan_capture.stop_capture
228
+ end
229
+
230
+ if @th_capture and @th_capture.alive?
231
+ $log.info("stopping capture thread")
232
+ @th_capture.join
233
+ end
234
+
235
+ @wlan_capture = nil
236
+ @th_capture = nil
237
+
238
+ @state = STATE_STOP
239
+ end
240
+
241
+ def change_channels arg
242
+ end
243
+
244
+ def get_config
245
+ end
246
+
247
+ def get_status
248
+ @cached_disk_info = disk_info()
249
+ @cached_memory_info = memory_info()
250
+ @cached_cpu_info = cpu_info()
251
+ @cached_file_info = capture_file_info()
252
+ @cached_wlan_info = wlan_info()
253
+
254
+ return {
255
+ :date => Time.now.to_i,
256
+ :hostname => @hostname,
257
+ :disk_size => @cached_disk_info[:size],
258
+ :disk_used => @cached_disk_info[:used],
259
+ :memory_size => @cached_memory_info[:size],
260
+ :memory_used => @cached_memory_info[:used],
261
+ :cpu_usage => @cached_cpu_info,
262
+
263
+ :state => @state,
264
+ :filename => @filename,
265
+ :filesize => @cached_file_info[:filesize],
266
+ :duration => @cached_file_info[:duration],
267
+
268
+ :current_channel => @cached_wlan_info[:current_channel],
269
+ :channel_walk => @cached_wlan_info[:channel_walk],
270
+ :utilization => @cached_wlan_info[:utilization],
271
+ :utilization_channel => @cached_wlan_info[:utilization_channel],
272
+ }
273
+ end
274
+ end
275
+ end