class Net::SSH::Connection::Session

end
ssh.exec! “/etc/init.d/some_process start”
# ‘ssh’ is an instance of Net::SSH::Connection::Session
Net::SSH.start(“localhost”, “user”) do |ssh|
all in one call.
connection, authenticate a user, and return a new connection session,
you’ll almost always use Net::SSH.start to initialize a new network
You will rarely (if ever) need to instantiate this class directly; rather,
port forwarding, SFTP, SCP, etc.).
and serves as a central point-of-reference for all SSH-related services (e.g.
It also encapsulates the SSH event loop (via #loop and #process),
#open_channel), and the dispatching of messages to the various channels.
the SSH transport layer. It manages the creation of channels (see
A session class representing the connection service running on top of

def [](key)

SSH connections.
store additional state in applications that must manage multiple
Retrieves a custom property from this instance. This can be used to
def [](key)
  @properties[key]
end

def []=(key, value)

Sets a custom property for this instance.
def []=(key, value)
  @properties[key] = value
end

def busy?(include_invisible = false)

ssh.loop { ssh.busy? }

to be run.
This can be useful for determining whether the event loop should continue

a +true+ value for +include_invisible+, then those will be counted.
(such as those created by forwarding ports and such), but if you pass
session. By default, this will not include "invisible" channels
Returns +true+ if there are any channels currently active on this
def busy?(include_invisible = false)
  if include_invisible
    channels.any?
  else
    channels.any? { |id, ch| !ch[:invisible] }
  end
end

def channel_close(packet)

def channel_close(packet)
  info { "channel_close: #{packet[:local_id]}" }
  channel = channels[packet[:local_id]]
  channel_closed(channel)
end

def channel_closed(channel)

def channel_closed(channel)
  channel.remote_closed!
  channel.close
  cleanup_channel(channel)
  channel.do_close
end

def channel_data(packet)

def channel_data(packet)
  info { "channel_data: #{packet[:local_id]} #{packet[:data].length}b" }
  channels[packet[:local_id]].do_data(packet[:data])
end

def channel_eof(packet)

def channel_eof(packet)
  info { "channel_eof: #{packet[:local_id]}" }
  channels[packet[:local_id]].do_eof
end

def channel_extended_data(packet)

def channel_extended_data(packet)
  info { "channel_extended_data: #{packet[:local_id]} #{packet[:data_type]} #{packet[:data].length}b" }
  channels[packet[:local_id]].do_extended_data(packet[:data_type], packet[:data])
end

def channel_failure(packet)

def channel_failure(packet)
  info { "channel_failure: #{packet[:local_id]}" }
  channels[packet[:local_id]].do_failure
end

def channel_open(packet)

accordingly.
is returned, otherwise the callback is invoked and everything proceeds
channel handler exists for the given channel type, CHANNEL_OPEN_FAILURE
Called when the server wants to open a channel. If no registered
def channel_open(packet)
  info { "channel open #{packet[:channel_type]}" }
  local_id = get_next_channel_id
  channel = Channel.new(self, packet[:channel_type], local_id, @max_pkt_size, @max_win_size)
  channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size])
  callback = channel_open_handlers[packet[:channel_type]]
  if callback
    begin
      callback[self, channel, packet]
    rescue ChannelOpenFailed => err
      failure = [err.code, err.reason]
    else
      channels[local_id] = channel
      msg = Buffer.from(:byte, CHANNEL_OPEN_CONFIRMATION, :long, channel.remote_id, :long,
                        channel.local_id, :long, channel.local_maximum_window_size, :long,
                        channel.local_maximum_packet_size)
    end
  else
    failure = [3, "unknown channel type #{channel.type}"]
  end
  if failure
    error { failure.inspect }
    msg = Buffer.from(:byte, CHANNEL_OPEN_FAILURE, :long, channel.remote_id, :long, failure[0], :string, failure[1], :string, "")
  end
  send_message(msg)
end

def channel_open_confirmation(packet)

def channel_open_confirmation(packet)
  info { "channel_open_confirmation: #{packet[:local_id]} #{packet[:remote_id]} #{packet[:window_size]} #{packet[:packet_size]}" }
  channel = channels[packet[:local_id]]
  channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size])
end

def channel_open_failure(packet)

