AnyEvent-Radius

 view release on metacpan or  search on metacpan

lib/AnyEvent/Radius/Client.pm  view on Meta::CPAN

use constant {
    READ_TIMEOUT_SEC => 5,
    WRITE_TIMEOUT_SEC => 5,
    RADIUS_PORT => 1812,
    MAX_REQUEST_ID => 0xFF,
};

# deprecated?
use constant MAX_QUEUE => MAX_REQUEST_ID() + 1;

# new 'NAS'
# args:
#   ip
#   port
#   secret
#   dictionary
#   read_timeout
#   write_timeout
#   bind_ip
#   initial_last_request_id - random by default
#- callbacks:
#    on_read
#    on_read_raw
#    on_read_timeout
#    on_write_timeout
#    on_error
sub new {
    my ($class, %h) = @_;

    my $obj = bless {}, $class;

    # either pre-created packer object, or need radius secret to create new one
    # dictionary is optional
    if ( defined $h{packer} ) {
        $obj->packer( $h{packer} );
    } elsif ( defined $h{secret} ) {
        $obj->packer( Data::Radius::Packet->new(dict => $h{dictionary}, secret => $h{secret}) );
    } else {
        die "No radius secret";
    }

    my %udp_handle_args = (
        rtimeout => $h{read_timeout} // READ_TIMEOUT_SEC,
        wtimeout => $h{write_timeout} // WRITE_TIMEOUT_SEC,
    );

    die "No IP argument" if ! exists $h{ip};
    $udp_handle_args{connect} = [ $h{ip}, $h{port} // RADIUS_PORT ];
    $udp_handle_args{bind} = [$h{bind_ip}, 0] if exists $h{bind_ip};

    $udp_handle_args{on_recv} = sub {
        my ($data, $handle, $from) = @_;
        $obj->read_cv->end;
        $obj->reply_cnt($obj->reply_cnt + 1);

        if ($h{on_read_raw}) {
            # dump raw data
            $h{on_read_raw}->($obj, $data, $from);
        }

        # using authenticator from request to verify reply
        my $request_id = $obj->packer()->request_id($data);
        # FIXME how to react on unknown request_id ?
        my $send_info = delete $obj->send_cache()->{ $request_id };
        if (! $send_info ) {
            # got unknown reply (with wrong request id?)
            if ($h{on_error}) {
                $h{on_error}->($obj, 'Unknown reply');
            }
            else {
                warn "Error: unknown reply";
            }
        }
        else {
            my $on_read = $h{on_read};
            my $req_callback = $send_info->{callback};
            if ( $on_read || $req_callback ) {
                # how to decode $from
                # my($port, $host) = AnyEvent::Socket::unpack_sockaddr($from);
                # my $ip = format_ipv4($host);

                my ($type, $req_id, $auth, $av_list) = $obj->packer()->parse($data, $send_info->{authenticator});

                $on_read->($obj, {
                            type => $type,
                            request_id => $req_id,
                            av_list => $av_list,
                            # from is sockaddr binary data
                            from => $from,
                            authenticator => $auth,
                        }) if $on_read;
                $req_callback->($type, $av_list) if $req_callback;
            }
        }

        $obj->queue_cv->end;
    };

    $udp_handle_args{on_rtimeout} = sub {
        my $handle = shift;
        if(! $obj->read_cv->ready) {
            if($h{on_read_timeout}) {
                $h{on_read_timeout}->($obj, $handle);
            }
            $obj->clear_send_cache();
            # stop queue
            $obj->queue_cv->send;
        }
        $handle->clear_rtimeout();
    };

    $udp_handle_args{on_wtimeout}  = sub {
        my $handle = shift;
        if(! $obj->write_cv->ready) {
            if($h{on_write_timeout}) {
                $h{on_write_timeout}->($obj, $handle);
            }
            $obj->clear_send_cache();
            # stop queue
            $obj->queue_cv->send;
        }

lib/AnyEvent/Radius/Client.pm  view on Meta::CPAN

#  $nas1->on_ready($cv);
#  $nas2->on_ready($cv);
#  $nas3->on_ready($cv);
#  $cv->recv;
#
sub on_ready {
    my ($self, $cv) = @_;

    $cv->begin();
    $self->queue_cv()->cb(sub { $cv->end });
}

sub load_dictionary {
    my ($class, $path) = @_;
    my $dict = Data::Radius::Dictionary->load_file($path);

    if(ref($class)) {
        $class->packer()->dict($dict);
    }

    return $dict;
}

sub next_request_id {
    my $self = shift;
    return undef if $self->queue_cnt() > MAX_REQUEST_ID();
    my $last_request_id = $self->last_request_id();
    my $new_request_id = ($last_request_id + 1) & MAX_REQUEST_ID();
    my $send_cache = $self->send_cache();
    while (exists $send_cache->{$new_request_id}) {
        $new_request_id = ($new_request_id + 1) & MAX_REQUEST_ID();
        return undef if $new_request_id == $last_request_id; # send cache full ??
    }
    $self->last_request_id($new_request_id);
    return $new_request_id;
}

# add packet to the queue
# type - radius request packet type code or its text alias
# av_list - list of attributes in {Name => ... Value => ... } form
# cb - optional callback to be called on result:
#      - when received response as $cb->($resp_type, $resp_av_list)
#      - when failed (eg time out, invalid or non matching response)
#        with empty parameter list cb->();
sub send_packet {
    my ($self, $type, $av_list, $cb) = @_;

    my $request_id = $self->next_request_id();
    if ( !defined $request_id ) {
        return;
    }

    $type = $RADIUS_PACKET_TYPES{$type} if exists $RADIUS_PACKET_TYPES{$type};

    my ($packet, $req_id, $auth) = $self->packer()->build(
                        type => $type,
                        av_list => $av_list,
                        request_id => $request_id,
                    );

    # required to verify reply
    $self->send_cache()->{ $req_id } = {
        authenticator => $auth,
        type => $type,
        callback => $cb,
        time_cached => AE::now(),
    };
    $self->queue_cnt($self->queue_cnt() + 1);

    $self->_send_packet($packet);

    return wantarray() ? ($req_id, $auth) : $req_id;
}

# shortcut methods:

sub send_auth {
    my $self = shift;
    return $self->send_packet(AUTH => @_);
}

sub send_acct {
    my $self = shift;
    return $self->send_packet(ACCT => @_);
}

sub send_pod {
    my $self = shift;
    return $self->send_packet(POD => @_);
}

sub send_coa {
    my $self = shift;
    return $self->send_packet(COA => @_);
}

1;

__END__

=head1 NAME

AnyEvent::Radius::Client - module to implement AnyEvent based RADIUS client

=head1 SYNOPSYS

    use AnyEvent;
    use AnyEvent::Radius::Client;

    my $dict = AnyEvent::Radius::Client->load_dictionary('path-to-radius-dictionary');

    sub read_reply_callback {
        # $h is HASH-REF {type, request_id, av_list, from, authenticator}
        my ($self, $h) = @_;
        ...
    }

    my $client = AnyEvent::Radius::Client->new(
                        ip => $ip,
                        port => $port,
                        on_read => \&read_reply_callback,



( run in 0.715 second using v1.01-cache-2.11-cpan-13bb782fe5a )