1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
|
# $ ruby -Ilib -Itest -rrubygems test/test_forward.rb
# Tests for the following patch:
#
# http://github.com/net-ssh/net-ssh/tree/portfwfix
#
# It fixes 3 issues, regarding closing forwarded ports:
#
# 1.) if client closes a forwarded connection, but the server is reading, net-ssh terminates with IOError socket closed.
# 2.) if client force closes (RST) a forwarded connection, but server is reading, net-ssh terminates with
# 3.) if server closes the sending side, the on_eof is not handled.
#
# More info:
#
# http://net-ssh.lighthouseapp.com/projects/36253/tickets/7
require 'common'
require 'net/ssh/buffer'
require 'net/ssh'
require 'timeout'
require 'tempfile'
class TestForward < Test::Unit::TestCase
def localhost
'localhost'
end
def ssh_start_params
[localhost ,ENV['USER'], {:keys => "~/.ssh/id_rsa", :verbose => :debug}]
end
def find_free_port
server = TCPServer.open(0)
server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR,true)
port = server.addr[1]
server.close
port
end
def start_server_sending_lot_of_data(exceptions)
server = TCPServer.open(0)
Thread.start do
loop do
Thread.start(server.accept) do |client|
begin
10000.times do |i|
client.puts "item#{i}"
end
client.close
rescue
exceptions << $!
raise
end
end
end
end
return server
end
def start_server_closing_soon(exceptions=nil)
server = TCPServer.open(0)
Thread.start do
loop do
Thread.start(server.accept) do |client|
begin
client.recv(1024)
client.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, [1, 0].pack("ii"))
client.close
rescue
exceptions << $!
raise
end
end
end
end
return server
end
def test_loop_should_not_abort_when_local_side_of_forward_is_closed
session = Net::SSH.start(*ssh_start_params)
server_exc = Queue.new
server = start_server_sending_lot_of_data(server_exc)
remote_port = server.addr[1]
local_port = find_free_port
session.forward.local(local_port, localhost, remote_port)
client_done = Queue.new
Thread.start do
begin
client = TCPSocket.new(localhost, local_port)
client.recv(1024)
client.close
sleep(0.2)
ensure
client_done << true
end
end
session.loop(0.1) { client_done.empty? }
assert_equal "Broken pipe", "#{server_exc.pop}" unless server_exc.empty?
end
def test_loop_should_not_abort_when_local_side_of_forward_is_reset
session = Net::SSH.start(*ssh_start_params)
server_exc = Queue.new
server = start_server_sending_lot_of_data(server_exc)
remote_port = server.addr[1]
local_port = find_free_port
session.forward.local(local_port, localhost, remote_port)
client_done = Queue.new
Thread.start do
begin
client = TCPSocket.new(localhost, local_port)
client.recv(1024)
client.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, [1, 0].pack("ii"))
client.close
sleep(0.1)
ensure
client_done << true
end
end
session.loop(0.1) { client_done.empty? }
assert_equal "Broken pipe", "#{server_exc.pop}" unless server_exc.empty?
end
def create_local_socket(&blk)
tempfile = Tempfile.new("net_ssh_forward_test")
path = tempfile.path
tempfile.delete
yield UNIXServer.open(path)
File.delete(path)
end
def test_forward_local_unix_socket_to_remote_port
session = Net::SSH.start(*ssh_start_params)
server_exc = Queue.new
server = start_server_sending_lot_of_data(server_exc)
remote_port = server.addr[1]
client_data = nil
create_local_socket do |local_socket|
session.forward.local(local_socket, localhost, remote_port)
client_done = Queue.new
Thread.start do
begin
client = UNIXSocket.new(local_socket.path)
client_data = client.recv(1024)
client.close
sleep(0.2)
ensure
client_done << true
end
end
session.loop(0.1) { client_done.empty? }
end
assert_not_nil(client_data, "client should have received data")
assert(client_data.match(/item\d/), 'client should have received the string item')
end
def test_loop_should_not_abort_when_server_side_of_forward_is_closed
session = Net::SSH.start(*ssh_start_params)
server = start_server_closing_soon
remote_port = server.addr[1]
local_port = find_free_port
session.forward.local(local_port, localhost, remote_port)
client_done = Queue.new
Thread.start do
begin
client = TCPSocket.new(localhost, local_port)
1.times do |i|
client.puts "item#{i}"
end
client.close
sleep(0.1)
ensure
client_done << true
end
end
session.loop(0.1) { client_done.empty? }
end
def start_server
server = TCPServer.open(0)
Thread.start do
loop do
Thread.start(server.accept) do |client|
yield(client)
end
end
end
return server
end
def test_server_eof_should_be_handled
session = Net::SSH.start(*ssh_start_params)
server = start_server do |client|
client.write "This is a small message!"
client.close
end
client_done = Queue.new
client_exception = Queue.new
client_data = Queue.new
remote_port = server.addr[1]
local_port = find_free_port
session.forward.local(local_port, localhost, remote_port)
Thread.start do
begin
client = TCPSocket.new(localhost, local_port)
data = client.read(4096)
client.close
client_done << data
rescue
client_done << $!
end
end
timeout(5) do
session.loop(0.1) { client_done.empty? }
assert_equal "This is a small message!", client_done.pop
end
end
end
|