解决Net :: OpenSSH的问题并将多个命令传递给路由器

时间:2013-12-16 22:05:29

标签: perl openssh

我正在努力移动一个将命令推送到路由器的Perl脚本。我们关闭了telnet,所以我正在努力让SSH工作。在查看Perl中的许多SSH库之后,我选择使用Net :: OpenSSH。我没有登录并将命令传递给路由器的问题,但我遇到的问题是进入配置模式并随后传递命令。

问题是输入的每个命令都会显示基础系统注销,然后使用下一个后续命令重新输入。例如,使用Juniper路由器,我正在尝试执行以下操作:

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

从路由器告诉syslog我看到的东西......

(...)
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
(...)

正如您所注意到的,我在输入每个命令后得到LOGOUT_EVENT。当然在输入后立即退出配置模式会导致set interfaces命令失败,因为它不再处于配置模式。

我正在使用的Perl代码如下......

#!/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");

事件序列与使用telnet时相同。唯一真正的变化是使用SSH对象和Telnet对象。我很难过。您提供的任何想法都会非常有用。

[已解决,有点]

让Net :: Telnet开车的建议是正确的。以下代码有效......

#!/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");

我遇到的问题是我似乎无法将代码分成子程序。从子例程返回$conn对象后,底层ssh连接将断开。我需要分离这个逻辑,以便不必重写许多很多程序和代码行,这些代码和代码行在这个推送程序中继承。不过这个问题我会指向另一个问题。

[编辑,完全解决]

只是更新,以防任何人需要做类似的事情。

虽然在单个子例程下运行时上述工作非常好,但我发现每次将句柄传递给另一个子例程时,telnet句柄仍保持打开状态,但ssh连接断开。

为了解决这个问题,我发现如果我将ssh句柄传递给另一个子例程,然后附加了open2pty,并附加了Net :: Telnet,那么我可以在子例程之间传递Net :: Telnet句柄,而不会删除底层的ssh连接。这也适用于Net :: Telnet :: Cisco。我将此代码与Cisco,Juniper和Brocade路由器配合使用。

2 个答案:

答案 0 :(得分:3)

您还应该考虑向Net :: Telnet-> new()添加一些参数,因为它与ssh而不是TELNET服务器进行交互。

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

因为远程端没有TELNET服务器,-telnetmode => 0关闭TELNET协商。

行尾很可能只是一个回车符(即-output_record_separator =>“\ r”)而不是TCP或TELNET组合的回车换行符(“\ r \ n”)。

始终剥离回显输入-cmd_remove_mode => 1

答案 1 :(得分:1)

有几种可能性:

有些路由器接受通过stdin发送的命令序列:

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

在其他情况下,您必须使用Expect之类的命令发送命令,等待提示再次出现,发送另一个命令等。

如果您之前使用的是Net :: Telnet,Net :: OpenSSH文档将解释如何集成两者(尽管我必须承认该组合未经过严格测试)。

此外,一些路由器提供了一些方法来逃避完整的类Unix shell。即,用一声巨响来预备命令:

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