openvpn-status-web 0.0.1 → 1.0.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.
@@ -3,7 +3,6 @@ language: ruby
3
3
  rvm:
4
4
  - 2.0.0
5
5
  - 1.9.3
6
- - 1.8.7
7
6
 
8
7
  gemfile:
9
8
  - Gemfile
data/README.md CHANGED
@@ -1,17 +1,60 @@
1
1
  # openvpn-status-web
2
2
 
3
- Small (another word for naive in this case, it's simple and serves my needs) [rack](http://rack.github.com/) app
4
- providing the information the [OpenVPN](http://openvpn.net/index.php/open-source.html) server collects in it's status file
5
- especially including a list of currently connected clients (common name, remote address, traffic, ...).
6
- It comes with a Debian 6 compatible init.d file.
3
+ [![Build Status](https://travis-ci.org/cmur2/openvpn-status-web.png)](https://travis-ci.org/cmur2/openvpn-status-web)
4
+
5
+ ## Description
6
+
7
+ Small (another word for naive in this case, it's simple and serves my needs) [Rack](http://rack.github.com/) application providing the information the [OpenVPN](http://openvpn.net/index.php/open-source.html) server collects in it's status file especially including a list of currently connected clients (common name, remote address, traffic, ...).
7
8
 
8
9
  It lacks:
9
10
 
10
- * authentication
11
- * caching (parses file on each request, page does auto-refresh every minute as OpenVPN updates the status file these often)
11
+ * tracking multiple status at the same time
12
+ * newer status file versions than v1
13
+ * caching (parses file on each request, page does auto-refresh every minute as OpenVPN updates the status file these often by default)
12
14
  * management interface support
13
15
  * *possibly more...*
14
16
 
17
+ ## Usage
18
+
19
+ Install the gem:
20
+
21
+ gem install openvpn-status-web
22
+
23
+ Create a configuration file in YAML format somewhere:
24
+
25
+ ```yaml
26
+ # listen address and port
27
+ host: "0.0.0.0"
28
+ port: "8080"
29
+ # optional: drop priviliges in case you want to but you should give this user at least read access on the log files
30
+ user: "nobody"
31
+ group: "nogroup"
32
+ # logfile is optional, logs to STDOUT else
33
+ logfile: "openvpn-status-web.log"
34
+ # display name for humans and the status file path
35
+ name: "My Small VPN"
36
+ status_file: "/var/log/openvpn-status.log"
37
+ ```
38
+
39
+ Your OpenVPN configuration should contain something like this:
40
+
41
+ ```
42
+ # ...snip...
43
+ status /var/log/openvpn-status.log
44
+ status-version 1
45
+ # ...snip...
46
+ ```
47
+
48
+ ## Advanced topics
49
+
50
+ ### Authentication
51
+
52
+ If the information exposed is important to you serve it via the VPN or use a webserver as a proxy to handle SSL and/or HTTP authentication.
53
+
54
+ ### Init scripts
55
+
56
+ The [Debian 6 init.d script](init.d/debian-6-openvpn-status-web) assumes that openvpn-status-web is installed into the system ruby (no RVM support) and the config.yaml is at /opt/openvpn-status-web/config.yaml. Modify to your needs.
57
+
15
58
  ## License
16
59
 
17
60
  openvpn-statsu-web is licensed under the Apache License, Version 2.0. See LICENSE for more information.
@@ -0,0 +1,40 @@
1
+ #! /bin/sh
2
+ ### BEGIN INIT INFO
3
+ # Provides: openvpn-status-web
4
+ # Required-Start: $remote_fs $syslog
5
+ # Required-Stop: $remote_fs $syslog
6
+ # Default-Start: 2 3 4 5
7
+ # Default-Stop: 0 1 6
8
+ # Short-Description: Handle openvpn-status-web gem
9
+ ### END INIT INFO
10
+
11
+ # using the system ruby's gem binaries directory
12
+ DAEMON="/var/lib/gems/1.8/bin/openvpn-status-web"
13
+
14
+ CONFIG_FILE="/opt/openvpn-status-web/config.yaml"
15
+
16
+ DAEMON_OPTS="$CONFIG_FILE"
17
+
18
+ test -x $DAEMON || exit 0
19
+
20
+ . /lib/lsb/init-functions
21
+
22
+ case "$1" in
23
+ start)
24
+ log_daemon_msg "Starting openvpn-web-status" "openvpn-web-status"
25
+ start-stop-daemon --start --quiet --oknodo --make-pidfile --pidfile "/var/run/openvpn-web-status.pid" --background --exec $DAEMON -- $DAEMON_OPTS
26
+ ;;
27
+ stop)
28
+ log_daemon_msg "Stopping openvpn-web-status" "openvpn-web-status"
29
+ start-stop-daemon --stop --quiet --oknodo --pidfile "/var/run/openvpn-web-status.pid"
30
+ ;;
31
+ restart|force-reload)
32
+ log_daemon_msg "Restarting openvpn-web-status" "openvpn-web-status"
33
+ start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile "/var/run/openvpn-web-status.pid"
34
+ start-stop-daemon --start --quiet --oknodo --make-pidfile --pidfile "/var/run/openvpn-web-status.pid" --background --exec $DAEMON -- $DAEMON_OPTS
35
+ ;;
36
+ *)
37
+ echo "Usage: $0 {start|stop|restart|force-reload}" >&2
38
+ exit 1
39
+ ;;
40
+ esac
@@ -1,12 +1,19 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'date'
3
4
  require 'etc'
4
5
  require 'logger'
5
6
  require 'ipaddr'
6
7
  require 'yaml'
7
8
  require 'rack'
9
+ require 'erb'
8
10
  require 'metriks'
11
+ require 'better_errors'
9
12
 
13
+ require 'openvpn-status-web/status'
14
+ require 'openvpn-status-web/parser/v1'
15
+ require 'openvpn-status-web/parser/v2'
16
+ require 'openvpn-status-web/parser/v3'
10
17
  require 'openvpn-status-web/int_patch'
11
18
  require 'openvpn-status-web/version'
12
19
 
@@ -26,18 +33,25 @@ module OpenVPNStatusWeb
26
33
  end
27
34
 
28
35
  class Daemon
29
- def initialize(name, file)
30
- @name = name
31
- @file = file
36
+ def initialize(vpns)
37
+ @vpns = vpns
38
+
39
+ @main_tmpl = read_template(File.join(File.dirname(__FILE__), 'openvpn-status-web/main.html.erb'))
32
40
  end
33
41
 
34
42
  def call(env)
35
- main_tmpl = read_template(File.join(File.dirname(__FILE__), 'openvpn-status-web/main.html.erb'))
43
+ return [405, {"Content-Type" => "text/plain"}, ["Method Not Allowed"]] if env["REQUEST_METHOD"] != "GET"
44
+ return [404, {"Content-Type" => "text/plain"}, ["Not Found"]] if env["PATH_INFO"] != "/"
45
+
36
46
  # variables for template
37
- name = @name
38
- client_list, routing_table, global_stats = read_status_log(@file)
39
-
40
- html = main_tmpl.result(binding)
47
+ vpns = @vpns
48
+ stati = {}
49
+ @vpns.each do |name,config|
50
+ stati[name] = parse_status_log(config)
51
+ end
52
+ # eval
53
+ html = @main_tmpl.result(binding)
54
+
41
55
  [200, {"Content-Type" => "text/html"}, [html]]
42
56
  end
43
57
 
@@ -47,50 +61,67 @@ module OpenVPNStatusWeb
47
61
  ERB.new(text)
48
62
  end
49
63
 
50
- def read_status_log(file)
51
- text = File.open(file, 'rb') do |f| f.read end
52
-
53
- current_section = :none
54
- client_list = []
55
- routing_table = []
56
- global_stats = []
57
-
58
- text.lines.each do |line|
59
- (current_section = :cl; next) if line == "OpenVPN CLIENT LIST\n"
60
- (current_section = :rt; next) if line == "ROUTING TABLE\n"
61
- (current_section = :gs; next) if line == "GLOBAL STATS\n"
62
- (current_section = :end; next) if line == "END\n"
63
-
64
- case current_section
65
- when :cl then client_list << line.strip.split(',')
66
- when :rt then routing_table << line.strip.split(',')
67
- when :gs then global_stats << line.strip.split(',')
68
- end
64
+ def parse_status_log(vpn)
65
+ text = File.open(vpn['status_file'], 'rb') do |f| f.read end
66
+
67
+ case vpn['version']
68
+ when 1
69
+ OpenVPNStatusWeb::Parser::V1.new.parse_status_log(text)
70
+ when 2
71
+ OpenVPNStatusWeb::Parser::V2.new.parse_status_log(text)
72
+ when 3
73
+ OpenVPNStatusWeb::Parser::V3.new.parse_status_log(text)
74
+ else
75
+ raise "No suitable parser for status-version #{vpn['version']}"
69
76
  end
70
-
71
- [client_list[2..-1], routing_table[1..-1], global_stats]
72
77
  end
73
78
 
74
79
  def self.run!
75
- if ARGV.length != 4
76
- puts "Usage: openvpn-status-web vpn-name status-log listen-host listen-port"
80
+ if ARGV.length != 1
81
+ puts "Usage: openvpn-status-web config_file"
82
+ exit 1
83
+ end
84
+
85
+ config_file = ARGV[0]
86
+
87
+ if not File.file?(config_file)
88
+ puts "Config file not found!"
77
89
  exit 1
78
90
  end
91
+
92
+ puts "openvpn-status-web version #{OpenVPNStatusWeb::VERSION}"
93
+ puts "Using config file #{config_file}"
94
+
95
+ config = YAML::load(File.open(config_file, 'r') { |f| f.read })
79
96
 
80
- OpenVPNStatusWeb.logger = Logger.new(STDOUT)
97
+ if config['logfile']
98
+ OpenVPNStatusWeb.logger = Logger.new(config['logfile'])
99
+ else
100
+ OpenVPNStatusWeb.logger = Logger.new(STDOUT)
101
+ end
81
102
 
82
103
  OpenVPNStatusWeb.logger.progname = "openvpn-status-web"
83
104
  OpenVPNStatusWeb.logger.formatter = LogFormatter.new
84
105
 
85
106
  OpenVPNStatusWeb.logger.info "Starting..."
86
107
 
108
+ # drop privs (first change group than user)
109
+ Process::Sys.setgid(Etc.getgrnam(config['group']).gid) if config['group']
110
+ Process::Sys.setuid(Etc.getpwnam(config['user']).uid) if config['user']
111
+
112
+ # configure rack
113
+ app = Daemon.new(config['vpns'])
114
+ if ENV['RACK_ENV'] == "development"
115
+ app = BetterErrors::Middleware.new(app)
116
+ BetterErrors.application_root = File.expand_path("..", __FILE__)
117
+ end
118
+
87
119
  Signal.trap('INT') do
88
120
  OpenVPNStatusWeb.logger.info "Quitting..."
89
121
  Rack::Handler::WEBrick.shutdown
90
122
  end
91
-
92
- app = Daemon.new(ARGV[0], ARGV[1])
93
- Rack::Handler::WEBrick.run app, :Host => ARGV[2], :Port => ARGV[3]
123
+
124
+ Rack::Handler::WEBrick.run app, :Host => config['host'], :Port => config['port']
94
125
  end
95
126
  end
96
127
  end
@@ -42,67 +42,70 @@ thead {
42
42
  </head>
43
43
  <body>
44
44
 
45
- <h1>OpenVPN Status for <%= name %></h1>
45
+ <% vpns.each do |name,config| %>
46
+ <% status = stati[name] %>
47
+ <h1>OpenVPN Status for <%= name %></h1>
46
48
 
47
- <h2>Client List</h2>
48
- <div>
49
- <table>
50
- <thead>
51
- <td class="first">Common Name</td>
52
- <td class="middle">Real Address</td>
53
- <td class="middle">Data Received</td>
54
- <td class="middle">Data Sent</td>
55
- <td class="last">Connected Since</td>
56
- </thead>
57
- <tbody>
58
- <% client_list.each do |client| %>
59
- <tr>
60
- <td class="first"><%= client[0] %></td>
61
- <td class="middle"><%= client[1] %></td>
62
- <td class="middle"><%= client[2].to_i.as_bytes %></td>
63
- <td class="middle"><%= client[3].to_i.as_bytes %></td>
64
- <td class="last"><%= client[4] %></td>
65
- </tr>
49
+ <h2>Client List</h2>
50
+ <div>
51
+ <table>
52
+ <thead>
53
+ <td class="first">Common Name</td>
54
+ <td class="middle">Real Address</td>
55
+ <td class="middle">Data Received</td>
56
+ <td class="middle">Data Sent</td>
57
+ <td class="last">Connected Since</td>
58
+ </thead>
59
+ <tbody>
60
+ <% status.client_list.each do |client| %>
61
+ <tr>
62
+ <td class="first"><%= client[0] %></td>
63
+ <td class="middle"><%= client[1] %></td>
64
+ <td class="middle"><%= client[2].to_i.as_bytes %></td>
65
+ <td class="middle"><%= client[3].to_i.as_bytes %></td>
66
+ <td class="last"><%= client[4].strftime('%-d.%-m.%Y %H:%M:%S') %></td>
67
+ </tr>
66
68
  <% end %>
67
- </tbody>
68
- </table>
69
- </div>
69
+ </tbody>
70
+ </table>
71
+ </div>
70
72
 
71
- <h2>Routing Table</h2>
72
- <div>
73
- <table>
74
- <thead>
75
- <td class="first">Virtual Address</td>
76
- <td class="middle">Common Name</td>
77
- <td class="middle">Real Address</td>
78
- <td class="last">Last Ref</td>
79
- </thead>
80
- <tbody>
81
- <% routing_table.each do |e| %>
82
- <tr>
83
- <td class="first"><%= e[0] %></td>
84
- <td class="middle"><%= e[1] %></td>
85
- <td class="middle"><%= e[2] %></td>
86
- <td class="last"><%= e[3] %></td>
87
- </tr>
73
+ <h2>Routing Table</h2>
74
+ <div>
75
+ <table>
76
+ <thead>
77
+ <td class="first">Virtual Address</td>
78
+ <td class="middle">Common Name</td>
79
+ <td class="middle">Real Address</td>
80
+ <td class="last">Last Ref</td>
81
+ </thead>
82
+ <tbody>
83
+ <% status.routing_table.each do |route| %>
84
+ <tr>
85
+ <td class="first"><%= route[0] %></td>
86
+ <td class="middle"><%= route[1] %></td>
87
+ <td class="middle"><%= route[2] %></td>
88
+ <td class="last"><%= route[3].strftime('%-d.%-m.%Y %H:%M:%S') %></td>
89
+ </tr>
88
90
  <% end %>
89
- </tbody>
90
- </table>
91
- </div>
91
+ </tbody>
92
+ </table>
93
+ </div>
92
94
 
93
- <h2>Global Stats</h2>
94
- <div>
95
- <table>
96
- <tbody>
97
- <% global_stats.each do |e| %>
98
- <tr>
99
- <td><%= e[0] %>:</td>
100
- <td><%= e[1] %></td>
101
- </tr>
95
+ <h2>Global Stats</h2>
96
+ <div>
97
+ <table>
98
+ <tbody>
99
+ <% status.global_stats.each do |global| %>
100
+ <tr>
101
+ <td><%= global[0] %>:</td>
102
+ <td><%= global[1] %></td>
103
+ </tr>
104
+ <% end %>
105
+ </tbody>
106
+ </table>
107
+ </div>
102
108
  <% end %>
103
- </tbody>
104
- </table>
105
- </div>
106
109
 
107
110
  </body>
108
111
  </html>
@@ -0,0 +1,41 @@
1
+
2
+ module OpenVPNStatusWeb
3
+ module Parser
4
+ class ModernStateless
5
+ def self.parse_status_log(text, sep)
6
+ status = Status.new
7
+ status.client_list = []
8
+ status.routing_table = []
9
+ status.global_stats = []
10
+
11
+ text.lines.each do |line|
12
+ parts = line.strip.split(sep)
13
+ status.client_list << parse_client(parts[1..5]) if parts[0] == "CLIENT_LIST"
14
+ status.routing_table << parse_route(parts[1..4]) if parts[0] == "ROUTING_TABLE"
15
+ status.global_stats << parse_global(parts[1..2]) if parts[0] == "GLOBAL_STATS"
16
+ end
17
+
18
+ status
19
+ end
20
+
21
+ private
22
+
23
+ def self.parse_client(client)
24
+ client[2] = client[2].to_i
25
+ client[3] = client[3].to_i
26
+ client[4] = DateTime.strptime(client[4], '%a %b %d %k:%M:%S %Y')
27
+ client
28
+ end
29
+
30
+ def self.parse_route(route)
31
+ route[3] = DateTime.strptime(route[3], '%a %b %d %k:%M:%S %Y')
32
+ route
33
+ end
34
+
35
+ def self.parse_global(global)
36
+ global[1] = global[1].to_i
37
+ global
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,54 @@
1
+
2
+ module OpenVPNStatusWeb
3
+ module Parser
4
+ class V1
5
+ def parse_status_log(text)
6
+ current_section = :none
7
+ client_list = []
8
+ routing_table = []
9
+ global_stats = []
10
+
11
+ text.lines.each do |line|
12
+ (current_section = :cl; next) if line == "OpenVPN CLIENT LIST\n"
13
+ (current_section = :rt; next) if line == "ROUTING TABLE\n"
14
+ (current_section = :gs; next) if line == "GLOBAL STATS\n"
15
+ (current_section = :end; next) if line == "END\n"
16
+
17
+ case current_section
18
+ when :cl
19
+ client_list << line.strip.split(',')
20
+ when :rt
21
+ routing_table << line.strip.split(',')
22
+ when :gs
23
+ global_stats << line.strip.split(',')
24
+ end
25
+ end
26
+
27
+ status = Status.new
28
+ status.client_list = client_list[2..-1].map { |client| parse_client(client) }
29
+ status.routing_table = routing_table[1..-1].map { |route| parse_route(route) }
30
+ status.global_stats = global_stats.map { |global| parse_global(global) }
31
+ status
32
+ end
33
+
34
+ private
35
+
36
+ def parse_client(client)
37
+ client[2] = client[2].to_i
38
+ client[3] = client[3].to_i
39
+ client[4] = DateTime.strptime(client[4], '%a %b %d %k:%M:%S %Y')
40
+ client
41
+ end
42
+
43
+ def parse_route(route)
44
+ route[3] = DateTime.strptime(route[3], '%a %b %d %k:%M:%S %Y')
45
+ route
46
+ end
47
+
48
+ def parse_global(global)
49
+ global[1] = global[1].to_i
50
+ global
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,12 @@
1
+
2
+ require 'openvpn-status-web/parser/modern_stateless'
3
+
4
+ module OpenVPNStatusWeb
5
+ module Parser
6
+ class V2
7
+ def parse_status_log(text)
8
+ OpenVPNStatusWeb::Parser::ModernStateless.parse_status_log(text, ',')
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+
2
+ require 'openvpn-status-web/parser/modern_stateless'
3
+
4
+ module OpenVPNStatusWeb
5
+ module Parser
6
+ class V3
7
+ def parse_status_log(text)
8
+ OpenVPNStatusWeb::Parser::ModernStateless.parse_status_log(text, "\t")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+
2
+ module OpenVPNStatusWeb
3
+ class Status
4
+ attr_accessor :client_list
5
+ attr_accessor :routing_table
6
+ attr_accessor :global_stats
7
+ end
8
+ end
@@ -1,4 +1,4 @@
1
1
 
2
2
  module OpenVPNStatusWeb
3
- VERSION = "0.0.1"
3
+ VERSION = "1.0.0"
4
4
  end
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
7
7
  s.name = 'openvpn-status-web'
8
8
  s.version = OpenVPNStatusWeb::VERSION
9
9
  s.summary = 'openvpn-status-web'
10
- s.description = 'Small Rack application that parses and serves the OpenVPN status file.'
10
+ s.description = 'Small Rack (Ruby) application serving OpenVPN status file.'
11
11
  s.author = 'Christian Nicolai'
12
12
  s.email = 'chrnicolai@gmail.com'
13
13
  s.license = 'Apache License Version 2.0'
@@ -21,11 +21,12 @@ Gem::Specification.new do |s|
21
21
  s.executables = ['openvpn-status-web']
22
22
 
23
23
  s.add_runtime_dependency 'rack'
24
- s.add_runtime_dependency 'json'
25
24
  s.add_runtime_dependency 'metriks'
26
25
 
27
26
  s.add_development_dependency 'bundler', '~> 1.3'
28
27
  s.add_development_dependency 'rake'
29
28
  s.add_development_dependency 'rspec'
30
29
  s.add_development_dependency 'rack-test'
30
+ s.add_development_dependency 'better_errors'
31
+ s.add_development_dependency 'binding_of_caller'
31
32
  end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe OpenVPNStatusWeb::Parser::ModernStateless do
4
+ {
5
+ 2 => status_v2,
6
+ 3 => status_v3
7
+ }.each do |version, status|
8
+ context "for status-version #{version}" do
9
+ context 'for client list' do
10
+ it 'parses common names' do
11
+ status.client_list.map { |client| client[0] }.should be == ["foo", "bar"]
12
+ end
13
+
14
+ it 'parses real addresses' do
15
+ status.client_list.map { |client| client[1] }.should be == ["1.2.3.4:1234", "1.2.3.5:1235"]
16
+ end
17
+
18
+ it 'parses received bytes' do
19
+ status.client_list.map { |client| client[2] }.should be == [11811160064, 512]
20
+ end
21
+
22
+ it 'parses sent bytes' do
23
+ status.client_list.map { |client| client[3] }.should be == [4194304, 2048]
24
+ end
25
+
26
+ it 'parses connected since date' do
27
+ status.client_list.map { |client| client[4] }.should be == [DateTime.new(2012,1,1,23,42,0), DateTime.new(2012,1,1,23,42,0)]
28
+ end
29
+ end
30
+
31
+ context 'for routing table' do
32
+ it 'parses virtual addresses' do
33
+ status.routing_table.map { |route| route[0] }.should be == ["192.168.0.0/24", "192.168.66.2", "192.168.66.3", "2001:db8:0:0::1000"]
34
+ end
35
+
36
+ it 'parses common names' do
37
+ status.routing_table.map { |route| route[1] }.should be == ["foo", "bar", "foo", "bar"]
38
+ end
39
+
40
+ it 'parses real addresses' do
41
+ status.routing_table.map { |route| route[2] }.should be == ["1.2.3.4:1234", "1.2.3.5:1235", "1.2.3.4:1234", "1.2.3.5:1235"]
42
+ end
43
+
44
+ it 'parses last ref date' do
45
+ status.routing_table.map { |route| route[3] }.should be == [DateTime.new(2012,1,1,23,42,0), DateTime.new(2012,1,1,23,42,0), DateTime.new(2012,1,1,23,42,0), DateTime.new(2012,1,1,23,42,0)]
46
+ end
47
+ end
48
+
49
+ it 'parses global stats' do
50
+ status.global_stats.size.should be == 1
51
+ status.global_stats.first.should be == ["Max bcast/mcast queue length", 42]
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe OpenVPNStatusWeb::Parser::V1 do
4
+ def status; status_v1; end
5
+
6
+ context 'for client list' do
7
+ it 'parses common names' do
8
+ status.client_list.map { |client| client[0] }.should be == ["foo", "bar"]
9
+ end
10
+
11
+ it 'parses real addresses' do
12
+ status.client_list.map { |client| client[1] }.should be == ["1.2.3.4:1234", "1.2.3.5:1235"]
13
+ end
14
+
15
+ it 'parses received bytes' do
16
+ status.client_list.map { |client| client[2] }.should be == [11811160064, 512]
17
+ end
18
+
19
+ it 'parses sent bytes' do
20
+ status.client_list.map { |client| client[3] }.should be == [4194304, 2048]
21
+ end
22
+
23
+ it 'parses connected since date' do
24
+ status.client_list.map { |client| client[4] }.should be == [DateTime.new(2012,1,1,23,42,0), DateTime.new(2012,1,1,23,42,0)]
25
+ end
26
+ end
27
+
28
+ context 'for routing table' do
29
+ it 'parses virtual addresses' do
30
+ status.routing_table.map { |route| route[0] }.should be == ["192.168.0.0/24", "192.168.66.2", "192.168.66.3", "2001:db8:0:0::1000"]
31
+ end
32
+
33
+ it 'parses common names' do
34
+ status.routing_table.map { |route| route[1] }.should be == ["foo", "bar", "foo", "bar"]
35
+ end
36
+
37
+ it 'parses real addresses' do
38
+ status.routing_table.map { |route| route[2] }.should be == ["1.2.3.4:1234", "1.2.3.5:1235", "1.2.3.4:1234", "1.2.3.5:1235"]
39
+ end
40
+
41
+ it 'parses last ref date' do
42
+ status.routing_table.map { |route| route[3] }.should be == [DateTime.new(2012,1,1,23,42,0), DateTime.new(2012,1,1,23,42,0), DateTime.new(2012,1,1,23,42,0), DateTime.new(2012,1,1,23,42,0)]
43
+ end
44
+ end
45
+
46
+ it 'parses global stats' do
47
+ status.global_stats.size.should be == 1
48
+ status.global_stats.first.should be == ["Max bcast/mcast queue length", 42]
49
+ end
50
+ end
@@ -0,0 +1,21 @@
1
+
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+ require 'rack/test'
5
+
6
+ require 'openvpn-status-web'
7
+
8
+ def status_v1
9
+ text = File.open('examples/status.v1', 'rb') do |f| f.read end
10
+ OpenVPNStatusWeb::Parser::V1.new.parse_status_log text
11
+ end
12
+
13
+ def status_v2
14
+ text = File.open('examples/status.v2', 'rb') do |f| f.read end
15
+ OpenVPNStatusWeb::Parser::V2.new.parse_status_log text
16
+ end
17
+
18
+ def status_v3
19
+ text = File.open('examples/status.v3', 'rb') do |f| f.read end
20
+ OpenVPNStatusWeb::Parser::V3.new.parse_status_log text
21
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openvpn-status-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2013-05-03 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
16
- requirement: &70153082865000 !ruby/object:Gem::Requirement
16
+ requirement: &70153409956960 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,21 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70153082865000
25
- - !ruby/object:Gem::Dependency
26
- name: json
27
- requirement: &70153082864580 !ruby/object:Gem::Requirement
28
- none: false
29
- requirements:
30
- - - ! '>='
31
- - !ruby/object:Gem::Version
32
- version: '0'
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: *70153082864580
24
+ version_requirements: *70153409956960
36
25
  - !ruby/object:Gem::Dependency
37
26
  name: metriks
38
- requirement: &70153082864160 !ruby/object:Gem::Requirement
27
+ requirement: &70153409956540 !ruby/object:Gem::Requirement
39
28
  none: false
40
29
  requirements:
41
30
  - - ! '>='
@@ -43,10 +32,10 @@ dependencies:
43
32
  version: '0'
44
33
  type: :runtime
45
34
  prerelease: false
46
- version_requirements: *70153082864160
35
+ version_requirements: *70153409956540
47
36
  - !ruby/object:Gem::Dependency
48
37
  name: bundler
49
- requirement: &70153082863660 !ruby/object:Gem::Requirement
38
+ requirement: &70153409956040 !ruby/object:Gem::Requirement
50
39
  none: false
51
40
  requirements:
52
41
  - - ~>
@@ -54,10 +43,10 @@ dependencies:
54
43
  version: '1.3'
55
44
  type: :development
56
45
  prerelease: false
57
- version_requirements: *70153082863660
46
+ version_requirements: *70153409956040
58
47
  - !ruby/object:Gem::Dependency
59
48
  name: rake
60
- requirement: &70153082863240 !ruby/object:Gem::Requirement
49
+ requirement: &70153409955620 !ruby/object:Gem::Requirement
61
50
  none: false
62
51
  requirements:
63
52
  - - ! '>='
@@ -65,10 +54,10 @@ dependencies:
65
54
  version: '0'
66
55
  type: :development
67
56
  prerelease: false
68
- version_requirements: *70153082863240
57
+ version_requirements: *70153409955620
69
58
  - !ruby/object:Gem::Dependency
70
59
  name: rspec
71
- requirement: &70153082862780 !ruby/object:Gem::Requirement
60
+ requirement: &70153409955160 !ruby/object:Gem::Requirement
72
61
  none: false
73
62
  requirements:
74
63
  - - ! '>='
@@ -76,10 +65,32 @@ dependencies:
76
65
  version: '0'
77
66
  type: :development
78
67
  prerelease: false
79
- version_requirements: *70153082862780
68
+ version_requirements: *70153409955160
80
69
  - !ruby/object:Gem::Dependency
81
70
  name: rack-test
82
- requirement: &70153082862360 !ruby/object:Gem::Requirement
71
+ requirement: &70153409954740 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70153409954740
80
+ - !ruby/object:Gem::Dependency
81
+ name: better_errors
82
+ requirement: &70153409954320 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70153409954320
91
+ - !ruby/object:Gem::Dependency
92
+ name: binding_of_caller
93
+ requirement: &70153409953900 !ruby/object:Gem::Requirement
83
94
  none: false
84
95
  requirements:
85
96
  - - ! '>='
@@ -87,8 +98,8 @@ dependencies:
87
98
  version: '0'
88
99
  type: :development
89
100
  prerelease: false
90
- version_requirements: *70153082862360
91
- description: Small Rack application that parses and serves the OpenVPN status file.
101
+ version_requirements: *70153409953900
102
+ description: Small Rack (Ruby) application serving OpenVPN status file.
92
103
  email: chrnicolai@gmail.com
93
104
  executables:
94
105
  - openvpn-status-web
@@ -105,12 +116,20 @@ files:
105
116
  - examples/status.v1
106
117
  - examples/status.v2
107
118
  - examples/status.v3
108
- - init.d/openvpn-status-web
119
+ - init.d/debian-6-openvpn-status-web
109
120
  - lib/openvpn-status-web.rb
110
121
  - lib/openvpn-status-web/int_patch.rb
111
122
  - lib/openvpn-status-web/main.html.erb
123
+ - lib/openvpn-status-web/parser/modern_stateless.rb
124
+ - lib/openvpn-status-web/parser/v1.rb
125
+ - lib/openvpn-status-web/parser/v2.rb
126
+ - lib/openvpn-status-web/parser/v3.rb
127
+ - lib/openvpn-status-web/status.rb
112
128
  - lib/openvpn-status-web/version.rb
113
129
  - openvpn-status-web.gemspec
130
+ - spec/parser/modern_stateless_spec.rb
131
+ - spec/parser/v1_spec.rb
132
+ - spec/spec_helper.rb
114
133
  homepage: https://github.com/cmur2/openvpn-status-web
115
134
  licenses:
116
135
  - Apache License Version 2.0
@@ -136,4 +155,7 @@ rubygems_version: 1.8.6
136
155
  signing_key:
137
156
  specification_version: 3
138
157
  summary: openvpn-status-web
139
- test_files: []
158
+ test_files:
159
+ - spec/parser/modern_stateless_spec.rb
160
+ - spec/parser/v1_spec.rb
161
+ - spec/spec_helper.rb
@@ -1,48 +0,0 @@
1
- #! /bin/sh
2
- ### BEGIN INIT INFO
3
- # Provides: openvpn-status-web
4
- # Required-Start: $remote_fs $syslog
5
- # Required-Stop: $remote_fs $syslog
6
- # Default-Start: 2 3 4 5
7
- # Default-Stop: 0 1 6
8
- # Short-Description: Handle openvpn-status-web
9
- ### END INIT INFO
10
-
11
- # your ruby interpreter
12
- DAEMON="/usr/bin/ruby"
13
-
14
- # some unique name identifying your VPN
15
- VPN_NAME="vpn.example.org"
16
-
17
- # path to the OpenVPN status log file
18
- STATUS_PATH="/var/log/openvpn-status.log"
19
-
20
- # host and port for this daemon to listen on
21
- HOST="127.0.0.1"
22
- PORT="3000"
23
-
24
- DAEMON_OPTS="/opt/openvpn-status-web/status.rb $VPN_NAME $STATUS_PATH $HOST $PORT"
25
-
26
- test -x $DAEMON || exit 0
27
-
28
- . /lib/lsb/init-functions
29
-
30
- case "$1" in
31
- start)
32
- log_daemon_msg "Starting openvpn-web-status for $VPN_NAME" "openvpn-web-status"
33
- start-stop-daemon --start --quiet --oknodo --make-pidfile --pidfile "/var/run/$VPN_NAME.pid" --background --exec $DAEMON -- $DAEMON_OPTS
34
- ;;
35
- stop)
36
- log_daemon_msg "Stopping openvpn-web-status for $VPN_NAME" "openvpn-web-status"
37
- start-stop-daemon --stop --quiet --oknodo --pidfile "/var/run/$VPN_NAME.pid"
38
- ;;
39
- restart|force-reload)
40
- log_daemon_msg "Restarting openvpn-web-status for $VPN_NAME" "openvpn-web-status"
41
- start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile "/var/run/$VPN_NAME.pid"
42
- start-stop-daemon --start --quiet --oknodo --make-pidfile --pidfile "/var/run/$VPN_NAME.pid" --background --exec $DAEMON -- $DAEMON_OPTS
43
- ;;
44
- *)
45
- echo "Usage: $0 {start|stop|restart|force-reload}" >&2
46
- exit 1
47
- ;;
48
- esac