COPS-Client

 view release on metacpan or  search on metacpan

lib/COPS/Client.pm  view on Meta::CPAN

package COPS::Client;

use warnings;
use strict;
use IO::Select;
use IO::Socket;
use IO::Handle;

=head1 NAME

COPS::Client - COPS Protocol - Packet Cable Client 

=head1 VERSION

Version 0.06

=cut

our $VERSION = '0.06';

=head1 SYNOPSIS

    This module provides a simple COPS client for managing Packet Cable Multi
Media sessions for CMTS. It should provide all the neccessary functionality to
enable a service provider to deploy, manage and control service flows within
their network. This does not maintain a connection to the CMTS but issue the
configured command, get the response and then close the TCP connection. I am
working on a stateful Client however this is not yet available.

    As basic initial use of the module is as follows

    my $cops_client = new COPS::Client (
        [
        ServerIP => '192.168.0.1',
        ServerPort => '3918',
        Timeout => 2,
        DataHandler => \&display_data
        ]
        );

    $cops_client->set_command("set");
    $cops_client->subscriber_set("ipv4","172.20.1.1");

    $cops_client->gate_specification_add(
        [
        Direction       => 'Downstream',
        DSCPToSMark     => 0,
        Priority        => 0,
        PreEmption      => 0,
        Gate_Flags      => 0,
        Gate_TOSField   => 0,
        Gate_TOSMask    => 0,
        Gate_Class      => 0,
        Gate_T1         => 0,
        Gate_T2         => 0,
        Gate_T3         => 0,
        Gate_T4         => 0
        ]
        );

    $cops_client->classifier_add(
        [
        Classifier_Type         => 'Classifier',
        Classifier_Priority => 64,
        Classifier_SourceIP => "172.20.1.1",
        ]
        );

    $cops_client->envelope_add (
        [
        Envelope_Type           => "authorize,reserve,commit",
        Service_Type            => 'DOCSIS Service Class Name',
        ServiceClassName 	=> 'S_down'
        ]
        );

    $cops_client->connect();
    $cops_client->check_data_available();

    This will connect to a CMTS on IP 192.168.0.1 and apply a PCMM gate to 
the subscriber with IP address 172.20.1.1 and apply the service class S_down.

    It should be noted not all CMTS support all the functions available, so
    if the COPS request is failing for you remove opaque_set, timebase_set or
    volume_set and try again.

    You may also get a very cryptic error if an envelope or classifier is
    incorrectly configured.

=head1 EXPORT

There are no exports.

=head1 FUNCTIONS

    new
    set_command
    subscriber_set
    gate_specification_add
    classifier_add
    envelope_add
    rks_set
    decode_radius_attribute
    volume_set
    timebase_set
    opaque_set

=head2 new

    The new function initialises the module and sets the CMTS IP, Port and
the data handling function which gets called if a RTP message is received.

    The parameters are

        ServerIP    -  The IP address of the CMTS to connect to

        ServerPort  -  The port that the Packet Cable service is running
                       on. There is no default value however most server
                       implementations use port 3918, so this should be
                       set to that

        Timeout     -  This is a timeout parameter for the connection to the
                       CMTS. It has a default of 5 so can be omitted.

        DataHandler -  This should be set to point to a local function to
                       handle any data returned by a COPS message sent.

                       The function will accept 2 variables as input the
                       first is the module point, the second is a point to
                       a hash containing any data returned.

    An example of use would be.

    my $cops_client = new COPS::Client (
        [
        ServerIP => '192.168.0.1',
        ServerPort => '3918',
        Timeout => 2,
        DataHandler => \&display_data
        ]
        );

    sub display_data
        {
        my ( $self ) = shift;
        my ( $data ) = shift;
        print "Report Datagram sent.\n\n";
        foreach my $name ( sort { $a cmp $b } keys %{$data} )
            {
            print "Attribute Name  is '$name' value is '${$data}{$name}'\n";
            }
        }

=head2 set_command

    This command sets the type of command to be sent in the connection. It
    can be one of 4 types as follows

            set        -    Meaning GateSet
            delete     -    Meaning GateDelete
            info       -    Meaning GateInfo
            synch      -    Meaning Synch Request

    An example of use is

        $cops_client->set_command ( "set" );

    The command specified must match *Exactly* otherwise it will be ignored. It
    appears Cisco CMTS do NOT respond to Synch requests. Cisco have been asked
    to respond to this query but no information has been forthcoming.

