hrr_rb_ssh 0.3.0.pre3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,6 +27,9 @@ module HrrRbSsh
27
27
  if @sender_thread_finished && @receiver_thread_finished
28
28
  @logger.info { "closing forwarded-tcpip" }
29
29
  @socket.close
30
+ @logger.info { "closing channel IOs" }
31
+ @channel.io.each{ |io| io.close rescue nil }
32
+ @logger.info { "channel IOs closed" }
30
33
  @channel.close from=:channel_type_instance
31
34
  @logger.info { "forwarded-tcpip closed" }
32
35
  end
@@ -43,15 +46,15 @@ module HrrRbSsh
43
46
  @channel.io[1].write s.readpartial(10240)
44
47
  rescue EOFError
45
48
  @logger.info { "socket is EOF" }
46
- @channel.io[1].close
49
+ @channel.io[1].close rescue nil
47
50
  break
48
51
  rescue IOError
49
52
  @logger.info { "socket is closed" }
50
- @channel.io[1].close
53
+ @channel.io[1].close rescue nil
51
54
  break
52
55
  rescue => e
53
56
  @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
54
- @channel.io[1].close
57
+ @channel.io[1].close rescue nil
55
58
  break
56
59
  end
57
60
  end
@@ -18,7 +18,10 @@ module HrrRbSsh
18
18
  end
19
19
 
20
20
  def start
21
- @proc_chain_thread = proc_chain_thread
21
+ case @connection.mode
22
+ when Mode::SERVER
23
+ @proc_chain_thread = proc_chain_thread
24
+ end
22
25
  end
23
26
 
24
27
  def close
@@ -42,6 +45,9 @@ module HrrRbSsh
42
45
  exitstatus = 1
43
46
  ensure
44
47
  @logger.info { "closing proc chain thread" }
48
+ @logger.info { "closing channel IOs" }
49
+ @channel.io.each{ |io| io.close rescue nil }
50
+ @logger.info { "channel IOs closed" }
45
51
  @logger.info { "wait for sending output" }
46
52
  @channel.wait_until_senders_closed
47
53
  @logger.info { "sending output finished" }
@@ -2,6 +2,8 @@
2
2
  # vim: et ts=2 sw=2
3
3
 
4
4
  require 'socket'
5
+ require 'thread'
6
+ require 'monitor'
5
7
  require 'hrr_rb_ssh/logger'
6
8
  require 'hrr_rb_ssh/connection/channel/channel_type'
7
9
 
@@ -19,7 +21,8 @@ module HrrRbSsh
19
21
  :local_maximum_packet_size,
20
22
  :remote_window_size,
21
23
  :remote_maximum_packet_size,
22
- :receive_message_queue
24
+ :receive_message_queue,
25
+ :exit_status
23
26
 
24
27
  def initialize connection, message, socket=nil
25
28
  @logger = Logger.new self.class.name
@@ -38,60 +41,81 @@ module HrrRbSsh
38
41
 
39
42
  @receive_message_queue = Queue.new
40
43
  @receive_data_queue = Queue.new
44
+ @receive_extended_data_queue = Queue.new
41
45
 
42
46
  @r_io_in, @w_io_in = IO.pipe
43
47
  @r_io_out, @w_io_out = IO.pipe
44
48
  @r_io_err, @w_io_err = IO.pipe
45
49
 
50
+ @channel_closing_monitor = Monitor.new
51
+
46
52
  @closed = nil
53
+ @exit_status = nil
47
54
  end
48
55
 
49
56
  def set_remote_parameters message
50
57
  @remote_channel = message[:'sender channel']
51
- @remote_window_size = message[:'initial window size']
58
+ @remote_window_size = message[:'initial window size']
52
59
  @remote_maximum_packet_size = message[:'maximum packet size']
53
60
  end
54
61
 
55
62
  def io
56
- [@r_io_in, @w_io_out, @w_io_err]
63
+ case @connection.mode
64
+ when Mode::SERVER
65
+ [@r_io_in, @w_io_out, @w_io_err]
66
+ when Mode::CLIENT
67
+ [@w_io_in, @r_io_out, @r_io_err]
68
+ end
57
69
  end
58
70
 
59
71
  def start
60
72
  @channel_loop_thread = channel_loop_thread