def channel_open_failure(packet)
  error { "channel_open_failed: #{packet[:local_id]} #{packet[:reason_code]} #{packet[:description]}" }
  channel = channels.delete(packet[:local_id])
  channel.do_open_failed(packet[:reason_code], packet[:description])
end

def channel_request(packet)

def channel_request(packet)
  info { "channel_request: #{packet[:local_id]} #{packet[:request]} #{packet[:want_reply]}" }
  channels[packet[:local_id]].do_request(packet[:request], packet[:want_reply], packet[:request_data])
end

def channel_success(packet)

def channel_success(packet)
  info { "channel_success: #{packet[:local_id]}" }
  channels[packet[:local_id]].do_success
end

def channel_window_adjust(packet)

def channel_window_adjust(packet)
  info { "channel_window_adjust: #{packet[:local_id]} +#{packet[:extra_bytes]}" }
  channels[packet[:local_id]].do_window_adjust(packet[:extra_bytes])
end

def cleanup_channel(channel)

def cleanup_channel(channel)
  if channel.local_closed? and channel.remote_closed?
    info { "#{host} delete channel #{channel.local_id} which closed locally and remotely" }
    channels.delete(channel.local_id)
  end
end

def close

connection.
successfully closed, and then closes the underlying transport layer
Closes the session gracefully, blocking until all channels have
def close
  info { "closing remaining channels (#{channels.length} open)" }
  channels.each { |id, channel| channel.close }
  begin
    loop(0.1) { channels.any? }
  rescue Net::SSH::Disconnect
    raise unless channels.empty?
  end
  transport.close
end

def closed?

be useful if you just want to know if _you_ have closed the connection.
until the next operation on the socket. Nevertheless, this method can
closed the connection, the local end will still think it is open
this can be a little misleading, since if the remote server has
Returns true if the underlying transport has been closed. Note that
def closed?
  transport.closed?
end

def dispatch_incoming_packets(raise_disconnect_errors: true)

appropriate. Returns as soon as there are no more pending packets.
Read all pending packets from the connection and dispatch them as
def dispatch_incoming_packets(raise_disconnect_errors: true)
  while packet = transport.poll_message
    raise Net::SSH::Exception, "unexpected response #{packet.type} (#{packet.inspect})" unless MAP.key?(packet.type)
    send(MAP[packet.type], packet)
  end
rescue StandardError
  force_channel_cleanup_on_close if closed?
  raise if raise_disconnect_errors || !$!.is_a?(Net::SSH::Disconnect)
end

def each_channel(&block)

iterate channels with the posibility of callbacks opening new channels during the iteration
def each_channel(&block)
  channels.dup.each(&block)
end

def ev_do_calculate_rw_wait(wait)

we also return the max wait
Returns the file descriptors the event loop should wait for read/write events,
def ev_do_calculate_rw_wait(wait)
  r = listeners.keys
  w = r.select { |w2| w2.respond_to?(:pending_write?) && w2.pending_write? }
  [r, w, io_select_wait(wait)]
end

def ev_do_handle_events(readers, writers)

transport layer to rekey. Then returns true.
then calls Net::SSH::Transport::Session#rekey_as_needed to allow the
processing them as needed, and
It loops over the given arrays of reader IO's and writer IO's,
def ev_do_handle_events(readers, writers)
  Array(readers).each do |reader|
    if listeners[reader]
      listeners[reader].call(reader)
    else
      if reader.fill.zero?
        reader.close
        stop_listening_to(reader)
      end
    end
  end
  Array(writers).each do |writer|
    writer.send_pending
  end
end

def ev_do_postprocess(was_events)

transport layer to rekey
calls Net::SSH::Transport::Session#rekey_as_needed to allow the
def ev_do_postprocess(was_events)
  @keepalive.send_as_needed(was_events)
  transport.rekey_as_needed
  true
end

def ev_preprocess(&block)

event multiplexing
Called by event loop to process available data before going to
def ev_preprocess(&block)
  dispatch_incoming_packets(raise_disconnect_errors: false)
  each_channel { |id, channel| channel.process unless channel.local_closed? }
end

def exec(command, status: nil, &block)

end
end
puts data
else
puts "ERROR: #{data}"
if stream == :stderr
ssh.exec "grep something /some/files" do |ch, stream, data|

