示例#1
0
        private async Task CopyStreamAsync(bool fromClient, TcpClient tcpIn, TcpClient tcpOut, Stream strIn, Stream strOut,
                                           AsyncTimeoutHelper receiveTimeoutHelper, AsyncTimeoutHelper sendTimeoutHelper, AsyncTimeoutHelper oppositeReceiveTimeoutHelper)
        {
            try {
                byte[] buf = new byte[64 * 1024]; // 64 KiB Buffer for optimal efficiency

                Task        waitTask;
                Func <Task> getWaitTask = () => {
                    Task awaitableTask;
                    if (fromClient)
                    {
                        awaitableTask = waitForSending(sendWaitSemaphore);
                    }
                    else
                    {
                        awaitableTask = waitForReceiving(receiveWaitSemaphore);
                    }
                    return(awaitableTask);
                };

                int read = 0;

                while (true)
                {
                    /* OLD: Only handle exceptions that occur in Read() but not the ones that
                     * occur in Write().
                     * This is because when the other conenction is aborted while it is paused,
                     * Write()s will fail although the other conenction did not yet report
                     * the abortion.
                     * NEW: Also Exceptions that occur in Write() must be handled - otherwise it could happen that
                     * the client half-closes the connection while data is still transmitted from the server to the client,
                     * and then the client aborts (resets) the connection. In this case if Write() would not handle the exection,
                     * we would never raise the ConnectionAborted event.
                     */
                    Exception readException = null;
                    try {
                        await receiveTimeoutHelper.DoTimeoutableOperationAsync(async() => read = await strIn.ReadAsync(buf, 0, buf.Length));

                        if (!(read > 0))
                        {
                            break;
                        }
                    } catch (Exception ex) {
                        if (ExceptionUtils.ShouldExceptionBeRethrown(ex))
                        {
                            throw;
                        }

                        // The await operator cannot be used in a catch clause.
                        // Also, don't run an async delegate here because this method
                        // might return (and therefore release the Semaphores etc.) before
                        // the async delegate completes.
                        readException = ex;
                    }

                    if (readException != null)
                    {
                        // See if we need to pause the transmission.
                        // This also needs to be done here to ensure that when the code is waiting in ReadAsync()
                        // and then the connection is aborted that we wait correctly before shutting down the
                        // other side.
                        waitTask = getWaitTask();
                        if (waitTask != null)
                        {
                            await waitTask;
                        }

                        // Abort the connection.
                        AbortConnection(fromClient, readException);
                        return;
                    }

                    // See if we need to pause the transmission.
                    // This is done after reading the next block because it would be
                    // more difficult to reabk the reading operation (when currently
                    // no data arrives).
                    waitTask = getWaitTask();
                    if (waitTask != null)
                    {
                        await waitTask;
                    }



                    // Check if we need to throttle transfer speed.
                    // TODO: need a better handling for this
                    if (fromClient && forwarder.ThrottleSpeedClient || !fromClient && forwarder.ThrottleSpeedServer)
                    {
                        int           speed       = fromClient ? forwarder.SpeedClient : forwarder.SpeedServer;
                        SemaphoreSlim sem         = fromClient ? sendThrottleSemaphore : receiveThrottleSemaphore;
                        long          pauseTime   = (long)((double)read * 1000d / (double)speed);
                        long          startSwTime = forwarder.StopwatchElapsedMilliseconds;

                        int waitTime;
                        while (true)
                        {
                            waitTime = (int)Math.Min(200, pauseTime - (forwarder.StopwatchElapsedMilliseconds - startSwTime));
                            if (waitTime <= 0)
                            {
                                break;
                            }

                            await sem.WaitAsync(waitTime);
                        }
                    }

                    // Raise events
                    if (fromClient)
                    {
                        OnDataReceivedLocal(buf, 0, read);
                    }
                    else
                    {
                        OnDataReceivedRemote(buf, 0, read);
                    }

                    // Write the data.
                    try {
                        // Write
                        await sendTimeoutHelper.DoTimeoutableOperationAsync(() => strOut.WriteAsync(buf, 0, read));
                    } catch (Exception ex) {
                        if (ExceptionUtils.ShouldExceptionBeRethrown(ex))
                        {
                            throw;
                        }

                        /* The other connection has been aborted or a write time occured.
                         * We must handle the exception. See comment in the Read() method.
                         */
                        AbortConnection(!fromClient, ex);
                        return;
                    }

                    // We could write data to the other connection, so reset its read timeout as it is still alive.
                    oppositeReceiveTimeoutHelper.ResetTimeout();

                    // Raise DataForwarded event to indicate the data have actually
                    // been written.
                    if (fromClient)
                    {
                        OnDataForwardedLocal();
                    }
                    else
                    {
                        OnDataForwardedRemote();
                    }
                }



                // See if we need to pause the transmission.
                // This also needs to be done here to ensure that when the code is waiting in ReadAsync()
                // and then the connection is closed that we wait correctly before shutting down the
                // other side.
                waitTask = getWaitTask();
                if (waitTask != null)
                {
                    await waitTask;
                }



                if (fromClient)
                {
                    OnLocalConnectionClosed();
                }
                else
                {
                    OnRemoteConnectionClosed();
                }

                // The connection has been half-closed by tcpIn. Therefore we
                // need to initiate the shutdown on tcpOut.
                try {
                    tcpOut.Client.Shutdown(SocketShutdown.Send);
                } catch (SocketException ex) {
                    System.Diagnostics.Debug.WriteLine(ex.ToString());
                    // Ignore. This probably means the connection has already
                    // been reset and the other direction will get the error
                    // when reading.
                }

                int myShutdownCount = Interlocked.Add(ref shutdownCount, 1);

                // After the shutdown was initiated for both sides,
                // deregister this ForwardingConnection from the
                // TcpConnectionForwarder.
                if (myShutdownCount == 2)
                {
                    //forwarder.DeregisterConnection(this);
                    OnConnectionClosedCompletely();
                }
            } finally {
                // Free resources.
                if (fromClient)
                {
                    sendWaitSemaphore.Dispose();
                    sendThrottleSemaphore.Dispose();
                }
                else
                {
                    receiveWaitSemaphore.Dispose();
                    receiveThrottleSemaphore.Dispose();
                }
            }
        }
