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

Posted

技术标签:

【中文标题】解决 Net::OpenSSH 问题并将多个命令传递给路由器【英文标题】:Resolving issue with Net::OpenSSH and passing multiple commands to a router 【发布时间】:2014-01-04 11:48:03 【问题描述】:

我正在移动一个将命令推送到路由器的 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_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+[\$\%\#\>]\s0,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 路由器上运行良好。

【问题讨论】:

doc 说你需要 ssh 版本 > 4.1 评估只是代码的产物,出于安全原因,我不得不将其剥离。我删除了正在测试的内容,但忘记从帖子中删除评估。最终的代码要复杂得多,但根本问题如图所示。 版本很好:OpenSSH_5.5p1 Debian-4ubuntu5, OpenSSL 0.9.8o 2010 年 6 月 1 日 我的怀疑是我必须将它包装在 Expect 中才能让它做我想做的事. :\ 你可以尝试使用 Net::SSH2 @Bill Net::OpenSSH 的 pod 有一个 FAQ section 提到了这个问题。显然,它通过设计为每个命令打开了一个新连接。他们确实提到了一些可能的解决方法。我认为这回答了直接的问题,但作为评论发布,因为它本身不是一个有效的修复。 【参考方案1】:

有几种可能:

一些路由器接受通过标准输入预先发送命令序列:

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

在其他情况下,您将不得不使用 Expect 发送命令、等待提示再次出现、发送另一个命令等。

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

此外,一些路由器提供了某种方式来逃逸到一个完整的类 Unix 外壳。即,在命令前加上一个 bang:

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

【讨论】:

使用 Net::Telnet 似乎是正确的解决方案。我在上面发布了一个解决方案,虽然我还有一些障碍需要克服,但你的建议让我走上了正确的道路。【参考方案2】:

您还应该考虑向 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 如果可以的话,我会给你更多的选票。 Net::Telnet,尤其是 Net::Telnet::Cisco 的设置非常繁琐。这些选项结合起来立即解决了许多问题。很棒的建议

以上是关于解决 Net::OpenSSH 问题并将多个命令传递给路由器的主要内容,如果未能解决你的问题,请参考以下文章

Net::OpenSSH 中的交互模式

使用“start”命令并将参数传递给已启动的程序

perl Openssh ConnectionTimeout 不起作用

当我从命令行运行 exe 并将参数传递给它时,C# 索引超出范围[关闭]

第十一章 命令传参过滤器命令组合工具:xargs命令

运行多个 Powershell 命令并将结果导出到 csv