File Coverage

blib/lib/AnyEvent/Radius/Server.pm
Criterion Covered Total %
statement 30 71 42.2
branch 0 20 0.0
condition 0 8 0.0
subroutine 10 14 71.4
pod 2 2 100.0
total 42 115 36.5


line stmt bran cond sub pod time code
1             package AnyEvent::Radius::Server;
2             # AnyEvent-based radius server
3 1     1   1389 use strict;
  1         2  
  1         29  
4 1     1   3 use warnings;
  1         2  
  1         59  
5 1     1   3 use AnyEvent;
  1         2  
  1         16  
6 1     1   4 use AnyEvent::Handle::UDP;
  1         1  
  1         21  
7              
8 1     1   4 use base qw(Class::Accessor::Fast);
  1         1  
  1         188  
9             __PACKAGE__->mk_accessors(qw(handler packer));
10              
11 1     1   5 use Data::Radius v1.2.8;
  1         16  
  1         20  
12 1     1   3 use Data::Radius::Constants qw(:all);
  1         1  
  1         204  
13 1     1   4 use Data::Radius::Dictionary ();
  1         1  
  1         11  
14 1     1   2 use Data::Radius::Packet ();
  1         1  
  1         23  
15              
16             use constant {
17 1         548 READ_TIMEOUT_SEC => 5,
18             WRITE_TIMEOUT_SEC => 5,
19             RADIUS_PORT => 1812,
20 1     1   3 };
  1         2  
21              
22             my %DEFAUL_REPLY = (
23             &ACCESS_REQUEST => ACCESS_REJECT,
24             &ACCOUNTING_REQUEST => ACCOUNTING_RESPONSE,
25             &DISCONNECT_REQUEST => DISCONNECT_REJECT,
26             &COA_REQUEST => COA_REJECT,
27             );
28              
29             # new 'server'
30             # args:
31             # ip
32             # port
33             # secret
34             # dictionary
35             #- callbacks:
36             # on_read
37             # on_read_raw
38             # on_wrong_request
39             # on_error
40             sub new {
41 0     0 1   my ($class, %h) = @_;
42              
43 0 0         die "No IP argument" if (! $h{ip});
44             # either pre-created packer obect, or need radius secret to create new one
45             # dictionary is optional
46 0 0 0       die "No radius secret" if (! $h{packer} && ! $h{secret});
47              
48 0           my $obj = bless {}, $class;
49              
50             my $on_read_cb = sub {
51 0     0     my ($data, $handle, $from) = @_;
52              
53 0 0         if ($h{on_read_raw}) {
54             # dump raw data
55 0           $h{on_read_raw}->($obj, $data, $from);
56             }
57              
58             # how to decoded $from
59             # my($port, $host) = AnyEvent::Socket::unpack_sockaddr($from);
60             # my $ip = format_ipv4($host);
61              
62 0           my ($type, $req_id, $authenticator, $av_list) = $obj->packer()->parse($data);
63              
64 0 0         if (! $obj->packer()->is_request($type)) {
65             # we expect only requests in server
66 0 0         if ($h{on_wrong_request}) {
67 0           $h{on_wrong_request}->($obj, {
68             type => $type,
69             request_id => $req_id,
70             av_list => $av_list,
71             # from is sockaddr binary data
72             from => $from,
73             });
74             }
75              
76             # Do not reply
77 0           warn "Ignore wrong request type " . $type;
78             return
79 0           }
80              
81 0           my ($reply_type, $reply_av_list) = ();
82              
83 0 0         if($h{on_read}) {
84             # custom-reply
85 0           ($reply_type, $reply_av_list) = $h{on_read}->($obj, {
86             type => $type,
87             request_id => $req_id,
88             av_list => $av_list,
89             # from is sockaddr binary data
90             from => $from,
91             });
92             }
93              
94 0 0         if (! $reply_type) {
95             # reject by default
96 0           $reply_type = $DEFAUL_REPLY{ $type };
97 0           $reply_av_list = [{Name => 'Reply-Message', Value => 'Default rule: reject'}];
98             }
99              
100 0           my ($reply, $r_id, $r_auth) = $obj->packer()->build(
101             type => $reply_type,
102             av_list => $reply_av_list,
103             authenticator => $authenticator,
104             request_id => $req_id,
105             );
106 0 0         if(! $reply) {
107 0           warn "Failed to build reply";
108             return
109 0           }
110              
111 0           $obj->handler()->push_send($reply, $from);
112              
113 0           return;
114 0           };
115              
116             # low-level socket errors
117             my $on_error_cb = sub {
118 0     0     my ($handle, $fatal, $error) = @_;
119 0 0         if ($h{on_error}) {
120 0           $h{on_error}->($obj, $error);
121             }
122             else {
123 0           warn "Error occured: $error";
124             }
125 0           };
126              
127             my $server = AnyEvent::Handle::UDP->new(
128 0   0       bind => [$h{ip}, $h{port} // RADIUS_PORT ],
129             on_recv => $on_read_cb,
130             on_error => $on_error_cb,
131             );
132 0           $obj->handler($server);
133              
134             # allow to pass custom object
135 0   0       my $packer = $h{packer} || Data::Radius::Packet->new(dict => $h{dictionary}, secret => $h{secret});
136 0           $obj->packer($packer);
137              
138 0           return $obj;
139             }
140              
141             sub load_dictionary {
142 0     0 1   my ($class, $path) = @_;
143 0           my $dict = Data::Radius::Dictionary->load_file($path);
144              
145 0 0         if(ref($class)) {
146 0           $class->packer()->dict($dict);
147             }
148              
149 0           return $dict;
150             }
151              
152             1;
153              
154             __END__