我正在努力移动一个将命令推送到路由器的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路由器配合使用。
答案 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");