4

我一直在尝试让端口转发与 Net::SSH 一起正常工作。据我了解,如果我希望能够从同一个 Ruby 程序中使用 Net::SSH 会话,我需要分叉出它,以便事件处理循环实际上可以处理通过连接发送的数据包。但是,这会导致您在下面看到的丑陋:

#!/usr/bin/env ruby -w
require 'net/ssh'
require 'httparty'
require 'socket'
include Process

log = Logger.new(STDOUT)
log.level = Logger::DEBUG

local_port = 2006
child_socket, parent_socket = Socket.pair(:UNIX, :DGRAM, 0)
maxlen = 1000
hostname = "www.example.com"

pid = fork do
  parent_socket.close
  Net::SSH.start("hostname", "username") do |session|
    session.logger = log
    session.logger.sev_threshold=Logger::Severity::DEBUG
    session.forward.local(local_port, hostname, 80)
    child_socket.send("ready", 0)
    pidi = fork do
      msg = child_socket.recv(maxlen)
      puts "Message from parent was: #{msg}"
      exit
    end
    session.loop do
      status = waitpid(pidi, Process::WNOHANG)
      puts "Status: #{status.inspect}"
      status.nil?
    end
  end
end

child_socket.close

puts "Message from child: #{parent_socket.recv(maxlen)}"
resp = HTTParty.post("http://localhost:#{local_port}/", :headers => { "Host" => hostname } )
# the write cannot be the last statement, otherwise the child pid could end up
# not receiving it
parent_socket.write("done")
puts resp.inspect

任何人都可以向我展示一个更优雅/更好的工作解决方案吗?

4

3 回答 3

5

我花了很多时间试图弄清楚如何正确实现端口转发,然后我从 net/ssh/gateway 库中获得了灵感。我需要一个强大的解决方案,可以在各种可能的连接错误后工作。这是我现在正在使用的,希望它有帮助:

require 'net/ssh'

ssh_options = ['host', 'login', :password => 'password']
tunnel_port = 2222
begin
  run_tunnel_thread = true
  tunnel_mutex = Mutex.new
  ssh = Net::SSH.start *ssh_options
  tunnel_thread = Thread.new do
    begin
      while run_tunnel_thread do
        tunnel_mutex.synchronize { ssh.process 0.01 }
        Thread.pass
      end
    rescue => exc
      puts "tunnel thread error: #{exc.message}"
    end
  end
  tunnel_mutex.synchronize do
    ssh.forward.local tunnel_port, 'tunnel_host', 22
  end

  begin
    ssh_tunnel = Net::SSH.start 'localhost', 'tunnel_login', :password => 'tunnel_password', :port => tunnel_port
    puts ssh_tunnel.exec! 'date'
  rescue => exc
    puts "tunnel connection error: #{exc.message}"
  ensure
    ssh_tunnel.close if ssh_tunnel
  end

  tunnel_mutex.synchronize do
    ssh.forward.cancel_local tunnel_port
  end
rescue => exc
  puts "tunnel error: #{exc.message}"
ensure
  run_tunnel_thread = false
  tunnel_thread.join if tunnel_thread
  ssh.close if ssh
end
于 2013-06-18T09:37:43.557 回答
0

这就是 SSH 的一般情况。如果您对它的丑陋程度感到不快,您可能应该将该功能包装到某种端口转发类中,以便暴露的部分更加简洁。像这样的界面,也许:

forwarder = PortForwarder.new(8080, 'remote.host', 80)
于 2012-12-21T23:31:21.677 回答
0

所以我找到了一个更好的实现。它只需要一个分叉,但仍然使用套接字进行通信。它IO#read_nonblock用于检查消息是否准备好。如果没有,该方法将引发异常,在这种情况下,块继续返回 true,并且 SSH 会话继续为请求提供服务。一旦父级完成连接,它会发送一条消息,导致child_socket.read_nonblock(maxlen).nil?返回 false,使循环退出,从而关闭 SSH 连接。

我对此感觉好多了,所以在这和@tadman 的建议之间,将它包装在一个端口转发类中,我认为它已经达到了最好的水平。但是,非常欢迎任何进一步的改进建议。

#!/usr/bin/env ruby -w
require 'net/ssh'
require 'httparty'
require 'socket'

log = Logger.new(STDOUT)
log.level = Logger::DEBUG

local_port = 2006
child_socket, parent_socket = Socket.pair(:UNIX, :DGRAM, 0)
maxlen = 1000
hostname = "www.example.com"

pid = fork do
  parent_socket.close
  Net::SSH.start("ssh-tunnel-hostname", "username") do |session|
    session.logger = log
    session.logger.sev_threshold=Logger::Severity::DEBUG
    session.forward.local(local_port, hostname, 80)
    child_socket.send("ready", 0)
    session.loop { child_socket.read_nonblock(maxlen).nil? rescue true }
  end
end

child_socket.close

puts "Message from child: #{parent_socket.recv(maxlen)}"
resp = HTTParty.post("http://localhost:#{local_port}/", :headers => { "Host" => hostname } )
# the write cannot be the last statement, otherwise the child pid could end up
# not receiving it
parent_socket.write("done")
puts resp.inspect
于 2012-12-23T15:05:20.063 回答