callbacks. However, for most uses, this will be sufficient.
Net::SSH::Connection::Channel#exec, and then setting up the channel
This is effectively identical to calling #open_channel, and then

(see Session#loop) in order for the command to actually execute.
Note that this method returns immediately, and requires an event loop

(:stdout or :stderr), and the data (as a string).
arguments: the channel object, a symbol indicating the data type
the block is called for each data and extended data packet, with three
no block is given, all output is printed via $stdout and $stderr. Otherwise,
A convenience method for executing a command and interacting with it. If
def exec(command, status: nil, &block)
  open_channel do |channel|
    channel.exec(command) do |ch, success|
      raise "could not execute command: #{command.inspect}" unless success
      if status
        channel.on_request("exit-status") do |ch2, data|
          status[:exit_code] = data.read_long
        end
        channel.on_request("exit-signal") do |ch2, data|
          status[:exit_signal] = data.read_long
        end
      end
      channel.on_data do |ch2, data|
        if block
          block.call(ch2, :stdout, data)
        else
          $stdout.print(data)
        end
      end
      channel.on_extended_data do |ch2, type, data|
        if block
          block.call(ch2, :stderr, data)
        else
          $stderr.print(data)
        end
      end
    end
  end
end

def exec!(command, status: nil, &block)

the returned string has an exitstatus method to query its exit status

matches = ssh.exec!("grep something /some/files")

as a single string.
if no block is given, this will return all output (stdout and stderr)
Same as #exec, except this will block until the command finishes. Also,
def exec!(command, status: nil, &block)
  block_or_concat = block || Proc.new do |ch, type, data|
    ch[:result] ||= String.new
    ch[:result] << data
  end
  status ||= {}
  channel = exec(command, status: status, &block_or_concat)
  channel.wait
  channel[:result] ||= String.new unless block
  channel[:result] &&= channel[:result].force_encoding("UTF-8") unless block
  StringWithExitstatus.new(channel[:result], status[:exit_code]) if channel[:result]
end

def force_channel_cleanup_on_close

def force_channel_cleanup_on_close
  channels.each do |id, channel|
    channel_closed(channel)
  end
end

def forward

be used for forwarding ports over SSH.
Returns a reference to the Net::SSH::Service::Forward service, which can
def forward
  @forward ||= Service::Forward.new(self)
end

def get_next_channel_id

the counter.
Returns the next available channel id to be assigned, and increments
def get_next_channel_id
  @channel_id_counter += 1
end

def global_request(packet)

reply returned.
request callback will be invoked, if one exists, and the necessary
Invoked when a global request is received. The registered global
def global_request(packet)
  info { "global request received: #{packet[:request_type]} #{packet[:want_reply]}" }
  callback = @on_global_request[packet[:request_type]]
  result = callback ? callback.call(packet[:request_data], packet[:want_reply]) : false
  if result != :sent && result != true && result != false
    raise "expected global request handler for `#{packet[:request_type]}' to return true, false, or :sent, but got #{result.inspect}"
  end
  if packet[:want_reply] && result != :sent
    msg = Buffer.from(:byte, result ? REQUEST_SUCCESS : REQUEST_FAILURE)
    send_message(msg)
  end
end

def host

connect to.
Returns the name of the host that was given to the transport layer to
def host
  transport.host
end

def initialize(transport, options = {})

layer. Initializes the listeners to be only the underlying socket object.
Create a new connection service instance atop the given transport
def initialize(transport, options = {})
  self.logger = transport.logger
  @transport = transport
  @options = options
  @channel_id_counter = -1
  @channels = Hash.new(NilChannel.new(self))
  @listeners = { transport.socket => nil }
  @pending_requests = []
  @channel_open_handlers = {}
  @on_global_request = {}
  @properties = (options[:properties] || {}).dup
  @max_pkt_size = (options.key?(:max_pkt_size) ? options[:max_pkt_size] : 0x8000)
  @max_win_size = (options.key?(:max_win_size) ? options[:max_win_size] : 0x20000)
  @keepalive = Keepalive.new(self)
  @event_loop = options[:event_loop] || SingleSessionEventLoop.new
  @event_loop.register(self)
end

def io_select_wait(wait)

def io_select_wait(wait)
  [wait, max_select_wait_time].compact.min
end

def listen_to(io, &callback)

channel.wait

end
end
end
io.close
ssh.stop_listening_to(io)
ch.on_close do

end
end
ch.send_data(io.read_available)
if io.available > 0
ch.on_process do

ssh.listen_to(io)
io.extend(Net::SSH::BufferedIo)
io = TCPSocket.new(somewhere, port)

abort "can't execute!" unless success
ch.exec "/some/process/that/wants/input" do |ch, success|
channel = ssh.open_channel do |ch|

remote process' stdin stream:
a socket to somewhere, and then pipes data from that socket to the
The following example executes a process on the remote server, opens

object.
Net::SSH::BufferedIo functionality, typically by calling #extend on the
Any +io+ value passed to this method _must_ have mixed into it the

the io will merely have its #fill method invoked.
is given, it will be invoked when the io is ready to be read, otherwise,
Adds an IO object for the event loop to listen to. If a callback
def listen_to(io, &callback)
  listeners[io] = callback
end

def loop(wait = nil, &block)

ssh.loop(0.1) { not int_pressed }
trap("INT") { int_pressed = true }
int_pressed = false
# loop until ctrl-C is pressed

ssh.loop(0.1)
# the event loop runs at least once per 0.1 second
# loop for as long as there are any channels active, but make sure

ssh.loop
# loop for as long as there are any channels active

interpreted as the maximum number of seconds to wait for IO.select to return).
The # +wait+ parameter is also passed through to #process (where it is
used that just returns true if there are any channels active (see #busy?).
block is given, it is passed to #process, otherwise a default proc is
The main event loop. Calls #process until #process returns false. If a
def loop(wait = nil, &block)
  running = block || Proc.new { busy? }
  loop_forever { break unless process(wait, &running) }
  begin
    process(0)
  rescue IOError => e
    if e.message =~ /closed/
      debug { "stream was closed after loop => shallowing exception so it will be re-raised in next loop" }
    else
      raise
    end
  end
end

def max_select_wait_time

pass between callbacks.
periodically, this method returns the maximum number of seconds which may
If the #preprocess and #postprocess callbacks for this session need to run
def max_select_wait_time
  @keepalive.interval if @keepalive.enabled?
end

def on_global_request(type, &block)

if it returns false, REQUEST_FAILURE will be sent.
:sent. Otherwise, if it returns true, REQUEST_SUCCESS will be sent, and
is required). If the callback sends the response, it should return
parameter, and true/false as the second (indicating whether a response
of the given type. The callback receives the request data as the first
Registers a handler to be invoked when the server sends a global request
def on_global_request(type, &block)
  old, @on_global_request[type] = @on_global_request[type], block
  old
end

def on_open_channel(type, &block)

welcome to register handlers for other channel types, as needed.
when a remote forwarded port receives a connection. However, you are
This is used by the Net::SSH::Service::Forward service to open a channel

sent to the server.
reason. Otherwise, the channel will be opened and a confirmation message
raise ChannelOpenFailed if it is unable to open the channel for some
the new channel object, and the packet itself as arguments, and should
channel on the client. The callback receives the connection object,
Registers a handler to be invoked when the server wants to open a
def on_open_channel(type, &block)
  channel_open_handlers[type] = block
end

def open_channel(type = "session", *extra, &on_confirm)

channel.wait

end
end
...
ch.exec "grep something /some/files" do |ch, success|
channel = ssh.open_channel do |ch|

data is if you were implementing an SSH extension.
time you'd want to set the channel type or pass additional initialization
In general, you'll use #open_channel without any arguments; the only

for the callback is the channel object itself.
the server confirms that the channel opened successfully. The sole parameter
Net::SSH::Buffer.from. If a callback is given, it will be invoked when
must be even in number and conform to the same format as described for
of the channel types supported by the SSH protocol. The +extra+ parameters
of type "session", but if you know what you're doing you can select any
Requests that a new channel be opened. By default, the channel will be
def open_channel(type = "session", *extra, &on_confirm)
  local_id = get_next_channel_id
  channel = Channel.new(self, type, local_id, @max_pkt_size, @max_win_size, &on_confirm)
  msg = Buffer.from(:byte, CHANNEL_OPEN, :string, type, :long, local_id,
                    :long, channel.local_maximum_window_size,
                    :long, channel.local_maximum_packet_size, *extra)
  send_message(msg)
  channels[local_id] = channel
end

def postprocess(readers, writers)

This is called internally as part of #process.
def postprocess(readers, writers)
  ev_do_handle_events(readers, writers)
end

def preprocess(&block)

false, this method returns false. Otherwise, it returns true.
start of the method and again at the end, and if the block ever returns
for any active channels. If a block is given, it is invoked at the
available incoming packets, and then runs Net::SSH::Connection::Channel#process
This is called internally as part of #process. It dispatches any
def preprocess(&block)
  return false if block_given? && !yield(self)
  ev_preprocess(&block)
  return false if block_given? && !yield(self)
  return true
end

def process(wait = nil, &block)

end
break if connections.empty?
connections.delete_if { |ssh| !ssh.process(0.1, &condition) }
loop do

condition = Proc.new { |s| s.busy? }

end
ssh.exec "grep something /in/some/files"
connections.each do |ssh|

]
Net::SSH.start("host2", ...)
Net::SSH.start("host1", ...),
connections = [
# process multiple Net::SSH connections in parallel

TODO revise example

Net::SSH::Connection::Channel#on_process).
This will also cause all active channels to be processed once each (see

frequently it can make your CPU quite busy!
Passing 0 is a good way to poll the connection, but if you do it too
to indicate that it should block for no more than that many seconds.
it to not block, you can pass 0, or you can pass any other numeric value
monitored IO objects are ready to be read from or written to. If you want
If +wait+ is nil (the default), this method will block until any of the

only argument.
#process returns true. The session itself is yielded to the block as its
should abort, which causes #process to return false. Otherwise,
loop. If a block is given, it should return false when the processing
The core of the event loop. It processes a single iteration of the event
def process(wait = nil, &block)
  @event_loop.process(wait, &block)
rescue StandardError
  force_channel_cleanup_on_close if closed?
  raise
end

def request_failure(packet)

Invokes the next pending request callback with +false+.
def request_failure(packet)
  info { "global request failure" }
  callback = pending_requests.shift
  callback.call(false, packet) if callback
end

def request_success(packet)

Invokes the next pending request callback with +true+.
def request_success(packet)
  info { "global request success" }
  callback = pending_requests.shift
  callback.call(true, packet) if callback
end

def send_global_request(type, *extra, &callback)

ssh.send_global_request("keep-alive@openssh.com")

this method is available to you.
send a global request that isn't explicitly handled by Net::SSH, and so
class, for instance). However, there may be times when you need to
(e.g. port forward requests and such are handled in the Net::SSH::Service::Forward
Generally, Net::SSH will manage global requests that need to be sent

being the packet itself.
first parameter being true or false (success, or failure), and the second
success or failure is indicated by the callback being invoked, with the
to respond and indicate whether the request was successful or not. This
not require a response from the server, otherwise the server is required
Net::SSH::Buffer.from. If a callback is not specified, the request will
be even in number, and conform to the same format as described for
Send a global request of the given type. The +extra+ parameters must
def send_global_request(type, *extra, &callback)
  info { "sending global request #{type}" }
  msg = Buffer.from(:byte, GLOBAL_REQUEST, :string, type.to_s, :bool, !callback.nil?, *extra)
  send_message(msg)
  pending_requests << callback if callback
  self
end

def send_message(message)

ssh.send_message(Buffer.from(:byte, REQUEST_SUCCESS).to_s)

use this to send it.
need to send a packet that Net::SSH does not directly support, you can
if you are implementing an extension to the SSH protocol, or if you
available for writing. Most programs will never need to call this, but
Enqueues a message to be sent to the server as soon as the socket is
def send_message(message)
  transport.enqueue_message(message)
end

def shutdown!

underlying protocol's state).
when the connection needs to close but you don't know the status of the
never be done, but it might be necessary (in a rescue clause, for instance,
Performs a "hard" shutdown of the connection. In general, this should
def shutdown!
  transport.shutdown!
end

def stop_listening_to(io)

event loop will no longer monitor it.
Removes the given io object from the listeners collection, so that the
def stop_listening_to(io)
  listeners.delete(io)
end