rojmab has asked for the wisdom of the Perl Monks concerning the following question:

Hello,

I'm trying to create a small TCP server with perl. I have a small Java app on my blackberry that sends data to the server. It's simple data, just an IP address, a colon, then a number. The server then takes that information, looks up a switch's actual port name (i.e. FastEthernet0/2) from a hash, and SSH's to a switch and applies some commands for me.

I've gotten the basic functionality to work. If a connection comes in and calls a port that's not defined in the hash, it returns an error. If a connection comes in that is not formatted properly, it ignores it. If a connection comes in that is formatted correctly, it connects and enters the commands.

The problem is that when a good connection comes in, the script works its SSH magic and then crashes with the following error Can't call method "recv" on an undefined value at udplisten802.pl line 44 (Yes, the script is called UDPlisten when I'm using TCP. I started out trying to use UDP but found out i couldn't tunnel UDP through my BES server, so i switched it and never renamed :) )

It only has issues if it has to SSH to the device. I put some print statements in to see where it gets before it dies. It seems its going back as far as it needs to, but when the code reads $client=$sock->accept();, it doesn't seem to actual let the socket acccept the connection. I've tried putting in a 2nd $client=$socket->accept(); right after the first, but that made it so that it only reacted to every other connection I made.

I've quite confused why it's not behaving. If someone could take a look, it'd be greatly appreciated. I will post the script as well as another script I made for generating test traffic. Please go easy on me, I'm a fairly novice Perl guy, but I'm trying to learn. Thanks!!

udplisten.pl - the actual "Server" script.

use IO::Socket; use Net::Appliance::Session; use strict; use warnings; my ($sock,$LOCALPORT,$MAXLEN,$newmsg,$USER,$PASSWORD,$FILENAME,%PORTHA +SH,$client,$message); $LOCALPORT=2500; $MAXLEN=5151; $USER="REMOVED"; $PASSWORD="REMOVED"; $FILENAME="ports.txt"; #Pull in port mappings. Format is <Patch Panel> , <Switch Port name> I +.E. 13,FastEthernet1/0/20 print "Opening Ports and Reading\n"; open PORTS, $FILENAME or die $!; my @ports=<PORTS>; close PORTS; foreach(@ports) { chomp($_); my @t=split(/,/,$_); $PORTHASH{$t[0]}=$t[1]; } print "Port reading complete\n"; #Open a file to print things to (implemented later on), reusing the sa +me variable. $FILENAME=">data.txt"; #open STDOUT, '>>data.txt' or die $!; #Open Listener on TCP 2500 #$sock= IO::Socket::INET->new(LocalPort => $LOCALPORT,Proto => 'udp') +or die "Socket: $@"; $sock= IO::Socket::INET->new(LocalPort => $LOCALPORT,Proto => 'tcp', R +euse => 1, Listen => SOMAXCONN) or die "Socket: $@"; print "Starting... running on port: $LOCALPORT\n"; while(1) { $client=""; $client = $sock->accept(); $client->recv($message,1024); print "Here AGAIN!6\n"; while (1) { print "In here:\n"; print $message."\n"; #If the packet has the right format of <Switch IP> : <Patch pa +nel mapping>, SSH to it and implement the macro stored on the switch + if ($message=~ /(.*)\:(\d*)/) { #Make sure the port mapping exists, If it does, connect SS +H session my $host=$1; my $portnumber=$2; if (exists $PORTHASH{$portnumber}) { print "Exists!\n"; doSSH($host,$PORTHASH{$portnumber}); print "Here AGAIN!5\n"; last; }else { print"Port: ".$portnumber." is not defined. Host: ".$h +ost.".\n"; $client->send('Not Defined!!'); last; print "Here AGAIN!3\n"; } } else { last; close $client; } last; } } #Clean up sub doSSH { my $host=$_[0]; my $interface=$_[1]; my $s = Net::Appliance::Session->new($host); eval { $s->do_paging(0); $s->do_privileged_mode(0); #Send all SSH output to STDOUT so i can watch it $s->input_log(*STDOUT); print "Connecting to $host\n"; $s->connect(Name => $USER, Password =>$PASSWORD); $s->begin_configure(); $s->cmd("interface ".$interface." "); $s->cmd("macro apply 8021x"); $s->end_configure(); $s->close; }; if ( UNIVERSAL::isa($@,'Net::Appliance::Session::Exception') ) { print $@->message, "\n"; # fault description from Net::Applia +nce::Session print $@->errmsg, "\n"; # message from Net::Telnet print $@->lastline, "\n"; # last line of output from your appl +iance # perform any other cleanup as necessary $client->send('FAILED--'); }else { $client->send('Successful'); } print "Here AGAIN!4\n"; }

