perl_n00b_01 has asked for the wisdom of the Perl Monks concerning the following question:

Please forgive my n00bness. I have spent hours searching the web for a solution today but alas, I am at the mercy of the gurus of Perl. I am attempting to setup a small script to parse details from two files, hosts file and command file, and then using Net::OpenSSH login to each of those devices and execute the commands listed in the command file. I parse the command file into an array, but when I do a foreach loop on the array to pass the values to the ssh->capture function, only the first commands gets through. The loop finishes, as I have put in some debug ouput so that I can see the return of the array, but the SSH session was closed after the first command. Here is the code:
sub ssh_to_ip_simple { my $timeout = 10; $Net::OpenSSH::debug = 0; #open command file and execute each line chomp ($user, $password, $host_ip); $ssh = Net::OpenSSH->new($host_ip, user => $user, password => $pas +sword); &parse_commands; foreach (@cmd_array) { print "Attempting to execute command $_ on host $host_ip"; $ssh->capture($_); print @output."\n"; } } sub parse_commands { #Open cmd file specified #Parse file and pass each line to the $ssh->system value if (defined $cmdfile) {} else {(print "\nCommand file not found.\n + Specify command file with -c option\n") && die} print "\nOpening command file $cmdfile\n"; open_cmd {}; for $line_cmd (<CMDFILE>) { chomp ($line_cmd); print "Processing command $line_cmd from file $cmdfile\n\n\n"; #my @output = $ssh->capture({timeout => 10 }, $line_cmd); #$ssh->error and warn "Operation didn't complete successfully: + \n".$ssh->error; push (@cmd_array, $line_cmd); } foreach (@cmd_array) {print "Parse_commands command is ".$_."\n"} close CMDFILE; }
Debugging output is listed below. I notice that the last 5 lines or so, show that my loop print statement execute, but when it tries to execute the $ssh->capture command, the session is DESTROYed and it errors out.
Executing subroutine ssh_to_ip # ctl_path: /home/tmcurti/.libnet-openssh-perl/cisco-10.250.10.100-442 +1-812531, ctl_dir: /home/tmcurti/.libnet-openssh-perl/ # _is_secure_path(dir: /home/tmcurti/.libnet-openssh-perl, file mode: +16832, file uid: 1000, euid: 1000 # _is_secure_path(dir: /home/tmcurti, file mode: 16877, file uid: 1000 +, euid: 1000 # set_error(0 - 0) # call args: ['ssh','-xMN','-o','NumberOfPasswordPrompts=1','-o','Pref +erredAuthentications=keyboard-interactive,password','-S','/home/tmcur +ti/.libnet-openssh-perl/cisco-10.250.10.100-4421-812531','-o','User=c +isco','--','10.250.10.100'] # passwd requested (Password:) # call args: ['ssh','-O','check','-T','-S','/home/tmcurti/.libnet-open +ssh-perl/cisco-10.250.10.100-4421-812531','-o','User=cisco','--','10. +250.10.100'] # open_ex: ['ssh','-O','check','-T','-S','/home/tmcurti/.libnet-openss +h-perl/cisco-10.250.10.100-4421-812531','-o','User=cisco','--','10.25 +0.10.100'] # io3 mloop, cin: 0, cout: 1, cerr: 0 # io3 fast, cin: 0, cout: 1, cerr: 0 # stdout, bytes read: 27 at offset 0 #> 4d 61 73 74 65 72 20 72 75 6e 6e 69 6e 67 20 28 70 69 64 3d 34 34 3 +2 32 29 0d 0a | Master running (pid=4422).. # io3 fast, cin: 0, cout: 1, cerr: 0 # stdout, bytes read: 0 at offset 27 # leaving _io3() # _waitpid(4424) => pid: 4424, rc: Opening command file cmd.txt Processing command show ver | inc uptime from file cmd.txt Processing command show run | inc interface.*5 from file cmd.txt Parse_commands command is show ver | inc uptime Parse_commands command is show run | inc interface.*5 Test array value is show ver | inc uptime Test array value is show run | inc interface.*5 # call args: ['ssh','-S','/home/tmcurti/.libnet-openssh-perl/cisco-10. +250.10.100-4421-812531','-o','User=cisco','--','10.250.10.100','show +ver | inc uptime'] # open_ex: ['ssh','-S','/home/tmcurti/.libnet-openssh-perl/cisco-10.25 +0.10.100-4421-812531','-o','User=cisco','--','10.250.10.100','show ve +r | inc uptime'] Attempting to execute command show ver | inc uptime on host 10.250.10. +100# io3 mloop, cin: 0, cout: 1, cerr: 0 # io3 fast, cin: 0, cout: 1, cerr: 0 # stdout, bytes read: 61 at offset 0 #> 20 63 73 2d 6c 61 62 36 35 30 36 20 75 70 74 69 6d 65 20 69 73 20 3 +4 20 77 65 65 6b 73 2c 20 34 | cs-lab6506 uptime is 4 weeks, 4 #> 20 64 61 79 73 2c 20 32 31 20 68 6f 75 72 73 2c 20 33 30 20 6d 69 6 +e 75 74 65 73 0d 0a | days, 21 hours, 30 minutes.. # io3 fast, cin: 0, cout: 1, cerr: 0 Connection to 10.250.10.100 closed by remote host. # stdout, bytes read: 0 at offset 61 # leaving _io3() # _waitpid(4425) => pid: 4425, rc: 0 # set_error(1 - master ssh connection broken) Attempting to execute command show run | inc interface.*5 on host 10.2 +50.10.1000 # DESTROY(Net::OpenSSH=HASH(0x913ed78), pid => 4422) # killing master # sending exit control to master # _kill_master: 4422 # waitpid(master: 4422) => pid: 4422, rc:
What do I have to do to keep the existing session alive while looping through the commands? Am I doing this correctly or is there an easier way to pipe the command file or array into the function? I have been able to successfully get this to work by calling the connection string during the loop, but based on what I read about the module I thoguht it would support this ability. Any help is greatly appreciated. Thank you. Tony

Replies are listed 'Best First'.
Re: Net::OpenSSH Multiple Commands - Session Destroyed
by salva (Canon) on Mar 17, 2011 at 09:28 UTC
    It seems that the SSH server is closing the connection after the first request. It may be a limitation of the device you are connecting to (unfortunately it is not to unusual to find defective SSH implementations in network devices and other embedded systems).

    Just to be sure that this is the problem, ask the module to run the master ssh client in verbose mode:

    $ssh = Net::OpenSSH->new($host_ip, user => $user, password => $passwor +d, master_opts => '-vv');
    Also, add an error check after the capture call:
    my @output = $ssh->capture($_); if ($ssh->error) { print STDERR "capture failed: " . $ssh->error; } else { print @output, "\n"; }
      Thank you for the quick feedback. I did what you suggested and the output is posted below.
      Executing subroutine ssh_to_ip OpenSSH_5.5p1 Debian-4ubuntu5, OpenSSL 0.9.8o 01 Jun 2010 debug1: Reading configuration data /etc/ssh/ssh_config debug1: Applying options for * debug2: ssh_connect: needpriv 0 debug1: Connecting to 10.250.10.100 [10.250.10.100] port 22. debug1: Connection established. debug1: identity file /home/tmcurti/.ssh/id_rsa type -1 debug1: identity file /home/tmcurti/.ssh/id_rsa-cert type -1 debug1: identity file /home/tmcurti/.ssh/id_dsa type -1 debug1: identity file /home/tmcurti/.ssh/id_dsa-cert type -1 debug1: Remote protocol version 1.99, remote software version Cisco-1. +25 debug1: no match: Cisco-1.25 debug1: Enabling compatibility mode for protocol 2.0 debug1: Local version string SSH-2.0-OpenSSH_5.5p1 Debian-4ubuntu5 debug2: fd 3 setting O_NONBLOCK debug1: SSH2_MSG_KEXINIT sent debug1: SSH2_MSG_KEXINIT received debug2: kex_parse_kexinit: diffie-hellman-group-exchange-sha256,diffie +-hellman-group-exchange-sha1,diffie-hellman-group14-sha1,diffie-hellm +an-group1-sha1 debug2: kex_parse_kexinit: ssh-rsa-cert-v00@openssh.com,ssh-dss-cert-v +00@openssh.com,ssh-rsa,ssh-dss debug2: kex_parse_kexinit: aes128-ctr,aes192-ctr,aes256-ctr,arcfour256 +,arcfour128,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,a +es256-cbc,arcfour,rijndael-cbc@lysator.liu.se debug2: kex_parse_kexinit: aes128-ctr,aes192-ctr,aes256-ctr,arcfour256 +,arcfour128,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,a +es256-cbc,arcfour,rijndael-cbc@lysator.liu.se debug2: kex_parse_kexinit: hmac-md5,hmac-sha1,umac-64@openssh.com,hmac +-ripemd160,hmac-ripemd160@openssh.com,hmac-sha1-96,hmac-md5-96 debug2: kex_parse_kexinit: hmac-md5,hmac-sha1,umac-64@openssh.com,hmac +-ripemd160,hmac-ripemd160@openssh.com,hmac-sha1-96,hmac-md5-96 debug2: kex_parse_kexinit: none,zlib@openssh.com,zlib debug2: kex_parse_kexinit: none,zlib@openssh.com,zlib debug2: kex_parse_kexinit: debug2: kex_parse_kexinit: debug2: kex_parse_kexinit: first_kex_follows 0 debug2: kex_parse_kexinit: reserved 0 debug2: kex_parse_kexinit: diffie-hellman-group1-sha1 debug2: kex_parse_kexinit: ssh-rsa debug2: kex_parse_kexinit: aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc debug2: kex_parse_kexinit: aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc debug2: kex_parse_kexinit: hmac-sha1,hmac-sha1-96,hmac-md5,hmac-md5-96 debug2: kex_parse_kexinit: hmac-sha1,hmac-sha1-96,hmac-md5,hmac-md5-96 debug2: kex_parse_kexinit: none debug2: kex_parse_kexinit: none debug2: kex_parse_kexinit: debug2: kex_parse_kexinit: debug2: kex_parse_kexinit: first_kex_follows 0 debug2: kex_parse_kexinit: reserved 0 debug2: mac_setup: found hmac-md5 debug1: kex: server->client aes128-cbc hmac-md5 none debug2: mac_setup: found hmac-md5 debug1: kex: client->server aes128-cbc hmac-md5 none debug2: dh_gen_key: priv key bits set: 124/256 debug2: bits set: 492/1024 debug1: sending SSH2_MSG_KEXDH_INIT debug1: expecting SSH2_MSG_KEXDH_REPLY debug1: Host '10.250.10.100' is known and matches the RSA host key. debug1: Found key in /home/tmcurti/.ssh/known_hosts:5 debug2: bits set: 506/1024 debug1: ssh_rsa_verify: signature correct debug2: kex_derive_keys debug2: set_newkeys: mode 1 debug1: SSH2_MSG_NEWKEYS sent debug1: expecting SSH2_MSG_NEWKEYS debug2: set_newkeys: mode 0 debug1: SSH2_MSG_NEWKEYS received debug1: Roaming not allowed by server debug1: SSH2_MSG_SERVICE_REQUEST sent debug2: service_accept: ssh-userauth debug1: SSH2_MSG_SERVICE_ACCEPT received debug2: key: /home/tmcurti/.ssh/id_rsa ((nil)) debug2: key: /home/tmcurti/.ssh/id_dsa ((nil)) debug1: Authentications that can continue: keyboard-interactive,passwo +rd debug1: Next authentication method: keyboard-interactive debug2: userauth_kbdint debug2: we sent a keyboard-interactive packet, wait for reply debug2: input_userauth_info_req debug2: input_userauth_info_req: num_prompts 1 debug1: Authentication succeeded (keyboard-interactive). debug1: setting up multiplex master socket debug2: fd 4 setting O_NONBLOCK debug1: channel 0: new [/home/tmcurti/.libnet-openssh-perl/cisco-10.25 +0.10.100-2449-484163] debug1: Entering interactive session. debug1: multiplexing control connection debug2: fd 5 setting O_NONBLOCK debug1: channel 1: new [mux-control] debug2: process_mux_master_hello: channel 1 slave version 4 debug2: process_mux_alive_check: channel 1: alive check debug2: channel 1: ctl read<=0 rfd 5 len 0 debug2: channel 1: read failed debug2: channel 1: close_read debug2: channel 1: input open -> drain debug2: channel 1: ibuf empty debug2: channel 1: input drain -> closed debug2: channel 1: rcvd close debug2: channel 1: output open -> drain debug2: channel 1: obuf empty debug2: channel 1: close_write debug2: channel 1: output drain -> closed debug2: channel 1: is dead (local) debug2: channel 1: gc: notify user debug2: channel 1: gc: user detached debug2: channel 1: is dead (local) debug2: channel 1: garbage collecting debug1: channel 1: free: mux-control, nchannels 2 Opening command file cmd.txt Executing command show run | inc nterface.*5 on device 10.250.10.100 Executing command show ver | inc uptime on device 10.250.10.100 Attempting to execute command show run | inc nterface.*5 on host 10.25 +0.10.100debug1: multiplexing control connection debug2: fd 5 setting O_NONBLOCK debug1: channel 1: new [mux-control] debug2: process_mux_master_hello: channel 1 slave version 4 debug2: process_mux_alive_check: channel 1: alive check debug2: process_mux_new_session: channel 1: request tty 0, X 0, agent +0, subsys 0, term "xterm", cmd "show run | inc nterface.*5", env 1 debug2: fd 7 setting O_NONBLOCK debug1: channel 2: new [client-session] debug2: process_mux_new_session: channel_new: 2 linked to control chan +nel 1 debug2: channel 2: send open debug2: callback start debug2: client_session2_setup: id 2 debug1: Sending environment. debug1: Sending env LANG = en_US.utf8 debug2: channel 2: request env confirm 0 debug1: Sending command: show run | inc nterface.*5 debug2: channel 2: request exec confirm 1 debug2: callback done debug2: channel 2: open confirm rwindow 1024 rmax 4096 debug2: channel_input_status_confirm: type 99 id 2 debug2: exec request accepted on channel 2 debug1: client_input_channel_req: channel 2 rtype exit-status reply 0 debug2: channel 2: rcvd eof debug2: channel 2: output open -> drain debug2: channel 2: obuf empty debug2: channel 2: close_write debug2: channel 2: output drain -> closed debug2: channel 2: rcvd close debug2: channel 2: close_read debug2: channel 2: input open -> closed debug2: channel 2: almost dead debug2: channel 2: gc: notify user debug2: channel 1: rcvd close debug2: channel 1: output open -> drain debug2: channel 1: close_read debug2: channel 1: input open -> closed debug2: channel 2: gc: user detached debug2: channel 2: send close debug2: channel 2: is dead debug2: channel 2: garbage collecting debug1: channel 2: free: client-session, nchannels 3 debug1: channel 0: free: /home/tmcurti/.libnet-openssh-perl/cisco-10.2 +50.10.100-2449-484163, nchannels 2 debug1: channel 1: free: mux-control, nchannels 1 Connection to 10.250.10.100 closed by remote host. Transferred: sent 1456, received 1256 bytes, in 1.2 seconds Bytes per second: sent 1178.8, received 1016.9 debug1: Exit status -1 7 capture failed: master ssh connection brokenAttempting to execute comm +and show ver | inc uptime on host 10.250.10.100
      I see the master connection is broken but do not understand from the output why it was broken. Any ideas?
        It seems that the SSH server on your particular device is not able to handle several requests over the same connection.

        I can think of two workarounds for that:

        1. Open a new connection for every request
        2. Run a full interactive session on the server and use Expect to drive it (you can also try with Net::SSH::Expect or any of the other modules targeting specific Cisco devices).