=head2 subscriber_set

    This function sets the subscriber ID to be used. The subcriber ID can be
    either an IPV4 or IPV6 address. If you use an IPV6 address it *MUST* be
    fully qualified.

    The function takes two parameters the first specifies which IPVx to use,
    the second is the IPVx value.

    An example of use is

        $cops_client->subscriber_set("ipv4","172.20.1.1");

    The subscriber ID is required for 99% of all COPS messages.

=head2 gate_specification_add

    This function builds a gate with the attributes specified. Possible
    attributes are

        Direction      -  This can be 'Upstream' or 'Downstream' only.
                          If specified this overrides Gate_Flags as
                          direction is one bit of the Gate_Flags 
                          parameter.

        Priority       -  This is a value of 0 to 7. If specified this
                          overrides Gate_Class as Priority is 3 bits

lib/COPS/Client.pm  view on Meta::CPAN


    This would set the time limit to 60 seconds.

=head2 opaque_set

    This function allows you to add arbitary data to the COPS message sent which *may* be
    recorded against the gate by the remote CMTS.

    The only attribute for this function is

        OpaqueData                   - This be any data, although keeping it to something
                                       humanly readable is probably a good idea.

    An example of use would be

    $cops_client->opaque_set(
        [
        OpaqueData => 'a test string'
        ]
        );

    This would add 'a test string' as Opaque data to the gate.

=head2 Summary

    This is very much a 'work in progress'. 

=cut

sub new {

        my $self = {};
        bless $self;

        my ( $class , $attr ) =@_;

        my ( %template );
        my ( %current_data );
        my ( %complete_decoded_data );
        my ( %handles );

        $self->{_GLOBAL}{'DEBUG'}=0;

        while (my($field, $val) = splice(@{$attr}, 0, 2))
                { $self->{_GLOBAL}{$field}=$val; }

        $self->{_GLOBAL}{'STATUS'}="OK";

        if ( !$self->{_GLOBAL}{'VendorID'} )
                { $self->{_GLOBAL}{'VendorID'}="Generic Client"; }

        if ( !$self->{_GLOBAL}{'ServerIP'} )
                { die "ServerIP Required"; }

        if ( !$self->{_GLOBAL}{'ServerPort'} )
                { die "ServerPort Required"; }

        if ( !$self->{_GLOBAL}{'KeepAlive'} )
                { $self->{_GLOBAL}{'KeepAlive'}=60; }

        if ( !$self->{_GLOBAL}{'Timeout'} )
                { $self->{_GLOBAL}{'Timeout'}=5; }

        if ( !$self->{_GLOBAL}{'ListenIP'} )
                { $self->{_GLOBAL}{'ListenIP'}=""; }

        if ( !$self->{_GLOBAL}{'ListenPort'} )
                { $self->{_GLOBAL}{'ListenPort'}=""; }

        if ( !$self->{_GLOBAL}{'ListenServer'} )
                { $self->{_GLOBAL}{'ListenServer'}=0; }

        if ( !$self->{_GLOBAL}{'RemotePassword'} )
                { $self->{_GLOBAL}{'RemotePassword'}=""; }

        if ( !$self->{_GLOBAL}{'RemoteSpeed'} )
                { $self->{_GLOBAL}{'RemoteSpeed'}=10; }

        if ( !$self->{_GLOBAL}{'TMPDirectory'} )
                { $self->{_GLOBAL}{'TMPDirectory'}="/tmp/"; }

        $self->{_GLOBAL}{'data_ack'}=0;
        $self->{_GLOBAL}{'TRANSACTION_COUNT'}=1;
        $self->{_GLOBAL}{'ERROR'}="" ;
        $self->{_GLOBAL}{'data_processing'}=0;
        $self->{_GLOBAL}{'current_command'}="";
	$self->{_GLOBAL}{'Classifier_Encoded'}="";
	$self->{_GLOBAL}{'Envelope_Encoded'}="";
	$self->{_GLOBAL}{'TimeLimit'}="";
	$self->{_GLOBAL}{'VolumeLimit'}="";
	$self->{_GLOBAL}{'OpaqueData'}="";
	$self->{_GLOBAL}{'RKS_Encoded'}="";

        $self->{_GLOBAL}{'template'}= \%template;
        $self->{_GLOBAL}{'current_data'}= \%current_data;
        $self->{_GLOBAL}{'complete_decoded_data'} = \%complete_decoded_data;

	$self->{_GLOBAL}{'Listener_HandlesP'}= \%handles;

        return $self;
}

