File Coverage

blib/lib/Plack/App/OpenVPN/Status.pm
Criterion Covered Total %
statement 114 118 96.6
branch 40 54 74.0
condition 2 3 66.6
subroutine 14 14 100.0
pod 4 4 100.0
total 174 193 90.1


line stmt bran cond sub pod time code
1             package Plack::App::OpenVPN::Status;
2              
3             # ABSTRACT: Plack application to display the sessions of OpenVPN server
4              
5 3     3   357580 use strict;
  3         10  
  3         101  
6 3     3   20 use warnings;
  3         8  
  3         94  
7 3     3   18 use feature ':5.10';
  3         5  
  3         3148  
8              
9 3     3   17 use parent 'Plack::Component';
  3         5  
  3         17  
10 3     3   145 use Carp ();
  3         6  
  3         44  
11 3     3   4772 use Text::MicroTemplate;
  3         14641  
  3         158  
12 3     3   21 use Plack::Util::Accessor qw/renderer status_from custom_view/;
  3         5  
  3         28  
13              
14             our $VERSION = '0.16'; # VERSION
15             our $AUTHORITY = 'cpan:CHIM'; # AUTHORITY
16              
17             #
18             # default view (uses Twitter Bootstrap v2.x.x layout)
19             sub default_view {
20 8     8 1 20 <<'EOTMPL' }
21             % my $vars = $_[0];
22            
23            
24            
25            
26             OpenVPN Status
27            
28            
29            
30            
31            
32            
33            

Active OpenVPN Sessions Updated <%= $vars->{updated} %>

34            
35            
36            
37            

Status Version #<%= $vars->{version} %>