61
- @out_sender_thread = out_sender_thread
62
- @err_sender_thread = err_sender_thread
63
- @receiver_thread = receiver_thread
64
- @channel_type_instance.start
73
+ case @connection.mode
74
+ when Mode::SERVER
75
+ @out_sender_thread = out_sender_thread
76
+ @err_sender_thread = err_sender_thread
77
+ @receiver_thread = receiver_thread
78
+ @channel_type_instance.start
79
+ when Mode::CLIENT
80
+ @out_receiver_thread = out_receiver_thread
81
+ @err_receiver_thread = err_receiver_thread
82
+ @sender_thread = sender_thread
83
+ @channel_type_instance.start
84
+ end
65
85
  @closed = false
86
+ @logger.debug { "in start: #{@waiting_thread}" }
87
+ @waiting_thread.wakeup if @waiting_thread
88
+ end
89
+
90
+ def wait_until_started
91
+ @waiting_thread = Thread.current
92
+ @logger.debug { "in wait_until_started: #{@waiting_thread}" }
93
+ Thread.stop
66
94
  end
67
95
 
68
96
  def wait_until_senders_closed
69
- [@w_io_out, @w_io_err].each{ |io|
97
+ [
98
+ @out_sender_thread,
99
+ @err_sender_thread,
100
+ ].each{ |t|
70
101
  begin
71
- io.close
72
- rescue IOError # for compatibility for Ruby version < 2.3
73
- Thread.pass
102
+ t.join if t.instance_of? Thread
103
+ rescue => e
104
+ @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
74
105
  end
75
106
  }
76
- [@out_sender_thread, @err_sender_thread].select{ |t| t.instance_of? Thread }.each(&:join)
77
107
  end
78
108
 
79
109
  def close from=:outside, exitstatus=0
80
- return if @closed
81
- @logger.info { "close channel" }
82
- @closed = true
110
+ @channel_closing_monitor.synchronize {
111
+ return if @closed
112
+ @logger.info { "close channel" }
113
+ @closed = true
114
+ }
83
115
  unless from == :channel_type_instance
84
116
  @channel_type_instance.close
85
117
  end
86
118
  @receive_message_queue.close
87
- @receive_data_queue.close
88
- [@r_io_in, @w_io_in, @r_io_out, @w_io_out, @r_io_err, @w_io_err].each{ |io|
89
- begin
90
- io.close
91
- rescue IOError # for compatibility for Ruby version < 2.3
92
- Thread.pass
93
- end
94
- }
95
119
  begin
96
120
  if from == :channel_type_instance
97
121
  send_channel_eof
@@ -111,6 +135,24 @@ module HrrRbSsh
111
135
  @logger.info { "channel closed" }
112
136
  end
113
137
 
138
+ def wait_until_closed
139
+ [
140
+ @out_sender_thread,
141
+ @err_sender_thread,
142
+ @receiver_thread,
143
+ @out_receiver_thread,
144
+ @err_receiver_thread,
145
+ @sender_thread,
146
+ @channel_loop_thread
147
+ ].each{ |t|
148
+ begin
149
+ t.join if t.instance_of? Thread
150
+ rescue => e
151
+ @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
152
+ end
153
+ }
154
+ end
155
+
114
156
  def closed?
115
157
  @closed
116
158
  end
@@ -118,44 +160,60 @@ module HrrRbSsh
118
160
  def channel_loop_thread
119
161
  Thread.start do
120
162
  @logger.info { "start channel loop thread" }
121
- loop do
122
- begin
123
- message = @receive_message_queue.deq
124
- if message.nil? && @receive_message_queue.closed?
125
- @receive_data_queue.close
126
- @logger.info { "closing channel loop thread" }
127
- break
128
- end
129
- case message[:'message number']
130
- when Message::SSH_MSG_CHANNEL_REQUEST::VALUE
131
- @logger.info { "received channel request: #{message[:'request type']}" }
132
- begin
133
- @channel_type_instance.request message
134
- rescue => e
135
- @logger.warn { "request failed: #{e.message}" }
136
- if message[:'want reply']
137
- send_channel_failure
163
+ begin
164
+ loop do
165
+ begin
166
+ message = @receive_message_queue.deq
167
+ if message.nil? && @receive_message_queue.closed?
168
+ break
169
+ end
170
+ case message[:'message number']
171
+ when Message::SSH_MSG_CHANNEL_EOF::VALUE
172
+ @receive_data_queue.close
173
+ @receive_extended_data_queue.close
174
+ when Message::SSH_MSG_CHANNEL_REQUEST::VALUE
175
+ @logger.info { "received channel request: #{message[:'request type']}" }
176
+ case @connection.mode
177
+ when Mode::SERVER
178
+ begin
179
+ @channel_type_instance.request message
180
+ rescue => e
181
+ @logger.warn { "request failed: #{e.message}" }
182
+ send_channel_failure if message[:'want reply']
183
+ else
184
+ send_channel_success if message[:'want reply']
185
+ end
186
+ when Mode::CLIENT
187
+ case message[:'request type']
188
+ when "exit-status"
189
+ @logger.info { "exit status: #{message[:'exit status']}" }
190
+ @exit_status = message[:'exit status'].to_i
191
+ end
138
192
  end
193
+ when Message::SSH_MSG_CHANNEL_DATA::VALUE
194
+ @logger.info { "received channel data" }
195
+ local_channel = message[:'recipient channel']
196
+ @receive_data_queue.enq message[:'data']
197
+ when Message::SSH_MSG_CHANNEL_EXTENDED_DATA::VALUE
198
+ @logger.info { "received channel extended data" }
199
+ local_channel = message[:'recipient channel']
200
+ @receive_extended_data_queue.enq message[:'data']
201
+ when Message::SSH_MSG_CHANNEL_WINDOW_ADJUST::VALUE
202
+ @logger.debug { "received channel window adjust" }
203
+ @remote_window_size = [@remote_window_size + message[:'bytes to add'], 0xffff_ffff].min
139
204
  else
140
- if message[:'want reply']
141
- send_channel_success
142
- end
205
+ @logger.warn { "received unsupported message: #{message.inspect}" }
143
206
  end
144
- when Message::SSH_MSG_CHANNEL_DATA::VALUE
145
- @logger.info { "received channel data" }
146
- local_channel = message[:'recipient channel']
147
- @receive_data_queue.enq message[:'data']
148
- when Message::SSH_MSG_CHANNEL_WINDOW_ADJUST::VALUE
149
- @logger.debug { "received channel window adjust" }
150
- @remote_window_size = [@remote_window_size + message[:'bytes to add'], 0xffff_ffff].min
151
- else
152
- @logger.warn { "received unsupported message: #{message.inspect}" }
207
+ rescue => e
208
+ @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
209
+ close from=:channel_loop_thread
210
+ break
153
211
  end
154
- rescue => e
155
- @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
156
- close from=:channel_loop_thread
157
- break
158
212
  end
213
+ ensure
214
+ @logger.info { "closing channel loop thread" }
215
+ @receive_data_queue.close
216
+ @receive_extended_data_queue.close
159
217
  end
160
218
  @logger.info { "channel loop thread closed" }
161
219
  end
@@ -175,17 +233,11 @@ module HrrRbSsh
175
233
  sending_data = data[0, sendable_size]
176
234
  send_channel_data sending_data if sendable_size > 0
177
235
  @remote_window_size -= sendable_size
178
- rescue EOFError => e
179
- begin
180
- @r_io_out.close
181
- rescue IOError # for compatibility for Ruby version < 2.3
182
- Thread.pass
183
- end
184
- rescue IOError => e
185
- @logger.warn { "channel IO is closed" }
186
- close
236
+ rescue EOFError, IOError => e
237
+ @r_io_out.close rescue nil
187
238
  rescue => e
188
239
  @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
240
+ @r_io_out.close rescue nil
189
241
  close
190
242
  end
191
243
  end
@@ -207,17 +259,11 @@ module HrrRbSsh
207
259
  sending_data = data[0, sendable_size]
208
260
  send_channel_extended_data sending_data if sendable_size > 0
209
261
  @remote_window_size -= sendable_size
210
- rescue EOFError => e
211
- begin
212
- @r_io_err.close
213
- rescue IOError # for compatibility for Ruby version < 2.3
214
- Thread.pass
215
- end
216
- rescue IOError => e
217
- @logger.warn { "channel IO is closed" }
218
- close
262
+ rescue EOFError, IOError => e
263
+ @r_io_err.close rescue nil
219
264
  rescue => e
220
265
  @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
266
+ @r_io_err.close rescue nil
221
267
  close
222
268
  end
223
269
  end
@@ -233,9 +279,9 @@ module HrrRbSsh
233
279
  data = @receive_data_queue.deq
234
280
  if data.nil? && @receive_data_queue.closed?
235
281
  @logger.info { "closing receiver thread" }
236
- @logger.info { "closing channel IO write" }
237
- @w_io_in.close_write
238
- @logger.info { "channel IO write closed" }
282
+ @logger.info { "closing w_io_in" }
283
+ @w_io_in.close
284
+ @logger.info { "w_io_in closed" }
239
285
  break
240
286
  end
241
287
  @w_io_in.write data
@@ -245,8 +291,7 @@ module HrrRbSsh
245
291
  send_channel_window_adjust
246
292
  @local_window_size += INITIAL_WINDOW_SIZE
247
293
  end
248
- rescue IOError => e
249
- @logger.warn { "channel IO is closed" }
294
+ rescue Errno::EPIPE, IOError => e
250
295
  close
251
296
  break
252
297
  rescue => e
@@ -259,6 +304,98 @@ module HrrRbSsh
259
304
  }
