private async void ProcessLocalConnection(object state)
        {
            var connection = (ConnectionInfo)state;
            var buffer     = new byte[BufferSize];

            try
            {
                var bytesRead = connection.LocalSocket.Receive(buffer);
                if (bytesRead < 1 || buffer[0] != Protocol.Socks4.Version)
                {
                    connection.Terminate();
                    return;
                }

                OnLogMessage?.Invoke(this, $"Got {bytesRead} bytes from ");
            }
            catch (SocketException ex)
            {
                OnLogMessage?.Invoke(this, $"Caught SocketException in ProcessLocalConnection with error code {ex.SocketErrorCode}");
            }

            try
            {
                switch (buffer[1])
                {
                case Protocol.Socks4.CommandStreamConnection:
                {
                    var portBuffer = new[] { buffer[2], buffer[3] };
                    var port       = (ushort)(portBuffer[0] << 8 | portBuffer[1]);

                    var address           = new[] { buffer[4], buffer[5], buffer[6], buffer[7] };
                    var destAddress       = new IPAddress(address).ToString();
                    var destAddressFamily = AddressFamily.InterNetwork;

                    if (IsSocks4AProtocol(address))
                    {
                        var hostBuffer = new byte[256];
                        Buffer.BlockCopy(buffer, 9, hostBuffer, 0, 100);

                        // Resolve hostname, fallback to remote proxy dns resolution
                        var hostname = Encoding.ASCII.GetString(hostBuffer).TrimEnd((char)0);
                        if (!ResolveHostnamesRemotely)
                        {
                            var resolvedHostname = await DnsResolver.TryResolve(hostname);

                            if (resolvedHostname == null)
                            {
                                OnLogMessage?.Invoke(this, $"DNS resolution failed for {hostname}");
                                SendSocks4Reply(connection.LocalSocket, Protocol.Socks4.StatusRequestGranted, address, portBuffer);
                                connection.Terminate();
                                break;
                            }

                            destAddress       = resolvedHostname.ToString();
                            destAddressFamily = resolvedHostname.AddressFamily;
                        }
                        else
                        {
                            destAddress       = hostname;
                            destAddressFamily = AddressFamily.Unspecified;
                        }
                    }

                    connection.RemoteSocket = Socks5Client.Connect(
                        RemotEndPoint.Address.ToString(),
                        RemotEndPoint.Port,
                        destAddress,
                        port,
                        Username,
                        Password,
                        SendTimeout,
                        ReceiveTimeout);

                    OnRemoteConnect?.Invoke(this, new DnsEndPoint(destAddress, port, destAddressFamily));

                    if (connection.RemoteSocket.Connected)
                    {
                        OnLogMessage?.Invoke(this, "RelayBiDirectionally between server and client started");
                        SendSocks4Reply(connection.LocalSocket, Protocol.Socks4.StatusRequestGranted, address, portBuffer);
                        SocketRelay.RelayBiDirectionally(connection.RemoteSocket, connection.LocalSocket);
                    }
                    else
                    {
                        OnLogMessage?.Invoke(this, "RemoteSocket connection failed");
                        SendSocks4Reply(connection.LocalSocket, Protocol.Socks4.StatusRequestFailed, address, portBuffer);
                        connection.Terminate();
                    }

                    break;
                }

                case Protocol.Socks4.CommandBindingConnection:
                {
                    var portBuffer = new[] { buffer[2], buffer[3] };
                    var address    = new[] { buffer[4], buffer[5], buffer[6], buffer[7] };

                    // TCP/IP port binding not supported
                    SendSocks4Reply(connection.LocalSocket, Protocol.Socks4.StatusRequestFailed, address, portBuffer);
                    connection.Terminate();
                    break;
                }

                default:
                    OnLogMessage?.Invoke(this, "Unknown protocol on LocalSocket");
                    connection.Terminate();
                    break;
                }
            }
            catch (SocketException ex)
            {
                OnLogMessage?.Invoke(this, $"Caught SocketException in ProcessLocalConnection with error code {ex.SocketErrorCode.ToString()}");
            }
            catch (Socks5Exception ex)
            {
                var portBuffer = new[] { buffer[2], buffer[3] };
                var address    = new[] { buffer[4], buffer[5], buffer[6], buffer[7] };

                SendSocks4Reply(connection.LocalSocket, Protocol.Socks4.StatusRequestFailed, address, portBuffer);
                connection.Terminate();

                OnLogMessage?.Invoke(this, $"Caught Socks5Exception in ProcessLocalConnection with message {ex.Message}");
            }
        }
        public static Socket Connect(string socksAddress, int socksPort, string destAddress, int destPort, string username, string password, int sendTimeout, int receiveTimeout)
        {
            var client = new Socks5Client(socksAddress, socksPort, destAddress, destPort, username, password, sendTimeout, receiveTimeout);

            return(client.Connect());
        }
        private void ProcessLocalConnection(object state)
        {
            var connection = (ConnectionInfo)state;
            var buffer     = new byte[BufferSize];
            int bytesRead;

            try
            {
                bytesRead = connection.LocalSocket.Receive(buffer);
                if (bytesRead < 1 || buffer[0] != Protocol.Socks4.Version)
                {
                    connection.LocalSocket.Close();
                    return;
                }

                OnLogMessage?.Invoke(this, $"LocalSocket.Receive {bytesRead}");
            }
            catch (SocketException ex)
            {
                OnLogMessage?.Invoke(this, $"Caught SocketException in ProcessLocalConnection with error code {ex.SocketErrorCode.ToString()}");
            }

            try
            {
                switch (buffer[1])
                {
                case Protocol.Socks4.CommandStreamConnection:
                {
                    var portBuffer = new[] { buffer[2], buffer[3] };
                    var port       = (ushort)(portBuffer[0] << 8 | portBuffer[1]);

                    var ipBuffer = new[] { buffer[4], buffer[5], buffer[6], buffer[7] };
                    var ip       = new IPAddress(ipBuffer);

                    var destinationEndPoint = new IPEndPoint(ip, port);
                    if (IsSocks4AProtocol(ipBuffer))
                    {
                        var hostBuffer = new byte[256];
                        Buffer.BlockCopy(buffer, 9, hostBuffer, 0, 100);

                        // Resolve hostname, fallback to remote proxy dns resolution
                        var hostname      = Encoding.ASCII.GetString(hostBuffer).TrimEnd((char)0);
                        var destinationIp = ResolveHostnamesRemotely ? null : DnsResolver.TryResolve(hostname);

                        connection.RemoteSocket = Socks5Client.Connect(
                            RemotEndPoint.Address.ToString(),
                            RemotEndPoint.Port, destinationIp == null ? hostname : destinationIp.ToString(),
                            port,
                            Username,
                            Password,
                            SendTimeout,
                            ReceiveTimeout
                            );

                        OnRemoteConnect?.Invoke(this, destinationEndPoint);
                    }
                    else
                    {
                        destinationEndPoint     = new IPEndPoint(new IPAddress(ipBuffer), port);
                        connection.RemoteSocket = Socks5Client.Connect(
                            RemotEndPoint.Address.ToString(),
                            RemotEndPoint.Port,
                            destinationEndPoint.Address.ToString(),
                            port,
                            Username,
                            Password,
                            SendTimeout,
                            ReceiveTimeout
                            );

                        OnRemoteConnect?.Invoke(this, destinationEndPoint);
                    }

                    if (connection.RemoteSocket.Connected)
                    {
                        SendSocks4Reply(connection.LocalSocket, Protocol.Socks4.StatusRequestGranted, ipBuffer, portBuffer);

                        // Create the thread for the receives.
                        connection.RemoteThread = new Thread(ProcessRemoteConnection)
                        {
                            IsBackground = true
                        };
                        connection.RemoteThread.Start(connection);
                    }
                    else
                    {
                        OnLogMessage?.Invoke(this, "RemoteSocket connection failed");
                        SendSocks4Reply(connection.LocalSocket, Protocol.Socks4.StatusRequestFailed, ipBuffer, portBuffer);
                        connection.LocalSocket.Close();
                    }

                    break;
                }

                case Protocol.Socks4.CommandBindingConnection:
                {
                    var portBuffer = new[] { buffer[2], buffer[3] };
                    var ipBuffer   = new[] { buffer[4], buffer[5], buffer[6], buffer[7] };

                    // TCP/IP port binding not supported
                    SendSocks4Reply(connection.LocalSocket, Protocol.Socks4.StatusRequestFailed, ipBuffer, portBuffer);
                    connection.LocalSocket.Close();
                    break;
                }

                default:
                    OnLogMessage?.Invoke(this, "Unknown protocol on LocalSocket");
                    connection.LocalSocket.Close();
                    break;
                }

                // start receiving actual data if the socket still open
                while (true)
                {
                    if (!connection.LocalSocket.Connected || !connection.RemoteSocket.Connected)
                    {
                        break;
                    }

                    bytesRead = connection.LocalSocket.Receive(buffer);
                    if (bytesRead == 0)
                    {
                        break;
                    }

                    if (connection.RemoteSocket.Connected)
                    {
                        connection.RemoteSocket.Send(buffer, bytesRead, SocketFlags.None);
                        OnLogMessage?.Invoke(this, $"Forwarded {bytesRead} bytes from LocalSocket to RemoteSocket");
                    }
                }
            }
            catch (SocketException ex)
            {
                OnLogMessage?.Invoke(this, $"Caught SocketException in ProcessLocalConnection with error code {ex.SocketErrorCode.ToString()}");
            }
            catch (Socks5Exception ex)
            {
                var portBuffer = new[] { buffer[2], buffer[3] };
                var ipBuffer   = new[] { buffer[4], buffer[5], buffer[6], buffer[7] };

                SendSocks4Reply(connection.LocalSocket, Protocol.Socks4.StatusRequestFailed, ipBuffer, portBuffer);
                connection.LocalSocket.Close();

                OnLogMessage?.Invoke(this, $"Caught Socks5Exception in ProcessLocalConnection with message {ex.Message}");
            }

            if (connection.LocalSocket.Connected)
            {
                OnLogMessage?.Invoke(this, "Closing LocalSocket");
                connection.LocalSocket.Close();
            }

            lock (_connections)
            {
                _connections.Remove(connection);
            }
        }