Simplesend.pl - just loops forever sending test traffic every 10 seconds.

use IO::Socket; use strict; my $LOCALPORT=2000; my ($newmsg,$MAXLEN,$TIMEOUT); $MAXLEN=5151; $TIMEOUT=5; while (1) { my $counter=10; while ($counter>0) { print "Sending in: ".$counter."\n"; $counter--; sleep 1; } print "Sending!\n\n"; my $sock= IO::Socket::INET->new(Proto => 'tcp', PeerPort=>'2500', +PeerAddr=>'192.168.110.253') or warn "Socket: $@\n"; if ($sock) { print $sock "192.168.250.13:13"; close ($sock); } }

One more piece of info, if anyone wants to run the script to test with actual data, the "ports.txt" file is formatted like this: <number>,<portanme> One per line. I.E. 13,FastEthernet0/2

Replies are listed 'Best First'.
Re: TCP Server Exits Loop and Crashes with error
by afoken (Chancellor) on Aug 03, 2010 at 14:46 UTC

    From IO::Socket:

    accept([PKG]) [...] In a scalar context the new socket is returned, or undef upon failure.

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
Re: TCP Server Exits Loop and Crashes with error
by roboticus (Chancellor) on Aug 03, 2010 at 15:47 UTC

    rojmab:

    A few miscellaneous notes:

    • Line 42 ($client="";) is unnecessary, as the very next line reassigns $client with the result of $sock->accept().
    • You're not checking whether $sock->accept() fails, which is the symptom you're reporting, I believe.
    • You should remove the inner while loop (starting at line 48), as you go to great lengths in never executing it again (the myriad last statements). Just delete lines 48, 49, 66, 71, 77, 80 and 81, and then fix the indentation.

    ...roboticus

Re: TCP Server Exits Loop and Crashes with error
by jethro (Monsignor) on Aug 03, 2010 at 15:46 UTC

    As afoken said, check if your io commands succeed. Alternatively you might check $sock->error (see IO::Handle, IO::Socket objects inherit the methods from IO::Handle).

    I'm a little confused by your description of the problem: Did you say that your script works correctly and prints no error message if you send a message that doesn't result in an ssh attempt, for example if the port number is not in your hash ?