260
305
  end
261
306
 
307
+ def out_receiver_thread
308
+ Thread.start {
309
+ @logger.info { "start out receiver thread" }
310
+ loop do
311
+ begin
312
+ data = @receive_data_queue.deq
313
+ if data.nil? && @receive_data_queue.closed?
314
+ @logger.info { "closing out receiver thread" }
315
+ @logger.info { "closing w_io_out" }
316
+ @w_io_out.close
317
+ @logger.info { "w_io_out closed" }
318
+ break
319
+ end
320
+ @w_io_out.write data
321
+ @local_window_size -= data.size
322
+ if @local_window_size < INITIAL_WINDOW_SIZE/2
323
+ @logger.info { "send channel window adjust" }
324
+ send_channel_window_adjust
325
+ @local_window_size += INITIAL_WINDOW_SIZE
326
+ end
327
+ rescue Errno::EPIPE, IOError => e
328
+ close
329
+ break
330
+ rescue => e
331
+ @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
332
+ close
333
+ break
334
+ end
335
+ end
336
+ @logger.info { "out receiver thread closed" }
337
+ }
338
+ end
339
+
340
+ def err_receiver_thread
341
+ Thread.start {
342
+ @logger.info { "start err receiver thread" }
343
+ loop do
344
+ begin
345
+ data = @receive_extended_data_queue.deq
346
+ if data.nil? && @receive_extended_data_queue.closed?
347
+ @logger.info { "closing err receiver thread" }
348
+ @logger.info { "closing w_io_err" }
349
+ @w_io_err.close
350
+ @logger.info { "w_io_err closed" }
351
+ break
352
+ end
353
+ @w_io_err.write data
354
+ @local_window_size -= data.size
355
+ if @local_window_size < INITIAL_WINDOW_SIZE/2
356
+ @logger.info { "send channel window adjust" }
357
+ send_channel_window_adjust
358
+ @local_window_size += INITIAL_WINDOW_SIZE
359
+ end
360
+ rescue Error::EPIPE, IOError => e
361
+ close
362
+ break
363
+ rescue => e
364
+ @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
365
+ close
366
+ break
367
+ end
368
+ end
369
+ @logger.info { "err receiver thread closed" }
370
+ }
371
+ end
372
+
373
+ def sender_thread
374
+ Thread.start {
375
+ @logger.info { "start sender thread" }
376
+ loop do
377
+ if @r_io_in.closed?
378
+ @logger.info { "closing sender thread" }
379
+ break
380
+ end
381
+ begin
382
+ data = @r_io_in.readpartial(10240)
383
+ sendable_size = [data.size, @remote_window_size].min
384
+ sending_data = data[0, sendable_size]
385
+ send_channel_data sending_data if sendable_size > 0
386
+ @remote_window_size -= sendable_size
387
+ rescue EOFError, IOError => e
388
+ @r_io_in.close rescue nil
389
+ rescue => e
390
+ @logger.error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
391
+ @r_io_in.close rescue nil
392
+ close
393
+ end
394
+ end
395
+ @logger.info { "sender thread closed" }
396
+ }
397
+ end
398
+
262
399
  def send_channel_success
