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

Perl 使用Net::OpenSSH解决问题并将多个命令传递给路由器,perl,openssh,Perl,Openssh,我正在研究一个将命令推送到路由器的Perl脚本。我们已经关闭了telnet,所以我正在努力让SSH正常工作。在查看了Perl中的许多SSH库之后,我选择了使用Net::OpenSSH。我登录并向路由器传递命令没有问题,但我遇到的问题是进入配置模式并随后传递命令 问题是,输入每个命令后,底层系统都会注销,然后使用下一个后续命令重新输入。例如,对于Juniper路由器,我尝试执行以下操作: edit private set interfaces xe-1/3/2 description "AVAI

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

问题是,输入每个命令后,底层系统都会注销,然后使用下一个后续命令重新输入。例如,对于Juniper路由器,我尝试执行以下操作:

edit private
set interfaces xe-1/3/2  description "AVAIL: SOMETHING GOES HERE"
commit
exit
quit
跟踪路由器的系统日志,我看到了这样的东西

(...)
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\u事件。当然,进入配置模式后立即退出会导致
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连接将断开。我需要分离这个逻辑,以便不必重写很多很多程序和代码行,这些程序和代码行都依赖于这个pusher例程。然而,对于这个问题,我将直接提出另一个问题

[编辑,完全解决]

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

当在单个子例程下运行时,上述操作非常有效,但我发现,每当我将句柄传递给另一个子例程时,telnet句柄都保持打开状态,但ssh连接断开


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

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

my $out = $ssh->capture({stdin_data => join("\r\n", @cmds, '')})
在其他情况下,您将不得不使用诸如Expect发送命令、等待提示再次出现、发送另一个命令等

如果您以前使用过Net::Telnet,那么Net::OpenSSH文档将解释如何集成这两者(尽管我不得不承认,这种组合并没有经过很好的测试)

此外,一些路由器提供了一些方法,可以跳转到一个完整的类Unix外壳。即,用“砰”一声预先挂起命令:

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

有几种可能性:

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

my $out = $ssh->capture({stdin_data => join("\r\n", @cmds, '')})
在其他情况下,您将不得不使用诸如Expect发送命令、等待提示再次出现、发送另一个命令等

如果您以前使用过Net::Telnet,那么Net::OpenSSH文档将解释如何集成这两者(尽管我不得不承认,这种组合并没有经过很好的测试)

此外,一些路由器提供了一些方法,可以跳转到一个完整的类Unix外壳。即,用“砰”一声预先挂起命令:

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

您还应该考虑向Net添加更多的参数::telnet -> NeW(),因为它与SSH而不是telnet服务器交互。

-telnetmode => 0
-output_record_separator => "\r",
-cmd_remove_mode => 1,
因为远程端没有TELNET服务器,-telnetmode=>0关闭TELNET协商

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


总是复制回音输入-CMDYReleVyMoL>= 1

< P>您还应该考虑向Net添加更多的参数::telnet -> NeW(),因为它与SSH而不是telnet服务器交互。
-telnetmode => 0
-output_record_separator => "\r",
-cmd_remove_mode => 1,
因为远程端没有TELNET服务器,-telnetmode=>0关闭TELNET协商

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


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

doc说您需要ssh版本>4.1 eval只是出于安全原因我不得不剥离的代码的工件。我删除了正在测试的内容,但忘了从帖子上删除evals。最终的代码要复杂得多,但根本问题如图所示。版本很好:OpenSSH_5.5p1 Debian-4ubuntu5,OpenSSL 0.9.8o 2010年6月1日我怀疑我将不得不将其包装起来,以期望它能实现我想要的功能\您可以尝试使用Net::SSH2@BillOpenSSH的pod中有一个提到了这个问题。显然,它为每个命令打开了一个新的连接。他们确实提到了一些可能的解决办法。我认为这回答了眼前的问题,但作为注释发布,因为它本身不是一个有效的修复程序。doc说您需要ssh版本>4.1评估只是出于安全原因我不得不删除的代码的工件。我删除了正在测试的内容,但忘了从帖子上删除evals。最终的代码要复杂得多,但根本问题如图所示。版本很好:OpenSSH_5.5p1 Debian-4ubuntu5,OpenSSL 0.9.8o 2010年6月1日我怀疑我将不得不将其包装起来,以期望它能实现我想要的功能\您可以尝试使用Net::SSH2@BillOpenSSH的pod中有一个提到了这个问题。显然,它为每个命令打开了一个新的连接。他们确实提到了一些可能的解决办法。我在想这个