Example #1
0
        private static void Splitter_HostDisconnected(object sender, HostInfo source)
        {
            HostInfo target;

            if (!activeConnections.TryRemove(source, out target))
            {
                return;
            }

            int targetConnections;

            lock (currentBalancing)
            {
                targetConnections        = currentBalancing[target] - 1;
                currentBalancing[target] = targetConnections;
            }

            logger.Info($"{source} disconnected from {target} ({activeConnections.Count} total, {targetConnections} on target)");
        }
Example #2
0
        private void OnHttpConnection(IAsyncResult asyncResult)
        {
            if (!running)
            {
                return;
            }

            try
            {
                // Accept client
                HttpListenerContext context = httpListener.EndGetContext(asyncResult);

                IPEndPoint clientEndPoint = context.Request.RemoteEndPoint;
                HostInfo   clientInfo     = new HostInfo(clientEndPoint.Address.ToString(), (ushort)clientEndPoint.Port);

                new Thread(() =>
                {
                    // Connect to main server
                    HostInfo mainTargetInfo = targetBalancer(clientInfo);
                    if (mainTargetInfo == null)
                    {
                        return;
                    }

                    HostConnected?.Invoke(this, clientInfo);

                    // Create a HttpWebRequest
                    Func <HostInfo, HttpWebRequest> requestBuilder = (HostInfo target) =>
                    {
                        Uri uri = new Uri(new Uri($"http://{target.Hostname}:{target.Port}"), context.Request.RawUrl);
                        HttpWebRequest request = WebRequest.CreateHttp(uri);

                        // Copy headers
                        string xff = "";

                        foreach (string key in context.Request.Headers.Keys)
                        {
                            string value = context.Request.Headers[key];

                            switch (key.ToLower())
                            {
                            case "connection":
                                {
                                    switch (value.ToLower())
                                    {
                                    case "keep-alive": request.KeepAlive = true; break;

                                    case "close": break;

                                    default: request.Connection = value; break;
                                    }

                                    break;
                                }

                            case "host": break;

                            case "accept": request.Accept = value; break;

                            case "user-agent": request.UserAgent = value; break;

                            case "x-forwarded-for": xff = value; break;

                            case "content-length": request.ContentLength = int.Parse(value); break;

                            case "content-type": request.ContentType = value; break;

                            case "referer": request.Referer = value; break;

                            default:
                                request.Headers.Add(key, value);
                                break;
                            }
                        }

                        request.Method = context.Request.HttpMethod;
                        request.Host   = $"{target.Hostname}:{target.Port}";
                        request.Headers["X-Forwarded-For"] = string.IsNullOrEmpty(xff) ? clientInfo.Hostname : $"{xff}, {clientInfo.Hostname}";

                        return(request);
                    };

                    try
                    {
                        // Copy stream if needed
                        Stream requestStream = context.Request.InputStream;

                        HostInfo[] otherTargets = targetCloner(clientInfo).ToArray();
                        if (otherTargets.Length > 0)
                        {
                            MemoryStream bufferedStream = new MemoryStream();
                            requestStream.CopyTo(bufferedStream);
                            requestStream = bufferedStream;

                            requestStream.Seek(0, SeekOrigin.Begin);
                        }

                        // Execute request and copy stream
                        HttpWebRequest mainTargetRequest = requestBuilder(mainTargetInfo);

                        if (context.Request.HttpMethod != "GET")
                        {
                            using (Stream mainTargetStream = mainTargetRequest.GetRequestStream())
                                requestStream.CopyTo(mainTargetStream);
                        }

                        // Send request to secondary targets
                        new Thread(() =>
                        {
                            foreach (HostInfo target in otherTargets)
                            {
                                HttpWebRequest targetRequest = requestBuilder(target);

                                if (context.Request.HttpMethod != "GET")
                                {
                                    requestStream.Seek(0, SeekOrigin.Begin);

                                    using (Stream targetStream = targetRequest.GetRequestStream())
                                        requestStream.CopyTo(targetStream);
                                }

                                try
                                {
                                    targetRequest.GetResponse();
                                }
                                catch { }
                            }
                        }).Start();

                        HttpWebResponse mainTargetResponse;

                        try
                        {
                            mainTargetResponse = mainTargetRequest.GetResponse() as HttpWebResponse;
                        }
                        catch (WebException e)
                        {
                            mainTargetResponse = e.Response as HttpWebResponse;
                        }

                        // Copy headers
                        foreach (string key in mainTargetResponse.Headers.Keys)
                        {
                            string value = mainTargetResponse.Headers[key];

                            switch (key.ToLower())
                            {
                            case "transfer-encoding": break;

                            case "content-length": context.Response.ContentLength64 = int.Parse(value); break;

                            default:
                                context.Response.Headers.Add(key, value);
                                break;
                            }
                        }

                        context.Response.StatusCode = (int)mainTargetResponse.StatusCode;

                        try
                        {
                            using (Stream mainTargetStream = mainTargetResponse.GetResponseStream())
                                mainTargetStream.CopyTo(context.Response.OutputStream);
                        }
                        catch (Exception e)
                        {
                            logger.Warn($"Error while writing HTTP response. The client might have disconnected. " + e);
                        }

                        context.Response.Close();
                    }
                    catch (Exception e)
                    {
                        logger.Warn($"Error while processing HTTP client. " + e);
                    }
                    finally
                    {
                        HostDisconnected?.Invoke(this, clientInfo);
                    }
                }).Start();
            }
            catch (Exception e)
            {
                logger.Warn($"Error while processing HTTP client. " + e);
            }

            httpListener.BeginGetContext(OnHttpConnection, null);
        }
