Perl socket programing send method - perl

I am making socket programming for simple communication now.
I have noticed that the server is not the one I created and it works fine (given the experimental client)
In my code, recv works fine, but send does not work. Is there anything wrong with my code?
my $socket = new IO::Socket::INET (
#PeerHost => '127.0.0.1',
PeerHost => '192.168.0.100',
PeerPort => '8472',
Proto => 'tcp',
);
die "cannot connect to the server $!\n" unless $socket;
print "connected to the server\n";
while (1) {
my $response = "";
$socket->recv($response, 1024);
if ($response) {
my #test = split(//,$response);
my ($length,$data) = unpack("N A*",$response);
%json = json_decode($data,$length);
switch ($json{'type'}) {
case 1 { print "Game Start\n";}
#case 2 { my $tmp = &my_turn(%json);} #my_turn func is return "{'type': 0, 'point': [5, 4]}", but fail!
#case 2 { $socket->send("{'type': 0, 'point': [5, 4]}");} # fail!
case 2 { print $socket "{'type': 0, 'point': [5, 4]}"; print "ok\n";} # print is executed. However, the server does not receive packets
#case 2 { $socket->send("{'type': 0, 'point': [5, 4]}");} #fail...
case 3 { print "ACCEPT\n";}
case 5 { print "NOPOINT\n";}
case 6 { print "GAMEOVER\n";}
case 7 { print "ERROR\n";}
else {print "ERROR type : $json{'type'}\n"}
}
}
}
The server works fine. I checked with the example source (python code) given with the server. What am I missing?

You can't assume the recv (or read) will return the entire response. You need to call it repeatedly.
You can't assume the recv (or read) will just the response. You need to limit the size of the read of buffer the excess.
decode_json returns a reference (not a list of key-value pairs you can assign to a hash).
You might also have to handle encoding of the JSON string. The example below assumes UTF-8 encoding.
JSON response to the server (case 2 in the original code) needs to include length too.
The following code should be used instead:
#!/usr/bin/perl
use strict;
use warnings;
use JSON;
use IO::Socket;
my $socket = IO::Socket::INET->new(
PeerHost => '127.0.0.1',
PeerPort => '22',
Proto => 'tcp',
) or
die "cannot connect to the server $!\n";
print "connected to the server\n";
sub read_bytes($$) {
my($socket, $length) = #_;
my $result = '';
print "ATTEMPT TO READ ${length}\n";
while ($length > 0) {
my $received = $socket->read($result, $length, length($result));
die "socket error: $!\n" unless defined($received);
die "unexpected EOF\n" unless $received;
$length -= $received;
}
print "READ '${result}'\n";
return($result);
}
while (1) {
my $length = unpack("N", read_bytes($socket, 4));
my $json = read_bytes($socket, $length);
my $data = JSON->new->utf8->decode($json);
print $data->{type}, "\n";
if ($data->{type} == 2) {
my $response = {
type => 0,
point => [5, 4],
};
my $resp_json = JSON->new->utf8->encode($response);
print "JSON: ${resp_json}\n";
my $packet = pack('NA*', length($resp_json), $resp_json);
print "PACKET: ", unpack('H*', $packet), "\n";
$socket->write($packet);
}
}
As I don't have access to your server I used sshd on my local machine, which of course does not send me a JSON. But it shows that reading works :-)
$ perl dummy.pl
connected to the server
ATTEMPT TO READ 4
READ 'SSH-'
ATTEMPT TO READ 1397966893
^C
Output for an example response to the server would be:
JSON: {"type":0,"point":[5,4]}
PACKET: 000000187b2274797065223a302c22706f696e74223a5b352c345d7d

Related

WebSocket server from scratch showing opcode -1

