Question

I'm working on moving a Perl script that pushed commands to routers. We have turned off telnet, so I'm working on getting SSH to work. After looking at a number of SSH libraries in Perl, I've opted to use Net::OpenSSH. I have no problem logging in and passing commands to the routers, but the problem I'm having is with entering config mode and subsequently passing a command.

The problem is that with each command entered, the underlying system appears to logout then reenter with the next subsequent command. For example with a Juniper router I'm trying to do the following:

edit private
set interfaces xe-1/3/2  description "AVAIL: SOMETHING GOES HERE"
commit
exit
quit

Tailing the syslog from the router I'm seeing something like this...

(...)
UI_LOGIN_EVENT: User 'tools' login, class 'j-remote-user' [65151], ssh-connection   'xxx.xxx.xxx.xxx 42247 xxx.xxx.xxx.xxx 22', client-mode 'cli'
UI_CMDLINE_READ_LINE: User 'tools', command 'edit private '
UI_DBASE_LOGIN_EVENT: User 'tools' entering configuration mode
UI_DBASE_LOGOUT_EVENT: User 'tools' exiting configuration mode
UI_LOGOUT_EVENT: User 'tools' logout
UI_AUTH_EVENT: Authenticated user 'remote' at permission level 'j-remote-user'
UI_LOGIN_EVENT: User 'tools' login, class 'j-remote-user' [65153], ssh-connection 'xxx.xxx.xxx.xxx 42247 xxx.xxx.xxx.xxx 22', client-mode 'cli'
UI_CMDLINE_READ_LINE: User 'tools', command 'set interfaces '
UI_LOGOUT_EVENT: User 'tools' logout
(...)

As you notice I'm getting a LOGOUT_EVENT after each command entered. Of course exiting config mode immediately after entering it causes the set interfaces command to fail as it's no longer in config mode.

The Perl code I'm using is as follows...

#!/usr/bin/perl -w
use strict;
use lib qw(
  /usr/local/admin/protect/perl
  /usr/local/admin/protect/perl/share/perl/5.10.1
);

use Net::OpenSSH;

my $hostname = "XXXXX";
my $username = "tools";
my $password = "XXXXX";
my $timeout  = 60;
my $cmd1     = "edit private";
my $cmd2     = 'set interfaces xe-1/3/2  description "AVAIL: SOMETHING GOES HERE"';
my $cmd3     = "commit";
my $cmd4     = "exit";

my $ssh = Net::OpenSSH->new($hostname, user => $username, password => $password, timeout => $timeout,
           master_opts => [-o => "StrictHostKeyChecking=no"]);
$ssh->error and die "Unable to connect to remote host: " . $ssh->error;

my @lines = eval { $ssh->capture($cmd1) };
foreach (@lines) {
    print $_;
};

@lines = eval { $ssh->capture($cmd2) };
foreach (@lines) {
    print $_;
};

@lines = eval { $ssh->capture($cmd3) };
foreach (@lines) {
    print $_;
};


@lines = eval { $ssh->capture($cmd4) };
foreach (@lines) {
    print $_;
};

$ssh->system("quit");

The sequence of events is the same as when telnet was used. The only real change was in using SSH objects verses Telnet objects. I'm stumped. Any ideas you could provide would be quite helpful.

[SOLVED, sort of]

The suggestion let Net::Telnet do the driving was the correct one. The following code works...

#!/usr/bin/perl -w

use strict;
use Net::OpenSSH;
use Net::Telnet;
use Data::Dumper;

my $promptEnd = '/\w+[\$\%\#\>]\s{0,1}$/o';

my $cmd1 = "show system uptime | no-more";
my $cmd2 = "show version brief | no-more";

my $hostname = "xxx.xxx";
my $username = "xxxxxxx";
my $password = "xxxxxxx";
my $timeout  = 60;

my $ssh = Net::OpenSSH->new(
    $hostname,
    user        => $username,
    password    => $password,
    timeout     => $timeout,
    master_opts => [ -o => "StrictHostKeyChecking=no" ]
);
$ssh->error and die "Unable to connect to remote host: " . $ssh->error;

my ( $fh, $pid ) = $ssh->open2pty( { stderr_to_stdout => 1 } );

my %params = (
    fhopen    => $fh,
    timeout   => $timeout,
    errmode   => 'return',
);

$conn = Net::Telnet->new(%params);
$conn->waitfor($promptEnd);

@lines = $conn->cmd($cmd1);
foreach (@lines) {
    print $_;
}

@lines = $conn->cmd($cmd2);
foreach (@lines) {
    print $_;
}

$conn->cmd("quit");

The problem I'm having is that I can't seem to separate the code into subroutines. Once the $conn object is returned from a subroutine, the underlying ssh connection drops. I need to separate this logic in order to not have to rewrite many, many programs and lines of code that relay on this pusher routine. However that problem I'll direct to another question.

[Edit, fully solved]

Just an update in case anyone needs to do something similar.

While the above worked very well when run under a single subroutine, I found that any time I passed the handle to another subroutine, the telnet handle remained open, but the ssh connection dropped.

To solve this I found that if I passed the ssh handle to another subroutine, then later attached the open2pty, and attached Net::Telnet, then I could pass the Net::Telnet handle between subroutines without the underlying ssh connection dropping. This also worked for Net::Telnet::Cisco as well. I have this code working well with Cisco, Juniper, and Brocade routers.

Was it helpful?

Solution 2

There are several possibilities:

Some routers accept having the sequence of commands sent up front via stdin:

my $out = $ssh->capture({stdin_data => join("\r\n", @cmds, '')})

In other cases you will have to use something like Expect to send a command, wait for the prompt to appear again, send another command, etc.

If you were using Net::Telnet before, the Net::OpenSSH docs explain how to integrate both (though I have to admit that combination is not very tested).

Also, some routers provide some way to escape to a full Unix-like shell. I.e., preppending the commands with a bang:

$ssh->capture("!ls");

OTHER TIPS

You should also consider adding a few more parameters to the Net::Telnet->new() because it is interacting with ssh rather than a TELNET server.

-telnetmode => 0
-output_record_separator => "\r",
-cmd_remove_mode => 1,

Because there is no TELNET server on remote side, -telnetmode => 0 turns off TELNET negotiation.

The end-of-line is most likely just a carriage-return (i.e. -output_record_separator => "\r") rather than the TCP or TELNET combination of carriage-return linefeed ("\r\n").

Always strip the echoed back input -cmd_remove_mode => 1

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top