Example #3
0
 public TargetConnection(HostInfo target, TcpClient connection, NetworkStream stream)
 {
     Target     = target;
     Connection = connection;
     Stream     = stream;
 }
Example #4
0
        private void OnTcpConnection(IAsyncResult asyncResult)
        {
            if (!running)
            {
                return;
            }

            try
            {
                // Accept client
                TcpClient client = tcpListener.EndAcceptTcpClient(asyncResult);

                IPEndPoint clientEndPoint = client.Client.RemoteEndPoint as IPEndPoint;
                HostInfo   clientInfo     = new HostInfo(clientEndPoint.Address.ToString(), (ushort)clientEndPoint.Port);

                new Thread(() =>
                {
                    // Connect to main server
                    HostInfo mainTargetInfo = targetBalancer(clientInfo);
                    if (mainTargetInfo == null)
                    {
                        return;
                    }

                    TargetConnection mainTarget;

                    HostConnected?.Invoke(this, clientInfo);

                    try
                    {
                        TcpClient mainTargetConnection = new TcpClient(mainTargetInfo.Hostname, mainTargetInfo.Port);
                        mainTarget = new TargetConnection(mainTargetInfo, mainTargetConnection, mainTargetConnection.GetStream());
                    }
                    catch (Exception e)
                    {
                        logger.Warn($"Could not connect to output {mainTargetInfo.Hostname}:{mainTargetInfo.Port}. Skipping connection. {e}");

                        HostDisconnected?.Invoke(this, clientInfo);
                        return;
                    }

                    Dictionary <HostInfo, TargetConnection> outputTargets = targetCloner(clientInfo).ToDictionary <HostInfo, HostInfo, TargetConnection>(t => t, t => null);

                    ClientConnection clientConnection = new ClientConnection(clientInfo, client, client.GetStream(), mainTarget, outputTargets);
                    activeConnections.Add(clientConnection);

                    // Connect to output targets
                    foreach (var outputInfo in clientConnection.OutputTargets.ToArray())
                    {
                        try
                        {
                            TcpClient outputClient = new TcpClient();

                            Task connectTask = outputClient.ConnectAsync(outputInfo.Key.Hostname, outputInfo.Key.Port);
                            if (!connectTask.Wait(Program.Timeout))
                            {
                                logger.Warn($"Could not connect to output {outputInfo.Key.Hostname}:{outputInfo.Key.Port} after {Program.Timeout}. Output will be skipped");
                                continue;
                            }

                            TargetConnection outputConnection = new TargetConnection(outputInfo.Key, outputClient, outputClient.GetStream());
                            clientConnection.OutputTargets[outputInfo.Key] = outputConnection;
                        }
                        catch (Exception e)
                        {
                            logger.Warn($"Could not connect to output {outputInfo.Key.Hostname}:{outputInfo.Key.Port}. Output will be skipped. {e}");
                        }
                    }

                    // Client > Target
                    new Thread(() =>
                    {
                        Exception exception = null;

                        try
                        {
                            byte[] buffer = new byte[bufferSize];

                            while (true)
                            {
                                int read = clientConnection.Stream.Read(buffer, 0, buffer.Length);
                                if (read == 0)
                                {
                                    break;
                                }

                                foreach (var targetInfo in outputTargets.ToArray())
                                {
                                    if (targetInfo.Value == null)
                                    {
                                        continue;
                                    }

                                    ulong totalSize = (ulong)targetInfo.Value.Buffers.Count * bufferSize + bufferSize;
                                    if (totalSize > Program.BufferSize)
                                    {
                                        logger.Warn($"Output target {targetInfo.Key.Hostname}:{targetInfo.Key.Port} has reach its maximum buffer size. Output will be skipped");

                                        outputTargets[targetInfo.Key] = null;
                                        Try(targetInfo.Value.Connection.Close);

                                        continue;
                                    }

                                    targetInfo.Value.Buffers.Enqueue(new ArraySegment <byte>(buffer, 0, read));
                                }

                                mainTarget.Stream.Write(buffer, 0, read);
                                mainTarget.Stream.Flush();

                                buffer = new byte[bufferSize];
                            }
                        }
                        catch (Exception e)
                        {
                            exception = e;
                        }
                        finally
                        {
                            Try(mainTarget.Connection.Close);

                            foreach (TargetConnection targetConnection in outputTargets.Values)
                            {
                                if (targetConnection != null)
                                {
                                    Try(targetConnection.Connection.Close);
                                }
                            }

                            lock (activeConnections)
                            {
                                if (activeConnections.Remove(clientConnection))
                                {
                                    if (exception != null)
                                    {
                                        if (!(exception is IOException) || !(exception.InnerException is SocketException))
                                        {
                                            logger.Warn("Exception while reading from client. " + exception);
                                        }
                                    }

                                    HostDisconnected?.Invoke(this, clientConnection.ClientInfo);
                                }
                            }
                        }
                    }).Start();

                    // Target > Client
                    new Thread(() =>
                    {
                        Exception exception = null;

                        try
                        {
                            byte[] buffer = new byte[bufferSize];

                            while (true)
                            {
                                int read = mainTarget.Stream.Read(buffer, 0, buffer.Length);
                                if (read == 0)
                                {
                                    break;
                                }

                                clientConnection.Stream.Write(buffer, 0, read);
                                clientConnection.Stream.Flush();
                            }
                        }
                        catch (Exception e)
                        {
                            exception = e;
                        }
                        finally
                        {
                            Try(client.Close);

                            foreach (TargetConnection targetConnection in outputTargets.Values)
                            {
                                if (targetConnection != null)
                                {
                                    Try(targetConnection.Connection.Close);
                                }
                            }

                            lock (activeConnections)
                            {
                                if (activeConnections.Remove(clientConnection))
                                {
                                    if (exception != null)
                                    {
                                        if (!(exception is IOException) || !(exception.InnerException is SocketException))
                                        {
                                            logger.Warn("Exception while reading from target. " + exception);
                                        }
                                    }

                                    HostDisconnected?.Invoke(this, clientConnection.ClientInfo);
                                }
                            }
                        }
                    }).Start();

                    // Flush output target connections
                    byte[] readBuffer = new byte[bufferSize];

                    while (true)
                    {
                        int outputTargetCount = 0;
                        int dequeuedBuffers   = 0;

                        foreach (var outputInfo in clientConnection.OutputTargets.ToArray())
                        {
                            if (outputInfo.Value == null)
                            {
                                continue;
                            }

                            outputTargetCount++;

                            try
                            {
                                ArraySegment <byte> buffer;
                                while (outputInfo.Value.Buffers.TryDequeue(out buffer))
                                {
                                    dequeuedBuffers++;

                                    outputInfo.Value.Stream.Write(buffer.Array, buffer.Offset, buffer.Count);
                                    outputInfo.Value.Stream.Flush();
                                }

                                while (outputInfo.Value.Stream.DataAvailable)
                                {
                                    outputInfo.Value.Stream.Read(readBuffer, 0, readBuffer.Length);
                                }
                            }
                            catch
                            {
                                logger.Warn($"Could not send buffer to output target {outputInfo.Key.Hostname}:{outputInfo.Key.Port}. Output will be skipped");

                                outputTargets[outputInfo.Key] = null;
                                Try(outputInfo.Value.Connection.Close);

                                continue;
                            }
                        }

                        if (outputTargetCount == 0)
                        {
                            break;
                        }

                        if (dequeuedBuffers == 0)
                        {
                            Thread.Sleep(10);
                        }
                    }
                }).Start();
            }
            catch (Exception e)
            {
                logger.Warn($"Error while processing TCP client. " + e);
            }

            tcpListener.BeginAcceptTcpClient(OnTcpConnection, null);
        }