sub disconnect
{
my ( $self ) = shift;
if ( $self->{_GLOBAL}{'Handle'} )
	{
	$self->{_GLOBAL}{'Handle'}->close();
	}
return 1;
}

sub connect
{
my ( $self ) = shift;

my $lsn = IO::Socket::INET->new
                        (
                        PeerAddr => $self->{_GLOBAL}{'ServerIP'},
                        PeerPort => $self->{_GLOBAL}{'ServerPort'},
                        ReuseAddr => 1,
                        Proto     => 'tcp',
                        Timeout    => $self->{_GLOBAL}{'Timeout'}
                        );

if (!$lsn)
        {
        $self->{_GLOBAL}{'STATUS'}="Failed to bind to address '".$self->{_GLOBAL}{'ServerIP'}."' ";;
        $self->{_GLOBAL}{'STATUS'}.="and port '".$self->{_GLOBAL}{'ServerPort'};
        $self->{_GLOBAL}{'ERROR'}=$!;
        return 0;
        }

$self->{_GLOBAL}{'LocalIP'}=$lsn->sockhost();
$self->{_GLOBAL}{'LocalPort'}=$lsn->sockport();
$self->{_GLOBAL}{'Handle'} = $lsn;
$self->{_GLOBAL}{'Selector'}=new IO::Select( $lsn );
$self->{_GLOBAL}{'STATUS'}="Success Connected";

if ( $self->{_GLOBAL}{'ListenServer'} )
	{
	# we should do a fork here so the listener can wait for commands
	# how we signal when data is ready not sure.
	my $child;
	if ($child=fork)
		{}
		elsif (defined $child)
		{
		my $lsn2 = IO::Socket::INET->new
       	                 (
       	                 Listen    => 1024,
       	                 LocalAddr => $self->{_GLOBAL}{'ListenIP'},
       	                 LocalPort => $self->{_GLOBAL}{'ListenPort'},
       	                 ReuseAddr => 1,
       	                 Proto     => 'tcp',
       	                 Timeout    => $self->{_GLOBAL}{'Timeout'}
       	                 );
		if ( !$lsn2)
			{
			$self->{_GLOBAL}{'STATUS'}="Failed to bind to address '".$self->{_GLOBAL}{'ListenIP'}."' ";
      			$self->{_GLOBAL}{'STATUS'}.="and port '".$self->{_GLOBAL}{'ListenPort'};
			$self->{_GLOBAL}{'ERROR'}=$!;
			exit(0);
			}

		$self->{_GLOBAL}{'Listen_LocalIP'}=$lsn2->sockhost();
		$self->{_GLOBAL}{'Listen_LocalPort'}=$lsn2->sockport();
		$self->{_GLOBAL}{'Listen_Handle'} = $lsn2;
		$self->{_GLOBAL}{'Listen_Selector'}=new IO::Select( $lsn2 );
		$self->{_GLOBAL}{'Listen_STATUS'}="Success Connected";
		$self->check_listeners_available();
		exit(0);
		}
	}

return 1;
}

sub connect_flush
{
my ( $self ) = shift;
undef $self->{_GLOBAL}{'LocalIP'};
undef $self->{_GLOBAL}{'LocalPort'};
undef $self->{_GLOBAL}{'Handle'};
undef $self->{_GLOBAL}{'Selector'};
undef $self->{_GLOBAL}{'STATUS'};
return 1;
}

sub connected
{
my ( $self ) = shift;
return $self->{_GLOBAL}{'Selector'};
}


sub check_data_handles
{
my ( $self ) = shift;
my ( @handle ) = $self->{_GLOBAL}{'Selector'}->can_read;
if ( !@handle ) {  $self->{_GLOBAL}{'ERROR'}="Not Connected"; }
$self->{_GLOBAL}{'ready_handles'}=\@handle;
}

sub get_data_segment
{
my ( $self ) = shift;
my ( $header );
my ( $buffer ) = "";
my ( $dataset ) ;

my $link;
my ( $version, $type, $session, $flags, $length );
my ( $handles ) = $self->{_GLOBAL}{'ready_handles'};

foreach my $handle ( @{$handles} )



( run in 1.835 second using v1.01-cache-2.11-cpan-39bf76dae61 )