I am trying to create a simple WebSocket server in perl from scratch, when I tried it in Google Chrome it gave me opcode -1, How can I fix it?
websocket.pl
#!/usr/bin/perl -w
use strict;
use IO::Socket::INET;
use Digest::SHA1 "sha1_base64";
$| = 1;
my $magic_string = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
# Create a server
my $socket = IO::Socket::INET->new( LocalHost => 'localhost',
LocalPort => 7777,
Proto => 'tcp',
Listen => 5,
Reuse => 1
) || die "$!";
print "Server is running on port 7777\n";
while (1) {
my $client = $socket->accept();
my $key = "";
# Get the Request
my $data = "";
while (my $line = <$client>) {
$data .= $line;
}
# Get the Sec-WebSocket-Key value
foreach my $line ( split /\n/ => $data ) {
if ( $line =~ /^Sec-WebSocket-Key: (\S+)/ ) {
$key = $1;
}
}
print "Sec-WebSocket-Key: $key\n";
# Create the Sec-WebSocket-Accept header value
my $accept = sha1_base64($key);
$accept .= "="x(4-(length($accept)%4));
print "Sec-WebSocket-Accept: $accept\n";
# Response
print $client "HTTP/1.1 101 Switching Protocols\r\n";
print $client "Upgrade: websocket\r\n";
print $client "Connection: Upgrade\r\n";
print $client "Sec-WebSocket-Accept: $accept\r\n\r\n";
shutdown($client, 1);
}
$socket->close();
I am pretty sure that the key returned to website is correct, so where is the problem? What went wrong?
ws.js
var ws = new WebSocket("ws://localhost:7777/");
ws.onopen = function() {
alert("connected!");
ws.send( 'Hello server' );
};
ws.onclose = function() {
alert( 'Connection is closed... ');
};
Web Browser network traffic
Edit
Stefan Becker: Yea, I know, but in this case I was sure that the request is under 1024 bytes, I've fixed it, thanks.
(Opcode -1) is a generic error. In your case it is a bad Sec-WebSocket-Accept header. You forgot to use $magic_string:
my $accept = sha1_base64($key.$magic_string);
Also while (my $line = <$client>) { will probably run forever. You need to check for an empty line.

Simple websocket server from scratch is not working

Created a simple websocket server using perl from scratch, when pointing to it from Chrome (by using echo test), got error Error during WebSocket handshake: Incorrect 'Sec-WebSocket-Accept' header value.
Pretty sure the key returned for websocket handshake is correct. Any ideas?
use IO::Socket;
use IO::Select;
use Digest::SHA1 qw(sha1 sha1_hex sha1_base64);
$servSock = IO::Socket::INET->new( Proto => 'tcp',
LocalPort => 8080,
Reuse => 1,
Listen => 500
) || die "failed to setup outsock $#\n";
$s = IO::Select->new();
$s -> add ($servSock);
my #readySocks;
while (1) {
#readySocks = $s->can_read(5);
foreach $sock (#readySocks) {
if ($sock eq $servSock)
{
#print "inSock\n";
$clientSock = $servSock->accept();
setsockopt($clientSock, IPPROTO_TCP, TCP_NODELAY, 1);
$s->add ($clientSock);
} else
{
#print "outsock\n";
$buff = "";
$fromAddr = recv $sock, $buff, 1470, 0;
if (length($buff) <= 0)
{
$s->remove($sock);
close $sock; $numOfConns --;
next;
}
printf "recved %d bytes\n", length($buff);
if ($buff =~ /Sec-WebSocket-Key: (\S+)/) {
$str = $1;
print "key is $str|\n";
my $str = sha1_base64($str . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
send ($sock, qq{HTTP/1.1 101 Switching Protocols\r
Connection: Upgrade\r
Upgrade: websocket\r
Sec-Websocket-Accept: $str\r
\r\n}, 0);
}
}
}
}
Note: I need to create one from scratch as part of another project.
Update
Thanks to the suggestion from #steffen-ullrich, changed it from
send ($sock, qq{HTTP/1.1 101 Switching Protocols\r
Connection: Upgrade\r
Upgrade: websocket\r
Sec-Websocket-Accept: $str\r
\r\n}, 0);
to (note there $str is now $str=)
send ($sock, qq{HTTP/1.1 101 Switching Protocols\r
Connection: Upgrade\r
Upgrade: websocket\r
Sec-Websocket-Accept: $str=\r
\r\n}, 0);
made it work.
From the documentation of sha1_bas64:
Note that the base64 encoded string returned is not padded to be a multiple of 4 bytes long. If you want interoperability with other base64 encoded sha1 digests you might want to append the redundant string "=" to the result.
And adding '=' as described is what your code is missing.

How to set in PERL recv timeout in my code?

I want to set timeout in my recv function in this specific code below, because sometimes my script stuck forever. I am new in socket programming so i would really appreciate any help. Thanks in advance.
use IO::Socket::INET;
use IO::Select;
use LWP::UserAgent;
use JSON::XS 'decode_json';
use Data::Dumper;
use DBI();
sub dbconn {
my $db_conf = shift;
my $dbh = DBI->connect("DBI:Pg:dbname=somedatabase;host=somehost", "postgres", "",
{pg_server_prepare =>
0,AutoCommit => 1,RaiseError=>1});
$dbh->do("SET CLIENT_ENCODING TO 'UTF-8';");
return $dbh;
}
# auto-flush on socket
$| = 1;
# creating a listening socket
my $socket = new IO::Socket::INET (
LocalHost => '0.0.0.0',
LocalPort => '5000',
Proto => 'tcp',
Listen => 5,
Reuse => 1
);
die "cannot create socket $!\n" unless $socket;
$sel = IO::Select->new( $socket );
print "Server waiting for client connection on port 5000...\n";
my $command = 1;
my $watchTracker = "*HQ,";
my $tl206 = ",LAT:";
my $watchConnectedCheck = ",A,";
my $gpsType;
my $circleString = ",LINK,";
my $dataToSend;
my $new;
my $dbh = dbconn();
while(#ready = $sel->can_read) {
foreach $fh (#ready) {
if($fh == $socket) {
# Create a new socket
$new = $socket->accept;
$new->recv($dataReceived, 1024);
$new->recv($dataReceived, 1024);
# get information about a newly connected client
my $client_address = $new->peerhost();
my $client_port = $new->peerport();
print "===============================================\n";
print "===============================================\n\n";
print "Connection from $client_address:$client_port\n";
print "General data received: $dataReceived\n\n";
#MORE LINES...
}
else {
# Process socket
# Maybe we have finished with the socket
$sel->remove($fh);
$fh->close;
}
}
}
$dbh->disconnect();
Perhaps I am misunderstanding the question, but have you tried setting a timeout in the socket with "Timeout"?
See IO::Socket::INET.
EDIT: I did not catch the 'recv' bit. You have to use setsockopt, which is not wholly portable, so the final answer is somewhat dependent on your platform. Here are some posts that may help:
How do I set `SO_RCVTIMEO` on a socket in Perl?
http://www.perlmonks.org/?node_id=761935
E.g.,
$socket->setsockopt(SOL_SOCKET, SO_RCVTIMEO, pack('l!l!', 30, 0))
or die "setsockopt: $!";

Flush INET Socket response data with BLOCKING enabled

I am making a program that interfaces with Teamspeak, and I have an issue where the responses received will not match the commands sent. I run the program multiple times and each time, I will get different results when they should be the same, due to responses being out of sync.
my $buf = '';
use IO::Socket;
my $sock = new IO::Socket::INET (
PeerAddr => 'localhost'
,PeerPort => '10011'
,Proto => 'tcp'
,Autoflush => 1
,Blocking => 1
,Timeout => 10
);
sub ExecuteCommand{
print $sock $_[0]."\n";$sock->sysread($buf,1024*10);
return $buf;
};
ExecuteCommand("login ${username} ${password}");
ExecuteCommand("use sid=1");
ExecuteCommand("clientupdate client_nickname=Idle\\sTimer");
my $client_list = ExecuteCommand("clientlist");
Each command is executed properly, however the server likes to return extra lines, so a single sysread will not be enough and I will have to execute another. The size of responses are at most 512, so they aren't being cut off. If I try to run the sysread multiple times in an attempt to flush it, when there is nothing to read it will just make the program hang.
The end of the executions are followed with "error id=0 msg=ok"
How would I be able to read all the data that comes out, even if it's multiple lines? Or just be able to flush it all out so I can move onto the next command without having to worry about old data?
So you want to read until you find a line starting with error. In addition to doing that, the following buffers anything extra read since it's part of the next response.
sub read_response {
my ($conn) = #_;
my $fh = $conn->{fh};
our $buf; local *buf = \($conn->{buf}); # alias
our $eof; local *eof = \($conn->{eof}); # alias
$buf = '' if !defined($buf);
return undef if $eof;
while (1) {
if ($buf =~ s/\A(.*?^error[^\n]*\n)//ms) {
return $1;
}
my $rv = sysread($fh, $buf, 64*1024, length($buf));
if (!$rv) {
if (defined($rv)) {
$eof = 1;
return undef;
} else {
die "Can't read response: $!\n";
}
}
}
}
my $conn = { fh => $sock };
... send command ...
my $response = read_response($conn);
...
... send command ...
my $response = read_response($conn);
...
I changed my ExecuteCommand subroutine to include a check for "error code=[0-9]{1,}", which is what is always at the end of a response for Teamspeak 3 servers.
sub ExecuteCommand{
print $sock $_[0]."\n";
my $response = "";
while (1){
$sock->sysread($buf,1024*10);
last if($buf =~ /error id=([0-9]{1,})/);
$response .= $buf;
};
return $response;
};

filtering packets from a specific ip using perl and Net::Pcap and Net::PcapUtils

I've been trying to write a script that filters packets out of a device and from a specific ip address over that device.
I want data to be like the output i get from wireshark when you select a specific device and you use the ip.src==xx.xx.xx.xx
my program so far is like this
#!/usr/bin/perl -w
my $interface='eth1';
sub process_pkt #Packet processing routine
{
my ($user_data,$header, $packet) = #_;
my $minipacket = substr($packet,0,54);
print ("\n## raw: ###\n");
print ($minipacket);
print ("\n==Byte# / Hex / Dec / Bin==\n");
for ($i=0;$i<55;$i++)
{
$hexval = unpack('H2',substr($packet,$i,1));
$decval = hex(unpack('H2',substr($packet,$i,1)));
printf ("%03s-%02s-%03s-%08b\n", $i, $hexval, $decval, $decval);
}
}
# ######################################################################
# Here we are invoking the NetPcap module and looping through forever.
Net::PcapUtils::loop(\&process_pkt,
SNAPLEN => 65536, #Size of data to get from packet
PROMISC => 1, #Put in promiscuous mode
FILTER => 'tcp', #only pass TCP packets
DEV => $interface, );
and I am getting output
now i want to filter out packets that are received on the eth1 device and from the soruce ip of xx.xx.xx.xx
can we use the filter option in Net::PcapUtils::loop to do that?
and then i want packets of data length xx
...
i tried going through the documentation in cpan.org
but all i find is the options available.. i couldn't find any examples..
can someone please help me out?
improvements:
can i use something like
FILTER => 'ip src xx.xx.xx.xx'
after the
FILTER => 'tcp'
line in the code?
and can i somehow include the data length of the packet so as to filter the packets of data length = 86?
Alternative program i am using to get the payload of the packet:
#!/usr/bin/perl -w
# #########################
#
use Net::PcapUtils;
use NetPacket::Ethernet qw(:strip);
use NetPacket::IP;
use NetPacket::TCP;
use NetPacket::IP qw(:strip);
my $interface= 'eth1';
my $snaplen= 65536;
my $filter='tcp';
my $promisc = 1;
my $timeout = 10000 ;
my $err;
sub process_pkt
{
my ($user_data,$header,$packet) = #_;
$ip= NetPacket::IP->decode(eth_strip($packet));
$tcp= NetPacket::TCP->decode($ip->{data});
$payload = $tcp->{data};
print ("payload: \n ".$payload." \n----end-----\n");
for($i=0;$i<55;$i++){
$hexval = unpack('H2',substr($payload,$i,1));
open (MYFILE, '>>perldata1.txt');
print MYFILE ($i." :hex: ". $hexval."\n");
close (MYFILE);
}
}
Net::PcapUtils::loop(\&process_pkt,
SNAPLEN => 65536,
PROMISC => 1,
FILTER => 'tcp',
FILTER => 'ip src 129.7.236.40',
DEV => $interface, );
but am still not able to figure out how to get the length of the data field. :(
Thanks.
#!/usr/bin/perl -w
# #########################
#
use Net::PcapUtils;
use NetPacket::Ethernet qw(:strip);
use NetPacket::IP;
use NetPacket::TCP;
use NetPacket::IP qw(:strip);
use strict;
use Data::Dumper;
#use warnings;
my $interface= 'eth1';
my $snaplen= 65536;
my $filter='tcp';
my $promisc = 1;
my $timeout = 10000 ;
my $err;
my #array;
sub process_pkt
{
my ($user_data,$header,$packet) = #_;
my $ip= NetPacket::IP->decode(eth_strip($packet));
my $tcp= NetPacket::TCP->decode($ip->{data});
my $payload = $tcp->{data};
if(length($payload)==32)
{
for(my $decode=0;$decode<32;$decode++)
{
$array[$decode] = unpack('H2',substr($payload,$decode,1));
}
my $length= scalar(#array);
open (MYFILE, '>doorstatus.tab');
if($array[22] eq '0c')
{
print MYFILE ( " Decision: Granted\n");
}
elsif($array[22] eq '04')
{
print MYFILE ("Decision: Denied\n");
}
elsif($array[22] eq '0d')
{
print MYFILE ("Decision: Locked\n");
}
else
{
print MYFILE ("Decision: unknown \n");
}
#print MYFILE ( " Data: \n".Dumper(\#array)." \n");
close (MYFILE);
}
}
Net::PcapUtils::loop(\&process_pkt,
SNAPLEN => 65536,
PROMISC => 1,
FILTER => 'tcp',
FILTER => 'ip src xx.xx.xx.xx',
DEV => $interface, );
The code filters the data coming from a specific source into an array and you can do any thing with it,