File Coverage

blib/lib/AnyEvent/Radius/Server.pm
Criterion Covered Total %
statement 27 68 39.7
branch 0 20 0.0
condition 0 8 0.0
subroutine 9 13 69.2
pod 2 2 100.0
total 38 111 34.2


line stmt bran cond sub pod time code
1             package AnyEvent::Radius::Server;
2             # AnyEvent-based radius server
3 1     1   1296 use strict;
  1         2  
  1         30  
4 1     1   6 use warnings;
  1         1  
  1         24  
5 1     1   5 use AnyEvent;
  1         5  
  1         18  
6 1     1   5 use AnyEvent::Handle::UDP;
  1         2  
  1         23  
7              
8 1     1   4 use base qw(Class::Accessor::Fast);
  1         2  
  1         133  
9             __PACKAGE__->mk_accessors(qw(handler packer));
10              
11 1     1   7 use Data::Radius::Constants qw(:all);
  1         2  
  1         187  
12 1     1   8 use Data::Radius::Dictionary ();
  1         2  
  1         12  
13 1     1   5 use Data::Radius::Packet ();
  1         9  
  1         27  
14              
15             use constant {
16 1         676 READ_TIMEOUT_SEC => 5,
17             WRITE_TIMEOUT_SEC => 5,
18             RADIUS_PORT => 1812,
19 1     1   6 };
  1         2  
20              
21             my %DEFAUL_REPLY = (
22             &ACCESS_REQUEST => ACCESS_REJECT,
23             &ACCOUNTING_REQUEST => ACCOUNTING_RESPONSE,
24             &DISCONNECT_REQUEST => DISCONNECT_REJECT,
25             &COA_REQUEST => COA_REJECT,
26             );
27              
28             # new 'server'
29             # args:
30             # ip
31             # port
32             # secret
33             # dictionary
34             #- callbacks:
35             # on_read
36             # on_read_raw
37             # on_wrong_request
38             # on_error
39             sub new {
40 0     0 1   my ($class, %h) = @_;
41              
42 0 0         die "No IP argument" if (! $h{ip});
43             # either pre-created packer obect, or need radius secret to create new one
44             # dictionary is optional
45 0 0 0       die "No radius secret" if (! $h{packer} && ! $h{secret});
46              
47 0           my $obj = bless {}, $class;
48              
49             my $on_read_cb = sub {
50 0     0     my ($data, $handle, $from) = @_;
51              
52 0 0         if ($h{on_read_raw}) {
53             # dump raw data
54 0           $h{on_read_raw}->($obj, $data, $from);
55             }
56              
57             # how to decoded $from
58             # my($port, $host) = AnyEvent::Socket::unpack_sockaddr($from);
59             # my $ip = format_ipv4($host);
60              
61 0           my ($type, $req_id, $authenticator, $av_list) = $obj->packer()->parse($data);
62              
63 0 0         if (! $obj->packer()->is_request($type)) {
64             # we expect only requests in server
65 0 0         if ($h{on_wrong_request}) {
66 0           $h{on_wrong_request}->($obj, {
67             type => $type,
68             request_id => $req_id,
69             av_list => $av_list,
70             # from is sockaddr binary data
71             from => $from,
72             });
73             }
74              
75             # Do not reply
76 0           warn "Ignore wrong request type " . $type;
77             return
78 0           }
79              
80 0           my ($reply_type, $reply_av_list) = ();
81              
82 0 0         if($h{on_read}) {
83             # custom-reply
84 0           ($reply_type, $reply_av_list) = $h{on_read}->($obj, {
85             type => $type,
86             request_id => $req_id,
87             av_list => $av_list,
88             # from is sockaddr binary data
89             from => $from,
90             });
91             }
92              
93 0 0         if (! $reply_type) {
94             # reject by default
95 0           $reply_type = $DEFAUL_REPLY{ $type };
96 0           $reply_av_list = [{Name => 'Reply-Message', Value => 'Default rule: reject'}];
97             }
98              
99 0           my ($reply, $r_id, $r_auth) = $obj->packer()->build(
100             type => $reply_type,
101             av_list => $reply_av_list,
102             authenticator => $authenticator,
103             request_id => $req_id,
104             with_msg_auth => 1,
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__