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 )