panopticon 0.1.0

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