Re: TCP Server Exits Loop and Crashes with error
by rojmab (Initiate) on Aug 03, 2010 at 20:26 UTC

    Woot woot. It's working as expected now. Everything is working as I want it to. Thanks for everyone's help. It creates a child process to SSH to the device, then kills the process when it's finished while the "Parent" keeps running. Here's what the final code looks like:

    use IO::Socket; use Net::Appliance::Session; use strict; use warnings; use Sys::Hostname; use POSIX qw(:sys_wait_h); sub REAP { 1 until (-1 == waitpid(-1, WNOHANG)); $SIG{CHLD} = \&REAP; } $SIG{CHLD} = \&REAP; my ($sock,$LOCALPORT,$MAXLEN,$USER,$PASSWORD,$FILENAME,%PORTHASH,$clie +nt,$message,$con_handle); $LOCALPORT=2500; $MAXLEN=5151; $USER="REMOVED"; $PASSWORD="REMOVED"; #open STDOUT, '>>data.txt' or die $!; #Open Listener on TCP port identified by $LOCALPORT $sock= IO::Socket::INET->new(LocalPort => $LOCALPORT,Proto => 'tcp', R +euse => 1, Listen => SOMAXCONN) or die "Socket: $@"; $sock->autoflush(1); print "Starting... running on port: $LOCALPORT\n"; while($client = $sock->accept()) { next if my $con_handle = fork; die "fork fail: $!" unless defined($con_handle); $client->recv($message,1024); print "In here:\n"; print $message."\n"; $|=1; #If the packet has the right format of <Switch IP> : <Patch pa +nel mapping>, SSH to it and implement the macro stored on the switch + if ($message=~ /(.*)\:(\d*)/) { #Make sure the port mapping exists, If it does, connect SS +H session my $host=$1; my $portnumber=$2; #Pull in port mappings. Format is <Patch Panel> , <Switch +Port name> I.E. 13,FastEthernet1/0/20 $FILENAME="ports.".$host.".txt"; print "Opening Ports and Reading File:".$FILENAME."\n"; open PORTS, $FILENAME or die $!; my @ports=<PORTS>; close PORTS; foreach(@ports) { chomp($_); my @t=split(/,/,$_); $PORTHASH{$t[0]}=$t[1]; } print "Port reading complete\n"; if (exists $PORTHASH{$portnumber}) { print "Exists!\n"; doSSH($host,$PORTHASH{$portnumber}); print "Here AGAIN!5\n"; exit(fork); }else { print"Port: ".$portnumber." is not defined. Host: ".$h +ost.".\n"; $client->send('Not Defined!!'); last; } } else { print "In ELSE \n"; } } continue { close $client; kill CHLD => -$$; } sub doSSH { my $host=$_[0]; my $interface=$_[1]; my $s = Net::Appliance::Session->new($host); eval { $s->do_paging(0); $s->do_privileged_mode(0); #Send all SSH output to STDOUT so i can watch it $s->input_log(*STDOUT); print "Connecting to $host\n"; $s->connect(Name => $USER, Password =>$PASSWORD); $s->begin_configure(); $s->cmd("interface ".$interface." "); $s->cmd("macro apply 8021x"); $s->end_configure(); $s->close; }; if ( UNIVERSAL::isa($@,'Net::Appliance::Session::Exception') ) { print $@->message, "\n"; # fault description from Net::Applia +nce::Session print $@->errmsg, "\n"; # message from Net::Telnet print $@->lastline, "\n"; # last line of output from your appl +iance # perform any other cleanup as necessary $client->send('FAILED--'); }else { print "SUCCESS! \n"; $client->send('Successful'); } }

      Two random comments:

      Instead of killing the childs you could wait for them to finish with wait(). Are you sure that the child has always finished the task when you kill it?

      You have an exit(fork); right after the doSSH. It might be some clever construct I just don't get, but it looks like a senseless fork, where both parent and child exit immediately after the fork. Shouldn't it be just exit();?

Re: TCP Server Exits Loop and Crashes with error
by rojmab (Initiate) on Aug 03, 2010 at 16:43 UTC

    Thanks for all the feedback. I added or die "$!"; to the $client=$socket->accept(); line.

    I'm seeing an error now: No child processes at udplisten802.pl line 44.

    What does this mean? Out of random googling, I decided to tweak it a bit. I added this:

    my $con_handle = fork(); if ($con_handle) { print "Child Spawned [$con_handle]\n"; }else{ <put in the while(1) block here> }
    With this code, the script runs, still throws the error about no child processes, but it keeps running though. I don't understand what the code does, but it keeps running. I'd rather fix it so it works properly. What would cause the accept() call to fail?

    @ Jethro, If i get a packet for a port that's not defined by a mapping, I have it report an error "Port: 1 is not defined for this host" and the script keeps running. I only get the error after I do an SSH session.

    Thanks all for your help!

Re: TCP Server Exits Loop and Crashes with error
by rojmab (Initiate) on Aug 03, 2010 at 17:40 UTC

    Ok, Good news. I removed the extra loop that Roboticus mentioned. After doing that and with the code to fork it in there, it runs now without throwing any errors. The only thing left to fix is now that I'm using child processes, they are not closing themselves. Any thoughts on how to make them close nicely? Thanks again Gurus!

      The only thing left to fix is now that I'm using child processes, they are not closing themselves. Any thoughts on how to make them close nicely?

      Use a method of forking that returns the pid, store them in an array, and do a kill 9 (or 15) on them when you want them gone? You may need to do a killfam on the pid just in case you get an extraneous shell pid as a parent to your spawned child pid.


      I'm not really a human, but I play one on earth.
      Old Perl Programmer Haiku