解决 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 问题并将多个命令传递给路由器的主要内容,如果未能解决你的问题,请参考以下文章
perl Openssh ConnectionTimeout 不起作用