示例#2
0
        private async Task ConnectAsync()
        {
            strClient = client.GetStream();

            // If the server uses SSL, we try to authenticate as a Server before we open a forwarding connection.
            if (forwarder.LocalSslCertificate != null)
            {
                SslStream sslStream = new SslStream(strClient, true);
                try {
                    await sslStream.AuthenticateAsServerAsync(forwarder.LocalSslCertificate, false, forwarder.LocalSslProtocols, false);
                } catch (Exception ex) { // TODO: Use more specific catch clause
                    if (ExceptionUtils.ShouldExceptionBeRethrown(ex))
                    {
                        throw;
                    }

                    OnConnectionAborted(true, ex);

                    // We could not authenticate the connection. Therefore we need to close the client socket.
                    Cancel();

                    return;
                }

                // Authentication OK. Use the SslStream to send and receive data.
                strClient = sslStream;

                OnLocalConnectionAuthenticated(sslStream.SslProtocol, sslStream.CipherAlgorithm, sslStream.CipherStrength,
                                               sslStream.HashAlgorithm, sslStream.HashStrength, sslStream.KeyExchangeAlgorithm, sslStream.KeyExchangeStrength);
            }

            // Try to establish a connection to the server.
            // First we try to connect using a IPv6 socket; if it fails, we use a IPv4 socket.
            Exception e        = null;
            bool      usedIpv6 = false;


            for (int i = 0; i < 2; i++)
            {
                AddressFamily adrFamily = i == 0 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork;
                if (addressForBind != null && addressForBind.AddressFamily != adrFamily || addressForConnect != null && addressForConnect.AddressFamily != adrFamily)
                {
                    continue;
                }

                TcpClient c = new TcpClient(adrFamily);


                try {
                    if (addressForBind != null)
                    {
                        // Use the given addresses to bind and to connect, instead of the remotehost string
                        // (this is used when connecting to localhost addresses, as the forwarder will generate a random address
                        // in the range 127.0.0.1-127.255.255.254 to minimize the risk of re-using a combination of a specific local endpoint to connect
                        // to a specific remote endpoint in a short interval of time, which is not permitted by TCP/IP to prohibit data corruption).
                        c.Client.Bind(new IPEndPoint(addressForBind, 0));
                    }

                    if (addressForConnect != null)
                    {
                        await c.ConnectAsync(addressForConnect, forwarder.RemotePort);
                    }
                    else
                    {
                        await c.ConnectAsync(forwarder.RemoteHost, forwarder.RemotePort);
                    }
                } catch (Exception ex) {
                    if (ExceptionUtils.ShouldExceptionBeRethrown(ex))
                    {
                        throw;
                    }

                    if (e == null)
                    {
                        e = ex;
                    }
                    else
                    {
                        e = ex; // TODO!
                    }
                    c.Close();
                    continue;
                }

                // OK, Connection established
                usedIpv6 = adrFamily == AddressFamily.InterNetworkV6;
                server   = c;
                e        = null;
                break;
            }


            if (e != null)
            {
                // TODO maybe also pause here if pause is enabled for the server.

                OnConnectionAborted(false, e);

                // We could not establish the connection. Therefore we need to close the client socket.
                Cancel();

                return;
            }

            OnRemoteConnectionEstablished(usedIpv6, (IPEndPoint)server.Client.LocalEndPoint, (IPEndPoint)server.Client.RemoteEndPoint);


            strServer = server.GetStream();

            // If we use client SSL, then create an SSL Stream and authenticate
            // asynchronously.
            if (forwarder.RemoteSslHost != null)
            {
                SslStream stream = new SslStream(strServer, true);
                try {
                    await stream.AuthenticateAsClientAsync(forwarder.RemoteSslHost,
                                                           new System.Security.Cryptography.X509Certificates.X509CertificateCollection(),
                                                           forwarder.RemoteSslProtocols, false);
                } catch (Exception ex) {
                    if (ExceptionUtils.ShouldExceptionBeRethrown(ex))
                    {
                        throw;
                    }

                    // TOODO maybe also pause here if pause is enabled for the server.

                    OnConnectionAborted(false, ex);

                    // We could not authenticate the connection. Therefore we need to close the client and server sockets.
                    Cancel();

                    return;
                }

                // Authentication was OK - now use the SSLStream to transfer data.
                strServer = stream;
                System.Security.Cryptography.X509Certificates.X509Certificate2 remoteCert = new System.Security.Cryptography.X509Certificates.X509Certificate2(stream.RemoteCertificate);

                OnRemoteConnectionAuthenticated(stream.SslProtocol, remoteCert, stream.CipherAlgorithm, stream.CipherStrength,
                                                stream.HashAlgorithm, stream.HashStrength, stream.KeyExchangeAlgorithm, stream.KeyExchangeStrength);
            }


            // now start to read from the client and pass the data to the server.
            // Also, start to read from the server and pass the data to the client.

            // Note: The read timeout should only be used to determine if the remote endpoint is not available any more.
            // This means, if we can't read data for a long while we still can write data, a timeout should not occur.
            // Therefore we reset the timeout if we still can write data.
            string             exMsg = "The socket {0} operation exceeded the timeout of {{0}} ms.";
            AsyncTimeoutHelper clientSendTimeoutHelper    = new AsyncTimeoutHelper(sendTimeout, ex => AbortConnection(true, ex), string.Format(exMsg, "write"));
            AsyncTimeoutHelper clientReceiveTimeoutHelper = new AsyncTimeoutHelper(receiveTimeout, ex => AbortConnection(true, ex), string.Format(exMsg, "read"));
            AsyncTimeoutHelper serverSendTimeoutHelper    = new AsyncTimeoutHelper(sendTimeout, ex => AbortConnection(false, ex), string.Format(exMsg, "write"));
            AsyncTimeoutHelper serverReceiveTimeoutHelper = new AsyncTimeoutHelper(receiveTimeout, ex => AbortConnection(false, ex), string.Format(exMsg, "read"));

            // Use ExceptionUtils.WrapTaskForHandlingUnhandledExceptions because we start two tasks and only await t2 after t1 is finished.
            Task t1 = ExceptionUtils.WrapTaskForHandlingUnhandledExceptions(async() => await CopyStreamAsync(true, client, server, strClient, strServer, clientReceiveTimeoutHelper, serverSendTimeoutHelper, serverReceiveTimeoutHelper));
            Task t2 = ExceptionUtils.WrapTaskForHandlingUnhandledExceptions(async() => await CopyStreamAsync(false, server, client, strServer, strClient, serverReceiveTimeoutHelper, clientSendTimeoutHelper, clientReceiveTimeoutHelper));

            await t1;
            await t2;

            // Dispose the timeout helpers.
            await clientSendTimeoutHelper.DisposeAsync();

            await clientReceiveTimeoutHelper.DisposeAsync();

            await serverSendTimeoutHelper.DisposeAsync();

            await serverReceiveTimeoutHelper.DisposeAsync();


            // Copying the streams has finished.
            // In this state we should be able to Dispose the Streams and Sockets, as they initiated
            // shutdown so all data should be sent.
            // We don't need to lock on lockObjForClosing since every task that might close the sockets
            // (CopyStreamAsync() method, AsyncTimeoutHelper) has already finished in this state.
            strClient.Close();
            strServer.Close();
            client.Close();
            server.Close();
        }