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)"); }
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); }
public TargetConnection(HostInfo target, TcpClient connection, NetworkStream stream) { Target = target; Connection = connection; Stream = stream; }
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); }
private static IEnumerable <HostInfo> TargetCloner(HostInfo source) { return(cloningTargets); }
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); }
private static void Splitter_HostConnected(object sender, HostInfo source) { }