lib/chef_metal/transport/ssh.rb in chef-metal-0.13 vs lib/chef_metal/transport/ssh.rb in chef-metal-0.14
- old
+ new
@@ -1,6 +1,7 @@
require 'chef_metal/transport'
+require 'chef/log'
require 'uri'
require 'socket'
require 'timeout'
require 'net/ssh'
require 'net/scp'
@@ -106,18 +107,24 @@
end
end
def make_url_available_to_remote(local_url)
uri = URI(local_url)
- host = Socket.getaddrinfo(uri.host, uri.scheme, nil, :STREAM)[0][3]
- if host == '127.0.0.1' || host == '::1'
- unless session.forward.active_remotes.any? { |port, bind| port == uri.port && bind == uri.host }
- Chef::Log.debug("Forwarding local server #{uri.host}:#{uri.port} to port #{uri.port} on #{username}@#{self.host}")
- session.forward.remote(uri.port, uri.host, uri.port)
+ if is_local_machine(uri.host)
+ port, host = forward_port(uri.port, uri.host, uri.port, 'localhost')
+ if !port
+ # Try harder if the port is already taken
+ port, host = forward_port(uri.port, uri.host, 0, 'localhost')
+ if !port
+ raise "Error forwarding port: could not forward #{uri.port} or 0"
+ end
end
+ uri.host = host
+ uri.port = port
end
- local_url
+ Chef::Log.info("Port forwarded: local URL #{local_url} is available to #{self.host} as #{uri.to_s} for the duration of this SSH connection.")
+ uri.to_s
end
def disconnect
if @session
begin
@@ -228,9 +235,53 @@
begin
Net::SSH::Gateway.new(gw_host, gw_user, ssh_start_opts)
rescue Errno::ETIMEDOUT
Chef::Log.debug("Timed out connecting to gateway: #{$!}")
raise InitialConnectTimeout.new($!)
+ end
+ end
+
+ def is_local_machine(host)
+ local_addrs = Socket.ip_address_list
+ host_addrs = Addrinfo.getaddrinfo(host, nil)
+ local_addrs.any? do |local_addr|
+ host_addrs.any? do |host_addr|
+ local_addr.ip_address == host_addr.ip_address
+ end
+ end
+ end
+
+ # Forwards a port over the connection, and returns the
+ def forward_port(local_port, local_host, remote_port, remote_host)
+ # This bit is from the documentation.
+ if session.forward.respond_to?(:active_remote_destinations)
+ got_remote_port, remote_host = session.forward.active_remote_destinations[[local_port, local_host]]
+ if !got_remote_port
+ Chef::Log.debug("Forwarding local server #{local_host}:#{local_port} to #{username}@#{self.host}")
+
+ session.forward.remote(local_port, local_host, remote_port, remote_host) do |actual_remote_port|
+ got_remote_port = actual_remote_port || :error
+ :no_exception # I'll take care of it myself, thanks
+ end
+ # Kick SSH until we get a response
+ session.loop { !got_remote_port }
+ if got_remote_port == :error
+ return nil
+ end
+ end
+ [ got_remote_port, remote_host ]
+ else
+ @forwarded_ports ||= {}
+ remote_port, remote_host = @forwarded_ports[[local_port, local_host]]
+ if !remote_port
+ Chef::Log.debug("Forwarding local server #{local_host}:#{local_port} to #{username}@#{self.host}")
+ old_active_remotes = session.forward.active_remotes
+ session.forward.remote(local_port, local_host, local_port)
+ session.loop { !(session.forward.active_remotes.length > old_active_remotes.length) }
+ remote_port, remote_host = (session.forward.active_remotes - old_active_remotes).first
+ @forwarded_ports[[local_port, local_host]] = [ remote_port, remote_host ]
+ end
+ [ remote_port, remote_host ]
end
end
end
end
end