Re: Need help for passing data back and forth data over a persistent SSL TCP socket

[email protected] Mon, 24 Feb 2014 20:39:21 +0800
Newsgroups gmane.comp.lang.perl.poe
Message-ID <[email protected]>
Thanks for this, works perfectly. And since I had so much trouble 
finding examples of working code with SSL authentication and a 
persistent connection to pass data over I figured I would post the 
working code back so it's in the archive for others to poke at and 
steal. This is mostly just a combo of examples from CPAN and the perl 
POE cookbook but it seems to work as I wanted and allows multiple 
persistent clients etc. With this sever I am authenticating every 
command sent by the client against it's SSL cert. Probably un-needed 
beyond the SSLifying of the initial connection but colour me paranoid.

Server code :

#!/usr/bin/perl

   use strict;
   use warnings;
   use Socket;
   use POE qw(
     Wheel::SocketFactory
     Wheel::ReadWrite
     Driver::SysRW
     Filter::SSL
     Filter::Stackable
     Filter::Stream
   );


my $serverport = 2001;
my $debug = 1;

POE::Session->create(
   inline_states => {
     _start => \&parent_start,
     _stop  => \&parent_stop,

     socket_birth => \&socket_birth,
     socket_death => \&socket_death,
   }
);

sub parent_start {
   my $heap = $_[HEAP];
   $heap->{debug} = 1;
   if ($heap->{debug}) { print "= I = Starting POE session and 
initialising socket\n"};

   $heap->{listener} = POE::Wheel::SocketFactory->new(
     BindAddress  => '127.0.0.1',
     BindPort     => $serverport,
     Reuse        => 'yes',
     SuccessEvent => 'socket_birth',
     FailureEvent => 'socket_death',
   );
   if ($heap->{debug}) { print "= I = Socket initialised Waiting for 
connections\n"};
}

# clean up if we shut down the server
sub parent_stop {
   my $heap = $_[HEAP];
   delete $heap->{listener};
   delete $heap->{session};
   if ($_[HEAP]->{debug}) { print "= I = Listener Death!\n"};
}

# open the socket for the remote session.
sub socket_birth {
   my ($socket, $address, $port) = @_[ARG0, ARG1, ARG2];
   $address = inet_ntoa($address);

   print "\n= S = Socket birth client connecting\n" if $debug;

   POE::Session->create(
     inline_states => {
       _start => \&socket_success,
       _stop  => \&socket_death,

       socket_input => \&socket_input,
       socket_death => \&socket_death,
     },
     args => [$socket, $address, $port],
   );

}

# close the socket session when the user exits.
sub socket_death {
   my $heap = $_[HEAP];
   if ($heap->{socket_wheel}) {
     print "= S = Socket death, client disconnected\n" if $debug;
     delete $heap->{socket_wheel};
   }
}

#  yay! we sucessfully opened a socket. Set up the session.
sub socket_success {
         my ($heap, $kernel, $connected_socket, $address, $port) = 
@_[HEAP, KERNEL, ARG0, ARG1, ARG2];


         $heap->{debug} = 1;
         print "= I = CONNECTION from $address : $port \n" if 
$heap->{debug};
         if ($_[HEAP]->{debug}) { print "= SSL = Creating SSL Object\n"};
         ## make your own certificates to use here. Standard Cert 
creation. I used easy-rsa which is part of the openvpn package. Makes it 
really easy. As defined here: 
http://www.hermann-uwe.de/blog/howto-using-openvpn-on-debian-gnu-linux 
to the point of building client1 then you can stop.
         $heap->{sslfilter} = POE::Filter::SSL->new(
                  crt    => 'keys/server.crt',
                  key    => 'keys/server.key',
                  cacrt  => 'keys/ca.crt',
                  cipher => 'DHE-RSA-AES256-GCM-SHA384:AES256-SHA',
                  debug  => 1,
                  clientcert => 1
               );
               );
         $heap->{socket_wheel} = POE::Wheel::ReadWrite->new(
                 Handle => $connected_socket,
                 Driver => POE::Driver::SysRW->new(),
                 Filter => POE::Filter::Stackable->new(Filters => [
                                         $heap->{sslfilter},
                                         POE::Filter::Stream->new(),
                                 ]),
                 InputEvent => 'socket_input',
                 ErrorEvent => 'socket_death',
         );
         if ($_[HEAP]->{debug}) { print "= SSL = SSL connection 
established!\n"};
}

sub socket_input {

         my ($heap, $kernel, $buf) = @_[HEAP, KERNEL, ARG0];
         my $response;
         my $sub;
         my (@certid) = ($heap->{sslfilter}->clientCertIds());
         my $content = '';
         $heap->{debug} = 1;
         chomp($buf);
         if ($_[HEAP]->{debug}) {print "= I = Clint command received : 
$buf\n"};
         if ($_[HEAP]->{debug}) { print "= SSL = Authing Client 
Command\n"};
         if ($heap->{sslfilter}->clientCertValid()) {
                 if ($_[HEAP]->{debug}) { print "= SSL = Client 
Certificate Valid, Authorised\n"};
                 # this is where you take your input from the client, do 
something with it and send something back. I am getting temperature 
values and sending them to the client. YMMV
                 if ($buf eq "temp") {
                         $content = `/home/pi/c0de/sht15/temp-munin.py`;
                 } else {
                         $content = "Unknown request\n";
                 }
                 my $response = $content;
                 if ($_[HEAP]->{debug}) { print "= I = Sending Client 
Result:  $content\n"};
                 $heap->{socket_wheel}->put($response);
         } else {
                 # User is a bad bad person. No cookie!
                 if ($_[HEAP]->{debug}) { print "= SSL = Client 
Certificate Invalid! Rejecting command and disconnecting!\n"};
                 $content = "INVALID CERT! Connection rejected!\n";
                 my $response = $content;
                 $heap->{socket_wheel}->put($response);
                 if ($_[HEAP]->{debug}) { print "= I = Sending Client 
Result:  $content\n"};
                 $kernel->delay(socket_death => 1);
         }
}

$poe_kernel->run();
## END OF LINE

Client code:

#!/usr/bin/perl

   use warnings;
   use strict;

   use POE qw(Component::Client::TCP Filter::SSL);

   POE::Component::Client::TCP->new(
     RemoteAddress => "localhost",
     RemotePort    => 2001,
     ## make your own certificates to use here. Standard Cert creation. I 
used easy-rsa which is part of the openvpn package. Makes it really 
easy. As defined here: 
http://www.hermann-uwe.de/blog/howto-using-openvpn-on-debian-gnu-linux 
to the point of building client1 then you can stop.
     Filter        => [ "POE::Filter::SSL", crt => 'keys/client1.crt', 
key => 'keys/client1.key', client => 1 ],
     Connected     => sub {
         $_[HEAP]->{next_alarm_time} = int(time());   # Immediately 
trigger an alarm
         $_[KERNEL]->alarm(tick => $_[HEAP]->{next_alarm_time});

     },
     ServerInput   => sub {
         print "from server: ".$_[ARG0]."\n";
     },
     InlineStates => {
                 tick => sub {
                         #send the server a command server input will 
then capture the response while this bit waits 10 seconds and then does 
it all again. Thanks to Gunnar on the POE mailing list for help with 
this.
                         my $command = "temp";
                         print "Sending to server : $command\n";
                         $_[HEAP]{server}->put($command);
                         $_[HEAP]->{next_alarm_time}+=10;
                         $_[KERNEL]->alarm(tick => 
$_[HEAP]->{next_alarm_time});
                         },
                 }
   );

POE::Kernel->run();
## END OF LINE

Thanks for the help and I hope this helps someone in the future.

Kai

On 2014-02-21 18:14, Gunnar Strand wrote:
> Hi,
> 
> 
> 
> 2014-02-21 1:48 GMT+01:00  <[email protected]>:
>> Hi there,
>> 
>> I'm trying to write a POE based server/client combo that uses SSL 
>> authed
>> persistent connections for comunication between a client and server.
>> 
>> Basically I have a server running that listens on port 2001. A client
>> connects to the server. Sets up an SSL connection with client 
>> certificate
>> auth and then what I _want_ to happen is that the client then (every 
>> 10
>> seconds) asks for new temperature data from the server and is served 
>> the
>> current value. I have the client asking in a loop every 10 seconds but 
>> the
>> server isn't triggering and responding past the first connection 
>> event. I'm
>> not sure I'm doing this right (tm) can anyone help me? I have some 
>> inline
>> comments in the code.
> 
> You need to have a look at timers for POE:
> 
> http://poe.perl.org/?POE_Cookbook/Recurring_Alarms
> 
>> 
>> client :
>> 
>> #!/usr/bin/perl
>> 
>> ### very simple connect to server with auth certs and when connected 
>> sends
>> the "temp" command.
>> ### then when it receives input it fires off Server input and wait 10 
>> then
>> sends again. But
>> ### should it trigger a input event again? I think I'm looping in POE
>> incorrectly.
>> 
>>     ServerInput   => sub {
>>         my $command = "temp";
>>       while(1) {
>>         print "from server: ".$_[ARG0]."\n";
>>         sleep (10);
>>         print "Sending to server : $command\n";
>>         $_[HEAP]{server}->put($command);
>>       }
> 
> You are correct that you are "looping" incorrectly. POE is
> event-driven and uses run-to-completion scheduling
> which means that each subroutine must exit before the POE Kernel can
> schedule the next event.
> 
> You need to 1) set up a recurring alarm and corresponding event
> handler which sends "$command" to the server,
> and 2) handle data from the server in "ServerInput" and then return
> from the subroutine.
> 
> Add an alarm to "Connected":
> 
>       $_[HEAP]->{next_alarm_time} = int(time());   # Immediately
> trigger an alarm
>       $_[KERNEL]->alarm(tick => $_[HEAP]->{next_alarm_time});
> 
> Add an inline state "tick" event:
> 
>     tick => sub {
>       my $command = "temp";
>       print "Sending to server : $command\n";
>       $_[HEAP]{server}->put($command);
>       $_[HEAP]->{next_alarm_time}+=10;
>       $_[KERNEL]->alarm(tick => $_[HEAP]->{next_alarm_time});
>     },
> 
> See 
> http://search.cpan.org/dist/POE/lib/POE/Component/Client/TCP.pm#InlineStates
> 
> Remove *everything* from the "while" loop except the "print".
> 
> I have never dabbled in SSL, so I can't verify the server function, but 
> perhaps
> POE::Component::Server::TCP could relieve you of some of the socket 
> handling
> in the server part.
> 
> http://search.cpan.org/~rcaputo/POE/lib/POE/Component/Server/TCP.pm
> 
> BR
> Gunnar