1
+ using System ;
2
+ using System . Net ;
3
+ using System . Net . Sockets ;
4
+ using System . Threading ;
5
+ using Renci . SshNet . Abstractions ;
6
+ using Renci . SshNet . Common ;
7
+ using Renci . SshNet . Messages . Connection ;
8
+
9
+ namespace Renci . SshNet . Channels
10
+ {
11
+ /// <summary>
12
+ /// Implements "[email protected] " SSH channel.
13
+ /// </summary>
14
+ internal class ChannelDirectStreamLocal : ClientChannel , IChannelDirectStreamLocal
15
+ {
16
+ private readonly object _socketLock = new object ( ) ;
17
+
18
+ private EventWaitHandle _channelOpen = new AutoResetEvent ( false ) ;
19
+ private EventWaitHandle _channelData = new AutoResetEvent ( false ) ;
20
+ private IForwardedPort _forwardedPort ;
21
+ private Socket _socket ;
22
+
23
+ /// <summary>
24
+ /// Initializes a new <see cref="ChannelDirectStreamLocal"/> instance.
25
+ /// </summary>
26
+ /// <param name="session">The session.</param>
27
+ /// <param name="localChannelNumber">The local channel number.</param>
28
+ /// <param name="localWindowSize">Size of the window.</param>
29
+ /// <param name="localPacketSize">Size of the packet.</param>
30
+ public ChannelDirectStreamLocal ( ISession session , uint localChannelNumber , uint localWindowSize , uint localPacketSize )
31
+ : base ( session , localChannelNumber , localWindowSize , localPacketSize )
32
+ {
33
+ }
34
+
35
+ /// <summary>
36
+ /// Gets the type of the channel.
37
+ /// </summary>
38
+ /// <value>
39
+ /// The type of the channel.
40
+ /// </value>
41
+ public override ChannelTypes ChannelType
42
+ {
43
+ get { return ChannelTypes . DirectStreamLocal ; }
44
+ }
45
+
46
+ public void Open ( string remoteSocket , IForwardedPort forwardedPort , Socket socket )
47
+ {
48
+ if ( IsOpen )
49
+ throw new SshException ( "Channel is already open." ) ;
50
+ if ( ! IsConnected )
51
+ throw new SshException ( "Session is not connected." ) ;
52
+
53
+ lock ( _socketLock )
54
+ {
55
+ _socket = socket ;
56
+ }
57
+ _forwardedPort = forwardedPort ;
58
+ _forwardedPort . Closing += ForwardedPort_Closing ;
59
+
60
+ var originatorAddress = "" ;
61
+ var originatorPort = ( uint ) 0 ;
62
+
63
+ if ( socket . RemoteEndPoint is IPEndPoint )
64
+ {
65
+ var ep = ( IPEndPoint ) socket . RemoteEndPoint ;
66
+ originatorAddress = ep . Address . ToString ( ) ;
67
+ originatorPort = ( uint ) ep . Port ;
68
+ }
69
+
70
+ SendMessage ( new ChannelOpenMessage ( LocalChannelNumber , LocalWindowSize , LocalPacketSize ,
71
+ new DirectStreamLocalChannelInfo ( remoteSocket , originatorAddress , originatorPort ) ) ) ;
72
+ // Wait for channel to open
73
+ WaitOnHandle ( _channelOpen ) ;
74
+ }
75
+
76
+ /// <summary>
77
+ /// Occurs as the forwarded port is being stopped.
78
+ /// </summary>
79
+ private void ForwardedPort_Closing ( object sender , EventArgs eventArgs )
80
+ {
81
+ // signal to the client that we will not send anything anymore; this should also interrupt the
82
+ // blocking receive in Bind if the client sends FIN/ACK in time
83
+ ShutdownSocket ( SocketShutdown . Send ) ;
84
+
85
+ // if the FIN/ACK is not sent in time by the remote client, then interrupt the blocking receive
86
+ // by closing the socket
87
+ CloseSocket ( ) ;
88
+ }
89
+
90
+ /// <summary>
91
+ /// Binds channel to remote host.
92
+ /// </summary>
93
+ public void Bind ( )
94
+ {
95
+ // Cannot bind if channel is not open
96
+ if ( ! IsOpen )
97
+ return ;
98
+
99
+ var buffer = new byte [ RemotePacketSize ] ;
100
+
101
+ SocketAbstraction . ReadContinuous ( _socket , buffer , 0 , buffer . Length , SendData ) ;
102
+
103
+ // even though the client has disconnected, we still want to properly close the
104
+ // channel
105
+ //
106
+ // we'll do this in in Close() - invoked through Dispose(bool) - that way we have
107
+ // a single place from which we send an SSH_MSG_CHANNEL_EOF message and wait for
108
+ // the SSH_MSG_CHANNEL_CLOSE message
109
+ }
110
+
111
+ /// <summary>
112
+ /// Closes the socket, hereby interrupting the blocking receive in <see cref="Bind()"/>.
113
+ /// </summary>
114
+ private void CloseSocket ( )
115
+ {
116
+ if ( _socket == null )
117
+ return ;
118
+
119
+ lock ( _socketLock )
120
+ {
121
+ if ( _socket == null )
122
+ return ;
123
+
124
+ // closing a socket actually disposes the socket, so we can safely dereference
125
+ // the field to avoid entering the lock again later
126
+ _socket . Dispose ( ) ;
127
+ _socket = null ;
128
+ }
129
+ }
130
+
131
+ /// <summary>
132
+ /// Shuts down the socket.
133
+ /// </summary>
134
+ /// <param name="how">One of the <see cref="SocketShutdown"/> values that specifies the operation that will no longer be allowed.</param>
135
+ private void ShutdownSocket ( SocketShutdown how )
136
+ {
137
+ if ( _socket == null )
138
+ return ;
139
+
140
+ lock ( _socketLock )
141
+ {
142
+ if ( ! _socket . IsConnected ( ) )
143
+ return ;
144
+
145
+ try
146
+ {
147
+ _socket . Shutdown ( how ) ;
148
+ }
149
+ catch ( SocketException ex )
150
+ {
151
+ // TODO: log as warning
152
+ DiagnosticAbstraction . Log ( "Failure shutting down socket: " + ex ) ;
153
+ }
154
+ }
155
+ }
156
+
157
+ /// <summary>
158
+ /// Closes the channel, waiting for the SSH_MSG_CHANNEL_CLOSE message to be received from the server.
159
+ /// </summary>
160
+ protected override void Close ( )
161
+ {
162
+ var forwardedPort = _forwardedPort ;
163
+ if ( forwardedPort != null )
164
+ {
165
+ forwardedPort . Closing -= ForwardedPort_Closing ;
166
+ _forwardedPort = null ;
167
+ }
168
+
169
+ // signal to the client that we will not send anything anymore; this will also interrupt the
170
+ // blocking receive in Bind if the client sends FIN/ACK in time
171
+ //
172
+ // if the FIN/ACK is not sent in time, the socket will be closed after the channel is closed
173
+ ShutdownSocket ( SocketShutdown . Send ) ;
174
+
175
+ // close the SSH channel
176
+ base . Close ( ) ;
177
+
178
+ // close the socket
179
+ CloseSocket ( ) ;
180
+ }
181
+
182
+ /// <summary>
183
+ /// Called when channel data is received.
184
+ /// </summary>
185
+ /// <param name="data">The data.</param>
186
+ protected override void OnData ( byte [ ] data )
187
+ {
188
+ base . OnData ( data ) ;
189
+
190
+ if ( _socket != null )
191
+ {
192
+ lock ( _socketLock )
193
+ {
194
+ if ( _socket . IsConnected ( ) )
195
+ {
196
+ SocketAbstraction . Send ( _socket , data , 0 , data . Length ) ;
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ /// <summary>
203
+ /// Called when channel is opened by the server.
204
+ /// </summary>
205
+ /// <param name="remoteChannelNumber">The remote channel number.</param>
206
+ /// <param name="initialWindowSize">Initial size of the window.</param>
207
+ /// <param name="maximumPacketSize">Maximum size of the packet.</param>
208
+ protected override void OnOpenConfirmation ( uint remoteChannelNumber , uint initialWindowSize , uint maximumPacketSize )
209
+ {
210
+ base . OnOpenConfirmation ( remoteChannelNumber , initialWindowSize , maximumPacketSize ) ;
211
+
212
+ _channelOpen . Set ( ) ;
213
+ }
214
+
215
+ protected override void OnOpenFailure ( uint reasonCode , string description , string language )
216
+ {
217
+ base . OnOpenFailure ( reasonCode , description , language ) ;
218
+
219
+ _channelOpen . Set ( ) ;
220
+ }
221
+
222
+ /// <summary>
223
+ /// Called when channel has no more data to receive.
224
+ /// </summary>
225
+ protected override void OnEof ( )
226
+ {
227
+ base . OnEof ( ) ;
228
+
229
+ // the channel will send no more data, and hence it does not make sense to receive
230
+ // any more data from the client to send to the remote party (and we surely won't
231
+ // send anything anymore)
232
+ //
233
+ // this will also interrupt the blocking receive in Bind()
234
+ ShutdownSocket ( SocketShutdown . Send ) ;
235
+ }
236
+
237
+ /// <summary>
238
+ /// Called whenever an unhandled <see cref="Exception"/> occurs in <see cref="Session"/> causing
239
+ /// the message loop to be interrupted, or when an exception occurred processing a channel message.
240
+ /// </summary>
241
+ protected override void OnErrorOccured ( Exception exp )
242
+ {
243
+ base . OnErrorOccured ( exp ) ;
244
+
245
+ // signal to the client that we will not send anything anymore; this will also interrupt the
246
+ // blocking receive in Bind if the client sends FIN/ACK in time
247
+ //
248
+ // if the FIN/ACK is not sent in time, the socket will be closed in Close(bool)
249
+ ShutdownSocket ( SocketShutdown . Send ) ;
250
+ }
251
+
252
+ /// <summary>
253
+ /// Called when the server wants to terminate the connection immmediately.
254
+ /// </summary>
255
+ /// <remarks>
256
+ /// The sender MUST NOT send or receive any data after this message, and
257
+ /// the recipient MUST NOT accept any data after receiving this message.
258
+ /// </remarks>
259
+ protected override void OnDisconnected ( )
260
+ {
261
+ base . OnDisconnected ( ) ;
262
+
263
+ // the channel will accept or send no more data, and hence it does not make sense
264
+ // to accept any more data from the client (and we surely won't send anything
265
+ // anymore)
266
+ //
267
+ // so lets signal to the client that we will not send or receive anything anymore
268
+ // this will also interrupt the blocking receive in Bind()
269
+ ShutdownSocket ( SocketShutdown . Both ) ;
270
+ }
271
+
272
+ protected override void Dispose ( bool disposing )
273
+ {
274
+ // make sure we've unsubscribed from all session events and closed the channel
275
+ // before we starting disposing
276
+ base . Dispose ( disposing ) ;
277
+
278
+ if ( disposing )
279
+ {
280
+ if ( _socket != null )
281
+ {
282
+ lock ( _socketLock )
283
+ {
284
+ var socket = _socket ;
285
+ if ( socket != null )
286
+ {
287
+ _socket = null ;
288
+ socket . Dispose ( ) ;
289
+ }
290
+ }
291
+ }
292
+
293
+ var channelOpen = _channelOpen ;
294
+ if ( channelOpen != null )
295
+ {
296
+ _channelOpen = null ;
297
+ channelOpen . Dispose ( ) ;
298
+ }
299
+
300
+ var channelData = _channelData ;
301
+ if ( channelData != null )
302
+ {
303
+ _channelData = null ;
304
+ channelData . Dispose ( ) ;
305
+ }
306
+ }
307
+ }
308
+ }
309
+ }
0 commit comments