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 "direct-streamlocal@openssh.com" 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