Retry SSH to Host if Connection to the Host Fails in Perl - perl

I have a script, which does SSH to the server and execute some command (In this script, for demonstration I am running Perl print statement with Hello message).
Here is my script:
#!/usr/bin/perl
use strict; use warnings;
use Net::OpenSSH;
$Net::OpenSSH::debug = ~0;
BEGIN {
open my $out, '>', '/tmp/debug.txt' or warn $!;
$Net::OpenSSH::debug_fh = $out;
$Net::OpenSSH::debug = -1;
}
my #hosts = ("ipaddress1","ipaddress2");
my $ssh;
my $command = "perl -e 'print \"Hello..\"'";
foreach my $n (#hosts) {
#Here if connection to the host($n) fails, is it possible to retry again
$ssh = Connect($n, "user", "passwd");
$ssh->capture($command);
print "Done execution in Host: $n\n";
}
undef $ssh;
print "**End**\n";
sub Connect {
my ( $host, $user, $passwd ) = #_;
my $ssh = Net::OpenSSH->new($host, user=>$user, password=>$passwd);
$ssh->error and die "Couldn't establish SSH connection: " . $ssh->error;
return $ssh;
}
Whenever I execute this script, sometimes it successfully prints below message:
Done execution in Host: ipaddress1
Done execution in Host: ipaddress2
**End**
But sometimes cannot do ssh to host (either ipaddress1 or ipaddress2) and gives following message:
Couldn't establish SSH connection: unable to establish master SSH connection: master process exited unexpectedly at script.pl ....
Its being get died in Connect subroutine (cause I couldn't trace, opened question here).
So, is there any way if I cannot connect(ssh) to the host, retry can be done after certain period of time (for n number times) instead of printing error message and make the script die?

OpenSSH provides a nice interface for errors. I'd start by looking at the examples on the cpan page. Try the following
foreach my $n (#hosts) {
#Here if connection to the host($n) fails, is it possible to retry again
$ssh = Connect($n, "user", "passwd", 3);
$ssh->capture($command);
print "Done execution in Host: $n\n";
}
undef $ssh;
print "**End**\n";
sub Connect {
my ( $host, $user, $passwd , $retry_limit ) = #_;
my $timeout = 10;
my $con;
while ( $retry_limit-- > 0 )
{
$con = Net::OpenSSH->new($host,
user=>$user,
password=>$passwd,
timeout=> $timeout,
);
last unless $con->error();
}
die "unable to connect ".$con->error() if retry_limit <0;
return $con;
}

Related

Can't call method “send” on an undefined value at - Net::SSH::Expect Perl error

I am running some commands in a remote server by connecting from my local server. To achieve this I am connecting to remote server using Net::SSH::Expect perl module. Connection is establishing here, but sometimes when I execute the command the following error comes up -
Can't call method "send" on an undefined value at .....
Here is my code:
my ($ip, $user, $passwd) = ("my.ip.address.here","user", "password");
my $ssh = SSH_Connection( $ip, $user, $passwd );
my $command_to_execute = "<Command to be executed will build here>";
print "$command_to_execute\n";
$str = 'Bye';
$ssh->send("$command_to_execute; echo $str");
$output = $ssh->waitfor($str, undef);
$ssh->close();
print "END\n";
sub SSH_Connection {
my ( $host, $user, $passwd ) = #_;
my $ssh = Net::SSH::Expect->new (
host => $host, #ip
user => $user, #'user'
password => $passwd, #'password'
raw_pty => 1,
no_terminal => 0,
);
my $login_output;
my $handledie = eval {
$login_output = $ssh->login();
};
if ( $# ) {
if ($# =~ m/SSHConnectionError/i ) {
print "SSH Connection Error\n";
} elsif ( $# =~ m/SSHProcessError/ix ) {
print "SSH Process Error\n";
} elsif ( $# =~ m/SSHConnectionAborted/ix ) {
print "SSH Connection Aborted\n";
} else {
print "SSH Unknown Error: $#\n";
}
}
if ($login_output !~ /Last login/) {
die "Login has failed.";
} else {
return $ssh;
}
print "SSH to ip - $host failed\n";
}
First I'm building the command and storing it in $command_to_execute variable.
At the end of command execution I'll get keyword Bye. So I am waiting for that keyword to match.
My question is -
Why I am getting above mentioned error?
Suppose if my command execution is failed, will the control will come back to my script ? Because its waiting for $str word.
I doubt about the error catching method is not proper. Please suggest a better solution.

concurrent login to linux machine using perl

As part of concurrent testing, i have to login to a linux server using telnet concurrently. I need a maximum of 50 logins to the servers. I am able to write a simple script using perl expect but the session ends once it goes to the next item in the loop. Can someone help out on how to do this? ALso the below implementation does the connection serially and not concurrently.
for(my $i = 1; $i <= 5; $i++) {
my $exp = Expect->spawn("telnet abc") or die "Cannot spawn telnet: $!\n";
$exp->expect($timeout, "Login:");
$exp->send("$username\n");
$exp->expect($timeout, "Password:");
$exp->send("$password\n");
}
You should do all the work in Perl:
my %users = (
'name1' => 'pw1',
'name2' => 'pw2',
# ...
'name50' => 'pw50',
);
my #sessions;
while (my ($user, $pass) = each %users) {
my $exp = Expect->spawn("telnet abc")
or die "Cannot spawn telnet: $!\n";
$exp->expect($timeout, "Login:");
$exp->send("$user\n");
$exp->expect($timeout, "Password:");
$exp->send("$pass\n");
push #sessions, $exp;
}
# now that you're all logged in, logout
for my $exp (#sessions) {
$exp->send("exit\n");
$exp->expect('eof'); # I'm not sure this is correct
}

SSHProcessError The ssh process was terminated in $ssh->waitfor

I am very new to Perl script and trying to write a perl code to ssh to a Router and then run scp export command on the router (scp export from router to a remote destination server).
#!/usr/local/bin/perl
use Net::SSH::Expect;
use warnings;
$hostname = "Router";
my $ssh = Net::SSH::Expect->new (
host => $hostname,
password=> 'abcd',
user => 'admin',
raw_pty => 1,
timeout => 150,
);
my $login_output = $ssh->login();
if ($login_output !~ /Router/) {
die "Login has failed. Login output was $login_output";
}
$ssh->send("scp export log traffic start-time equal 2013/04/01\#00:00:00 to user\#192.168.1.1:<path> end-time equal 2013/04/01\#01:00:00",3);
$ssh->waitfor('user#192.168.1.1\'s password:\s*') or die "prompt 'password' not found";
$ssh->send("abcd");
$ssh->send("\n");
sleep 100;
my $logout = $ssh->close();
print "=" x 50;
print "\n";
I am able to see that script logins to the router fine (I can see a new user on the router everytime I run the script). However, after few seconds, script terminates with error:
SSHProcessError The ssh process was terminated. at cron1 line 22
What is wrong with line 22: $ssh->waitfor('user#192.168.1.1\'s password:\s*') or die "prompt 'password' not found";
I used a different approach to login to a server. Have made a function out of this piece of code, works fine every time for me. See if this can help you out..
sub connect {
my $host =shift;
my $password=shift;
my $user=shift;
my $counter=0;
my $login_output;
$ssh = Net::SSH::Expect->new (
host => "$host",
password=> "$password",
user => "$user",
raw_pty => 1
);
$login_output = $ssh->run_ssh();
LABLE_login: # Lable used for looping
$login_output=$ssh->read_all();
if( $login_output =~ /yes/){ # To check if you are logging in for the first time
$ssh->send("yes\n");
sleep(2);
$login_output=$ssh->read_all();
if( $login_output =~ /Password/){
$ssh->send("$password\n");
}
}
elsif( $login_output =~ /Password/)
{
$ssh->send("$password\n");
}
else
{
sleep(2);
$counter++;
if($counter eq 3)
{ print color('red');print "Cannot connect to host exiting now\n";print color('reset');exit();}
goto LABLE_login;
}
$login_output=$ssh->send("$password\n");
return $ssh;
}
I ran into the same problem when $hostname wasn't valid. Doing a simple hostname check first fixed it for me..
die "Host $hostname not found" unless gethostbyname($hostname);
update:
It seems just about any ssh connection error will cause the SSHProcessError error.

Remote command execution using perl client-server (socket)

My client.pl
#!/usr/bin/perl
use IO::Socket::INET;
use strict;
my $name = '172.20.10.189'; #Server IP
my $port = '7890';
my $socket = IO::Socket::INET->new('PeerAddr' => $name,
'PeerPort' => $port,
'Proto' => 'tcp') or die "Can't create socket ($!)\n";
print "Client sending\n";
while (1) {
my $msg = <STDIN>;
print $socket $msg;
print scalar <$socket>;
}
close $socket
or die "Can't close socket ($!)\n";
My server.pl
#!/usr/bin/perl
use IO::Socket::INET;
use strict;
my $port = "7890";
my $socket = IO::Socket::INET->new('LocalPort' => $port,
'Proto' => 'tcp',
'Listen' => SOMAXCONN)
or die "Can't create socket ($!)\n";
while (my $client = $socket->accept) {
my $name = gethostbyaddr($client->peeraddr, AF_INET);
my $port = $client->peerport;
while (<$client>) {
print "[$name $port] $_";
my #out = `$_`;
print #out;
print $client "$.: #out";
}
close $client
or die "Can't close ($!)\n";
}
die "Can't accept socket ($!)\n";
My client is sending a command (ls -lrt /) to the server and Server is supposed to run that command and send output to the client back.
Problem:-
The command is executed successfully on the server but it sends only first line to the client. If I press any key from client again the next line of output is sent to the client.
Or tell me how to send multiple line output to back to client.
Any help would be appreciated.
Thanks
Abhishek
The Server sends all lines to the client, the client however chooses to read only one line:
print scalar <$socket>;
If you remove the scalar, it should work. However, your architecture is still a security nightmare.
All servers should run in taint mode (-T switch).
Never blindly execute commands that a clients sends you. Only execute commands that pass a very strict validation test, do not run commands that just don't look malicious.
Perhaps you are trying to duplicate SSH, you might want to look at that program instead.
Your server doesn't do any kind of authentication. At least it logs all inputs.
It was a silly mistake... and I have fixed the first issue as follows and got multi-line output on the client side...
Client.pl
#!/usr/bin/perl
use IO::Socket::INET;
use strict;
my $name = '172.20.10.189'; #Server IP
my $port = '7890';
my $socket = IO::Socket::INET->new('PeerAddr' => $name,
'PeerPort' => $port,
'Proto' => 'tcp')
or die "Can't create socket ($!)\n";
print "Client sending\n";
while (1) {
my $msg = <STDIN>;
print $socket $msg;
while (<$socket>)
{
print "\n$_";
}
}
close $socket
or die "Can't close socket ($!)\n";
BUT there is one more issue -
I want my client to keep sending few other commands one after another until I close the client manually and receive output.
The problem is - It receives output of the first command only..
Can anyone now help me on this?

Perl Script cannot fork more than 10 times

My perl code does not allow more than 10 forks. For the following perl code, whenever I use more than 10 machines in the list of machines read in to the script, the perl script only forks 10 processes for 10 machines and for the rest it dies with error:
SSHProcessError The ssh process was terminated. at serverLogin.pl 44
It dies at the line where it says $ssh->waitfor('The authenticity of host*',15);.
PERL SCRIPT:
#!/usr/bin/perl -w
use Net::SSH::Expect;
use Term::ReadKey;
print "please enter filename:\n";
$filename = ReadLine;
chomp $filename;
print "please enter user ID:\n";
$userID = ReadLine;
chomp $userID;
print "please enter password:\n";
ReadMode 'noecho';
$passwordforuser = ReadLine 0;
chomp $passwordforuser;
ReadMode 'normal';
open READFILE,"<","$filename" or die "Could not open file listofmachines\n";
my #listofmachines = <READFILE>;
foreach $machine (#listofmachines)
{
my $pid=fork();
if ($pid){
push(#childprocs,$pid);
}
elsif ( $pid == 0 ) {
my $ssh = Net::SSH::Expect->new (
host => "$machine",
user => "$userID",
password=> "$passwordforuser",
timeout => 25,
raw_pty => 1,
);
my $login_output = $ssh->run_ssh or die "Could not launch SSH\n";
$ssh->waitfor('The authenticity of host*',15);
#print "This output for machine $machine\n";
$ssh->send("yes");
$ssh->waitfor('password: ', 15);
$ssh->send("$passwordforuser");
$ssh->waitfor('$ ', 10);
my #commresult=$ssh->exec("uptime");
print $login_output;
print #commresult;
exit 0;
}
else {
die "Could not Fork()\n";
}
}
foreach(#childprocs){
waitpid($_, 0)
}
Please help. Thanks, nblu.
Your script using Net::OpenSSH::Parallel instead of Net::SSH::Expect.
The number of simultaneous connections is limited to 10 to overcome any resource exhaustion problem as happening in your script (probably PTYs):
#!/usr/bin/perl -w
use Net::OpenSSH::Parallel;
use Term::ReadKey;
print "please enter filename:\n";
$filename = ReadLine;
chomp $filename;
print "please enter user ID:\n";
$userID = ReadLine;
chomp $userID;
print "please enter password:\n";
ReadMode 'noecho';
$passwordforuser = ReadLine 0;
chomp $passwordforuser;
ReadMode 'normal';
open READFILE,"<","$filename" or die "Could not open file listofmachines\n";
my #listofmachines = <READFILE>;
chomp #listofmachines;
my $pssh = Net::OpenSSH::Parallel->new(connections => 10);
$pssh->add_host($_,
user => $userID, password => $passwordforuser,
master_opts => [-o => 'StrictHostKeyChecking=no'])
for #listofmachines;
sub do_ssh_task {
my ($host, $ssh) = #_;
my $output = $ssh->capture('uptime');
print "$host: $output";
}
$pssh->all(parsub => \&do_ssh_task);
$pssh->run;
for my $host (#listofmachines) {
if (my $error = $pssh->get_error($host)) {
print STDERR "remote task failed for host $host: $error\n";
}
}
By default, the remote ssh daemon limits the number of concurrent ssh connections to something like 10 per userid. If that is a problem for you, you will need to change the server configuration...
Perhaps you have a limit to the number of processes you can create? Can you create 30 or more processes in a loop where the children just sleep(60)?
If in fact you have a limit of how many you can do at once, try using Parallel::ForkManager.
If this is from hitting a limit on pseudoterminals, how you set that depends on kernel version; what does uname -a say? also depends on whether the code is using BSD or SysV/UNIX98 ptys. If you see it opening files like /dev/ptyXY where X is one of a-e or p-z, it's the former, and you will have a hard limit of 256 systemwide.
You can change passwords without a pseudoterminal using usermod instead of passwd, but this momentarily exposes the crypted password in the process list; that may be acceptable in your case.