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 <k...@angry-monk.com>:
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