38             % if (scalar @{$vars->{users}}) {
39            
40            
41            
42             Virtual address
43             Common name
44             Remote IP (port)
45             Recv (from)
46             Xmit (to)
47             Connected since
48            
49            
50            
51             % for my $user (@{$vars->{users}}) {
52            
53             <%= $user->{'virtual'} %>
54             <%= $user->{'common-name'} %>
55             <%= $user->{'remote-ip'} %> (<%= $user->{'remote-port'} %>)
56             <%= $user->{'rx-bytes'} %>
57             <%= $user->{'tx-bytes'} %>
58             <%= $user->{'connected'} %>
59            
60             % }
61            
62            
63             % } else {
64            
65            

Attention!

66             There is no connected OpenVPN users.
67            
68             % }
69            
70            
71            
72            
73             EOTMPL
74              
75             #
76             # some preparations
77             sub prepare_app {
78 8     8 1 87355 my ($self) = @_;
79              
80 8         32 my $t_view = $self->default_view;
81              
82 8 100       39 if ($self->custom_view) {
83 2 100       78 if (ref($self->custom_view) eq 'CODE') {
84 1         8 $t_view = $self->custom_view->();
85             }
86             else {
87 1         233 Carp::croak "Parameter 'custom_view' must be a CODEREF";
88             }
89             }
90              
91             $self->renderer(
92 7         104 Text::MicroTemplate->new(
93             template => $t_view,
94             tag_start => '<%',
95             tag_end => '%>',
96             line_start => '%',
97             )->build
98             );
99             }
100              
101             #
102             # execute application
103             sub call {
104 7     7 1 55204 my ($self, $env) = @_;
105              
106 7         17 my ($body);
107              
108 7 100       36 unless ($self->status_from) {
109 1         9 $body = "Error: OpenVPN status file is not set!";
110             }
111             else {
112 6 100 66     66 unless (-e $self->status_from || -r _) {
113 1         36 $body = "Error: OpenVPN status file '" . $self->status_from . "' does not exist or unreadable!";
114             }
115             else {
116 5         183 $body = $self->renderer->($self->openvpn_status);
117             }
118             }
119              
120 7         682 [ 200, [ 'Content-Type' => 'text/html; charset=utf-8' ], [ $body ] ];
121             }
122              
123             #
124             # parse OpenVPN status log
125             sub openvpn_status {
126 5     5 1 11 my ($self) = @_;
127              
128 5         12 my $lines;
129              
130             {
131 5         8 local $/ = undef;
  5         27  
132 5 50       21 open STATUS, '<' . $self->status_from or Carp::croak "Cannot open '" . $self->status_from . "'";
133 5         334 $lines = ;
134 5         94 close STATUS;
135             }
136              
137 5         12 my ($st_ver, $delim, $sub);
138              
139             # guess status file version
140 5         11 given ($lines) {
141 5         22 when (/TITLE,/) {
142 1         3 $st_ver = 2;
143 1         2 $delim = ',';
144 1         4 $sub = \&_ovpn_status_v2_parse;
145             }
146 4         13 when (/TITLE\t/) {
147 1         2 $st_ver = 3;
148 1         3 $delim = '\t';
149 1         4 $sub = \&_ovpn_status_v2_parse;
150             }
151 3         8 default {
152 3         6 $st_ver = 1;
153 3         10 $delim = ',';
154 3         12 $sub = \&_ovpn_status_v1_parse;
155             }
156             }
157              
158 5         22 $sub->($lines, $delim, $st_ver);
159             }
160              
161             # octets formatter
162             # http://en.wikipedia.org/wiki/Octet_%28computing%29
163             sub _adaptive_octets {
164 8     8   14 my ($octets) = @_;
165              
166 8 50       62 if ($octets > 1152921504606846976) { # exbioctet (Eio) = 2^60 octets
    50          
    50          
    50          
    100          
    50          
167 0         0 $octets = sprintf('%.6f Eio', $octets/1152921504606846976);
168             }
169             elsif ($octets > 1125899906842624) { # pebioctet (Pio) = 2^50 octets
170 0         0 $octets = sprintf('%.5f Pio', $octets/1125899906842624);
171             }
172             elsif ($octets > 1099511627776) { # tebioctet (Tio) = 2^40 octets
173 0         0 $octets = sprintf('%.4f Tio', $octets/1099511627776);
174             }
175             elsif ($octets > 1073741824) { # gibioctet (Gio) = 2^30 octets
176 0         0 $octets = sprintf('%.3f Gio', $octets/1073741824);
177             }
178             elsif ($octets > 1048576) { # mebioctet (Mio) = 2^20 octets
179 7         62 $octets = sprintf('%.2f Mio', $octets/1048576);
180             }
181             elsif ($octets > 1024) { # kibioctet (Kio) = 2^10 octets
182 1         14 $octets = sprintf('%.1f Kio', $octets/1024);
183             }
184              
185 8         71 $octets;
186             };
187              
188             #
189             # OpenVPN status file format version #1 parser
190             sub _ovpn_status_v1_parse {
191 3     3   8 my ($lines, $delim, $version) = @_;
192              
193 3         7 my $vars = {};
194              
195 3         7 my ($users, $updated);
196              
197 3         23 for (split /\n/, $lines) {
198 28 50       81 next if /^$/;
199 28 100       128 next if /^(OpenVPN|ROUTING TABLE|GLOBAL STATS|Max bcast|END)/;
200              
201 13         99 my @line = split $delim, $_;
202 13         24 my $length = scalar(@line);
203              
204 13 100       52 $length == 2 && do {
205 3 50       18 next unless $line[0] =~ /^Updated/;
206 3         5 $updated = $line[1];
207 3         9 next;
208             };
209              
210 10 100       26 $length == 5 && do {
211 5 100       25 next if $line[0] =~ /^Common Name/;
212 2         7 my ($ip, $port) = split /:/, $line[1];
213 2         12 $users->{$line[0]} = {
214             'common-name' => $line[0],
215             'remote-ip' => $ip,
216             'remote-port' => $port,
217             'rx-bytes' => _adaptive_octets($line[2]),
218             'tx-bytes' => _adaptive_octets($line[3]),
219             'connected' => $line[4],
220             };
221 2         9 next;
222             };
223              
224 5 50       17 $length == 4 && do {
225 5 100       26 next if $line[0] =~ /^Virtual Address/;
226 2         6 $users->{$line[1]}->{'virtual'} = $line[0];
227 2         7 $users->{$line[1]}->{'last-ref'} = $line[3];
228 2         11 next;
229             };
230             }
231              
232             $vars = {
233 2         13 'version' => $version,
234             'updated' => $updated,
235 3         24 'users' => [ map { $users->{$_} } keys %$users ],
236             };
237              
238 3         24 $vars;
239             }
240              
241             #
242             # OpenVPN status file format version #2 and #3 parser
243             sub _ovpn_status_v2_parse {
244 2     2   5 my ($lines, $delim, $version) = @_;
245              
246 2         5 my $vars = {};
247              
248 2         3 my ($users, $updated);
249              
250 2         17 for (split /\n/, $lines) {
251 16 50       41 next if /^$/;
252 16 100       50 next if /^(TITLE|HEADER|GLOBAL_STATS|END)/;
253              
254 6         45 my @line = split $delim, $_;
255 6         11 my $length = scalar(@line);
256              
257 6 100       16 $length == 3 && do {
258 2 50       9 next unless $line[0] =~ /^TIME/;
259 2         4 $updated = $line[1];
260 2         6 next;
261             };
262              
263 4 100       10 $length == 8 && do {
264 2 50       9 next unless $line[0] =~ /^CLIENT_LIST/;
265 2         8 my ($ip, $port) = split /:/, $line[2];
266 2         11 $users->{$line[1]} = {
267             'common-name' => $line[1],
268             'remote-ip' => $ip,
269             'remote-port' => $port,
270             'rx-bytes' => _adaptive_octets($line[4]),
271             'tx-bytes' => _adaptive_octets($line[5]),
272             'connected' => $line[6],
273             };
274 2         7 next;
275             };
276              
277 2 50       8 $length == 6 && do {
278 2 50       9 next unless $line[0] =~ /^ROUTING_TABLE/;
279 2         5 $users->{$line[2]}->{'virtual'} = $line[1];
280 2         7 $users->{$line[2]}->{'last-ref'} = $line[4];
281 2         5 next;
282             };
283             }
284              
285             $vars = {
286 2         10 'version' => $version,
287             'updated' => $updated,
288 2         9 'users' => [ map { $users->{$_} } keys %$users ],
289             };
290              
291 2         12 $vars;
292             }
293              
294             1; # End of Plack::App::OpenVPN::Status
295              
296             __END__