The Forward::Driver class manages ports that have been forwarded. It will open a socket on each local port and listen for local connections, forwarding those connections over an SSH channel to the other end.
[R] | direct_channel_count | The number of direct (local-to-remote) channels that have been opened. |
[R] | open_direct_channel_count | The number of direct (local-to-remote) channels that are currently open. |
Create a new Driver instance.
[ show source ]
# File lib/net/ssh/service/forward/driver.rb, line 39 39: def initialize( connection, buffers, log, handlers ) 40: @connection = connection 41: @buffers = buffers 42: @log = log 43: @handlers = handlers 44: 45: @local_forwards = Hash.new 46: @remote_forwards = Hash.new 47: @direct_channel_count = 0 48: @open_direct_channel_count = 0 49: 50: @connection.add_channel_open_handler( 51: "forwarded-tcpip", &method(:do_open_channel) ) 52: end
Return an array of the active forwarded local connections. Each element of the array is another array containing the local port, and the remote host and port of the connection.
[ show source ]
# File lib/net/ssh/service/forward/driver.rb, line 229 229: def active_locals 230: @local_forwards.keys 231: end
Return an array of the active forwarded remote connections. Each element of the array is an integer representing the port number of the remote host that is being forwarded to the local client.
[ show source ]
# File lib/net/ssh/service/forward/driver.rb, line 236 236: def active_remotes 237: @remote_forwards.keys 238: end
Cease forwarding connections from the given local port (and interface). The parameters must match those given to a prior call to local. Existing forwarded connections are not affected by this call, but no more connections will be accepted on the local port.
[ show source ]
# File lib/net/ssh/service/forward/driver.rb, line 245 245: def cancel_local( local_port, bind_address="127.0.0.1" ) 246: key = [ local_port.to_i, bind_address ] 247: 248: forward = @local_forwards[ key ] 249: @local_forwards.delete key 250: 251: forward[ :socket ].close 252: forward[ :thread ].terminate 253: 254: true 255: end
Cease forwarding connections from the given remote port. The remote_host parameter must match the corresponding parameter that was passed to remote. Existing forwarded connections are not affected by this call, but no more connections will be forwarded from the remote host to the local host via that port.
[ show source ]
# File lib/net/ssh/service/forward/driver.rb, line 262 262: def cancel_remote( remote_port, remote_host="127.0.0.1" ) 263: writer = @buffers.writer 264: writer.write_string remote_host 265: writer.write_long remote_port.to_i 266: 267: @connection.global_request( "cancel-tcpip-forward", writer 268: ) do |success, response| 269: if success 270: @remote_forwards.delete remote_port 271: else 272: raise Net::SSH::Exception, 273: "could not cancel remote forward request on " + 274: "(#{remote_port},#{remote_host})" 275: end 276: end 277: end
Open a direct "tcpip" channel to the remote machine, which will then forward the connection to the given remote-host and remote-port. The connection will appear to have come from the given port on the local machine.
The handler object may respond to the following messages, in order to respond to requests over the channel:
- on_receive( channel, data ): when data is received over the channel (from the remote machine), this method will be invoked.
- on_eof( channel ): when the remote machine will no longer send data, this method will be invoked. The client may continue to send data over the channel, however.
- on_close( channel ): when the channel has been closed and is no longer valid for passing data, this method will be invoked.
- confirm( channel, local_port, remote_host, remote_port, *data ): when the channel has been opened and the remote machine has confirmed it, this method will be invoked. The data parameters are the same parameters as were passed to the direct_channel method.
- process( channel ): invoked after the channel is confirmed, to process the channel. It is invoked in a new Thread.
Only the process method is required—the others will only be invoked if the handler responds to them.
[ show source ]
# File lib/net/ssh/service/forward/driver.rb, line 79 79: def direct_channel( local_port, remote_host, remote_port, 80: handler, *data ) 81: # begin 82: writer = @buffers.writer 83: writer.write_string remote_host 84: writer.write_long remote_port.to_i 85: writer.write_string "127.0.0.1" 86: writer.write_long local_port.to_i 87: 88: @direct_channel_count += 1 89: @open_direct_channel_count += 1 90: 91: if @log.debug? 92: @log.debug "opening direct channel for " + 93: "#{local_port}:#{remote_host}:#{remote_port}" 94: end 95: 96: c = @connection.open_channel( 'direct-tcpip', writer ) do |channel| 97: if @log.debug? 98: @log.debug "direct channel confirmed for " + 99: "#{local_port}:#{remote_host}:#{remote_port}" 100: end 101: 102: if handler.respond_to?( :on_receive ) 103: channel.on_data( &handler.method(:on_receive) ) 104: end 105: 106: if handler.respond_to?( :on_eof ) 107: channel.on_eof( &handler.method(:on_eof) ) 108: end 109: 110: channel.on_close do |ch| 111: @open_direct_channel_count -= 1 112: handler.on_close( ch ) if handler.respond_to?( :on_close ) 113: end 114: 115: if handler.respond_to?( :confirm ) 116: handler.confirm( channel, local_port, remote_host, 117: remote_port, *data ) 118: end 119: 120: if handler.respond_to?( :process ) 121: Thread.new { handler.process( channel ) } 122: end 123: end 124: 125: c.on_confirm_failed do |channel, code,desc,lang| 126: raise Net::SSH::Exception, "could not open direct channel for " + 127: "#{local_port}:#{remote_host}:#{remote_port} (#{code}, #{desc})" 128: end 129: 130: nil 131: end
open a new channel as requested by the server
[ show source ]
# File lib/net/ssh/service/forward/driver.rb, line 280 280: def do_open_channel( connection, channel, data ) 281: connected_address = data.read_string 282: connected_port = data.read_long 283: originator_address = data.read_string 284: originator_port = data.read_long 285: 286: forward_data = @remote_forwards[ connected_port ] 287: unless forward_data 288: raise Net::SSH::Exception, 289: "recieved invalid channel-open command for a port forward " + 290: "that was never requested" 291: end 292: 293: handler = forward_data[:handler] 294: 295: forward_data[:channel] = channel 296: 297: if handler.respond_to?( :on_open ) 298: handler.on_open( channel, 299: connected_address, connected_port, 300: originator_address, originator_port ) 301: end 302: 303: channel.on_data( &handler.method(:on_receive) ) 304: 305: if handler.respond_to?( :on_close ) 306: channel.on_close( &handler.method(:on_close) ) 307: end 308: 309: if handler.respond_to?( :on_eof ) 310: channel.on_eof( &handler.method(:on_eof) ) 311: end 312: end
Forward connections on the given local port, to the given remote host and remote port.
This method will return immediately, forwarding the connections asynchronously.
[ show source ]
# File lib/net/ssh/service/forward/driver.rb, line 138 138: def local( *args ) 139: if args.length < 3 || args.length > 4 140: raise ArgumentError, 141: "expected 3 or 4 parameters, got #{args.length+1}" 142: end 143: 144: bind_address = "127.0.0.1" 145: bind_address = args.shift if args.first.is_a? String 146: 147: local_port = args.shift.to_i 148: remote_host = args.shift 149: remote_port = args.shift 150: 151: Thread.new do 152: begin 153: key = [ local_port.to_i, bind_address ] 154: 155: if @log.debug? 156: @log.debug "starting local forwarding server on " + 157: key.inspect 158: end 159: 160: socket = TCPServer.new( bind_address, local_port ) 161: 162: @local_forwards[ key ] = { :thread => Thread.current, 163: :socket => socket } 164: 165: if @log.debug? 166: @log.debug "listening for connections on #{key.inspect}" 167: end 168: 169: while ( client = socket.accept ) 170: @log.debug "#{key.inspect} received connection" if @log.debug? 171: 172: direct_channel( local_port, 173: remote_host, 174: remote_port, 175: @handlers[:local].call( client ) ) 176: end 177: 178: rescue Exception => e 179: @log.error "error forwarding local connection: " + 180: "#{e.class} (#{e.message})\n " + 181: e.backtrace.join( "\n " ) 182: end 183: end 184: end
Initiate forwarding of the given remote port on the connected host. Forwarded packets will be passed to the given block as they are recieved. The remote-host represents the address that should be bound on the remote host, and defaults to ‘127.0.0.1’.
[ show source ]
# File lib/net/ssh/service/forward/driver.rb, line 190 190: def remote( handler, remote_port, remote_host="127.0.0.1" ) 191: if @remote_forwards[ remote_port ] 192: raise Net::SSH::Exception, "#{remote_port} is already forwarded" 193: end 194: 195: writer = @buffers.writer 196: writer.write_string remote_host 197: writer.write_long remote_port.to_i 198: 199: @connection.global_request( "tcpip-forward", writer 200: ) do |success, response| 201: if success 202: remote_port = response.read_long if remote_port == 0 203: @remote_forwards[ remote_port ] = { :port => remote_port, 204: :handler => handler } 205: handler.setup( remote_port ) if handler.respond_to?( :setup ) 206: else 207: msg = "remote port #{remote_port} could not be forwarded " + 208: "to local host" 209: if handler.respond_to?( :error ) 210: handler.error( msg ) 211: else 212: raise Net::SSH::Exception, msg 213: end 214: end 215: end 216: end
A convenience method for setting up a forwarded connection from the given port on the remote host, to the given host and port (local).
[ show source ]
# File lib/net/ssh/service/forward/driver.rb, line 220 220: def remote_to( port, host, remote_port, remote_host="127.0.0.1" ) 221: # begin 222: remote( @handlers[:remote].call( port, host ), remote_port, 223: remote_host ) 224: end