Example #5
0
 private static IEnumerable <HostInfo> TargetCloner(HostInfo source)
 {
     return(cloningTargets);
 }
Example #6
0
        private static HostInfo TargetBalancer(HostInfo source)
        {
            HostInfo target = null;

            if (balancingTargets.Count == 1)
            {
                target = balancingTargets.Keys.First();
            }
            else if (balancingTargets.Count > 1)
            {
                Func <Random, HostInfo> randomSelector = random =>
                {
                    double value   = random.NextDouble() * balancingTargetsTotal;
                    double current = 0;

                    foreach (var pair in balancingTargets)
                    {
                        current += pair.Value;

                        if (value <= current)
                        {
                            return(pair.Key);
                        }
                    }

                    return(null);
                };

                if (BalancingMode == BalancingMode.Random)
                {
                    target = randomSelector(balancingTargetsRandom);
                }
                else if (BalancingMode == BalancingMode.IPHash)
                {
                    int    hash   = source.Hostname.GetHashCode();
                    Random random = new Random(hash);

                    target = randomSelector(random);
                }
                else if (BalancingMode == BalancingMode.LeastConn)
                {
                    target = balancingTargets.OrderBy(p => currentBalancing[p.Key] / p.Value).FirstOrDefault().Key;
                }
            }

            if (target != null)
            {
                activeConnections[source] = target;

                int targetConnections;
                lock (currentBalancing)
                {
                    targetConnections        = currentBalancing[target] + 1;
                    currentBalancing[target] = targetConnections;
                }

                logger.Info($"{source} connected to {target} ({activeConnections.Count} total, {targetConnections} on target)");
            }
            else
            {
                logger.Info($"No balancing target, {source} will be skipped");
            }

            return(target);
        }
Example #7
0
 private static void Splitter_HostConnected(object sender, HostInfo source)
 {
 }