263
400
  message = {
264
401
  :'message number' => Message::SSH_MSG_CHANNEL_SUCCESS::VALUE,
@@ -308,6 +445,98 @@ module HrrRbSsh
308
445
  @connection.send payload
309
446
  end
310
447
 
448
+ def send_channel_request_pty_req term_env_var_val, term_width_chars, term_height_rows, term_width_pixel, term_height_pixel, encoded_term_modes
449
+ message = {
450
+ :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
451
+ :'recipient channel' => @remote_channel,
452
+ :'request type' => "pty-req",
453
+ :'want reply' => false,
454
+ :'TERM environment variable value' => term_env_var_val,
455
+ :'terminal width, characters' => term_width_chars,
456
+ :'terminal height, rows' => term_height_rows,
457
+ :'terminal width, pixels' => term_width_pixel,
458
+ :'terminal height, pixels' => term_height_pixel,
459
+ :'encoded terminal modes' => encoded_term_modes,
460
+ }
461
+ payload = Message::SSH_MSG_CHANNEL_REQUEST.encode message
462
+ @connection.send payload
463
+ end
464
+
465
+ def send_channel_request_env variable_name, variable_value
466
+ message = {
467
+ :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
468
+ :'recipient channel' => @remote_channel,
469
+ :'request type' => "env",
470
+ :'want reply' => false,
471
+ :'variable name' => variable_name,
472
+ :'variable value' => variable_value,
473
+ }
474
+ payload = Message::SSH_MSG_CHANNEL_REQUEST.encode message
475
+ @connection.send payload
476
+ end
477
+
478
+ def send_channel_request_shell
479
+ message = {
480
+ :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
481
+ :'recipient channel' => @remote_channel,
482
+ :'request type' => "shell",
483
+ :'want reply' => false,
484
+ }
485
+ payload = Message::SSH_MSG_CHANNEL_REQUEST.encode message
486
+ @connection.send payload
487
+ end
488
+
489
+ def send_channel_request_exec command
490
+ message = {
491
+ :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
492
+ :'recipient channel' => @remote_channel,
493
+ :'request type' => "exec",
494
+ :'want reply' => false,
495
+ :'command' => command,
496
+ }
497
+ payload = Message::SSH_MSG_CHANNEL_REQUEST.encode message
498
+ @connection.send payload
499
+ end
500
+
501
+ def send_channel_request_subsystem subsystem_name
502
+ message = {
503
+ :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
504
+ :'recipient channel' => @remote_channel,
505
+ :'request type' => "subsystem",
506
+ :'want reply' => false,
507
+ :'subsystem name' => subsystem_name,
508
+ }
509
+ payload = Message::SSH_MSG_CHANNEL_REQUEST.encode message
510
+ @connection.send payload
511
+ end
512
+
513
+ def send_channel_request_window_change term_width_cols, term_height_rows, term_width_pixel, term_height_pixel
514
+ message = {
515
+ :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
516
+ :'recipient channel' => @remote_channel,
517
+ :'request type' => "window-change",
518
+ :'want reply' => false,
519
+ :'terminal width, columns' => term_width_cols,
520
+ :'terminal height, rows' => term_height_rows,
521
+ :'terminal width, pixels' => term_width_pixel,
522
+ :'terminal height, pixels' => term_height_pixel,
523
+ }
524
+ payload = Message::SSH_MSG_CHANNEL_REQUEST.encode message
525
+ @connection.send payload
526
+ end
527
+
528
+ def send_channel_request_signal signal_name
529
+ message = {
530
+ :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
531
+ :'recipient channel' => @remote_channel,
532
+ :'request type' => "signal",
533
+ :'want reply' => false,
534
+ :'signal name' => signal_name,
535
+ }
536
+ payload = Message::SSH_MSG_CHANNEL_REQUEST.encode message
537
+ @connection.send payload
538
+ end
539
+
311
540
  def send_channel_request_exit_status exitstatus
312
541
  message = {
313
542
  :'message number' => Message::SSH_MSG_CHANNEL_REQUEST::VALUE,