123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net;
- using System.Net.Sockets;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using DateTime = System.DateTime;
- namespace TcpRedirector
- {
- internal class Program
- {
- private static int _proxyPort;
- public static string ProxyHost { get; private set; }
- public static int ProxyPort => _proxyPort;
- public static IReadOnlyList<byte> TargetHostBytes { get; private set; }
- public static IReadOnlyList<byte> TargetPortBytes { get; private set; }
- public static bool IsRunning { get; private set; }
- private static int Number = 0;
- public static int GetNumber()
- {
- return Interlocked.Increment(ref Number);
- }
- private static int Main(string[] args)
- {
- if (args.Length < 6)
- {
- Console.WriteLine("listenAddress listenPort proxyHost proxyPort targetHost targetPort");
- return -1;
- }
- if (!IPAddress.TryParse(args[0], out var listenAddress)) listenAddress = IPAddress.Any;
- if (!int.TryParse(args[1], out var listenPort)) listenPort = 0;
- ProxyHost = args[2];
- if (!int.TryParse(args[3], out _proxyPort)) _proxyPort = 0;
- var targetHost = args[4];
- if (!int.TryParse(args[5], out var targetPort)) targetPort = 0;
- TargetHostBytes = Encoding.ASCII.GetBytes(targetHost);
- TargetPortBytes = new[] { (byte)(targetPort << 8), (byte)targetPort };
- Console.Title = $"TR {listenAddress}:{listenPort} via {ProxyHost}:{ProxyPort} to {targetHost}:{targetPort}";
- IsRunning = true;
- var list = new List<Socks5Forwarder>();
- var startup = DateTime.Now;
- Task.Run(() =>
- {
- var listener = new TcpListener(new IPEndPoint(listenAddress, listenPort));
- listener.Start();
- Task<TcpClient> task = null;
- do
- {
- if (task == null)
- {
- task = listener.AcceptTcpClientAsync();
- }
- else if (task.IsCompleted)
- {
- var client = task.Result;
- lock (list)
- {
- list.Add(new Socks5Forwarder(client));
- }
- task = null;
- }
- Thread.Sleep(100);
- } while (IsRunning);
- listener.Stop();
- });
- while (IsRunning)
- {
- Console.Clear();
- Console.WriteLine($"UP {(DateTime.Now - startup).CompactFormat()} Since {startup}");
- //Print running info
- lock (list)
- {
- Console.WriteLine();
- Console.WriteLine($"Connections: {list.Count}");
- Console.WriteLine();
- foreach (var item in list)
- {
- Console.WriteLine(
- $"#{item.Number:0000}" +
- $" {item.CreatedTime:yyyy-MM-dd HH:mm:ss}"+
- $" {item.IncomingEndPoint?.ToString() ?? "Unknown",20}" +
- $" {item.Status}" +
- $" Wai:{(DateTime.Now - item.LastActiveTime).CompactFormat(),6}" +
- $" Out:{item.OutgoingBytes.UnitFormat() + "B",10}" +
- $" In:{item.IncomingBytes.UnitFormat() + "B",10}" +
- ""
- );
- if (string.IsNullOrWhiteSpace(item.Error) == false) Console.WriteLine($"\t{item.Error}");
- }
- //remove timeout items
- foreach (var item in list.ToArray())
- {
- if ((DateTime.Now - item.LastActiveTime).TotalMinutes > 1)
- {
- if (item.Status
- is Socks5ForwarderStatus.ERRBERU
- or Socks5ForwarderStatus.ERRTOPR
- or Socks5ForwarderStatus.ERRTOTA
- or Socks5ForwarderStatus.DISCONN
- )
- {
- list.Remove(item);
- }
- }
- }
- }
- if (Console.KeyAvailable)
- {
- switch (char.ToUpper(Console.ReadKey(true).KeyChar))
- {
- case 'Q':
- IsRunning = false;
- break;
- default:
- Console.WriteLine("Press Q to exit");
- break;
- }
- }
- Thread.Sleep(1000);
- }
- return 0;
- }
- }
- internal class Socks5Forwarder
- {
- public int Number { get; }
- public Socks5ForwarderStatus Status { get; private set; } = Socks5ForwarderStatus.PENDING;
- public DateTime CreatedTime { get; } = DateTime.Now;
- public DateTime LastActiveTime { get; set; }
- public EndPoint? IncomingEndPoint { get; }
- public int IncomingBytes { get; private set; }
- public int OutgoingBytes { get; private set; }
- public string Error { get; private set; }
- public Socks5Forwarder(TcpClient incomingClient)
- {
- Number = Program.GetNumber();
-
- IncomingEndPoint = incomingClient.Client.RemoteEndPoint;
-
- Task.Run(() =>
- {
- Status = Socks5ForwarderStatus.CONTOPR;
- TcpClient outgoingClient = null;
- NetworkStream outgoingStream;
- try
- {
- outgoingClient = new TcpClient(Program.ProxyHost, Program.ProxyPort);
- outgoingStream = outgoingClient.GetStream();
- LastActiveTime = DateTime.Now;
- //socks 5 hand shake
- outgoingStream.Write(new byte[] { 5, 1, 0 });
- if (outgoingStream.ReadByte() != 5) throw new Exception("Server version error");
- if (outgoingStream.ReadByte() != 0) throw new Exception("Server auth method error");
- }
- catch (Exception e)
- {
- Status = Socks5ForwarderStatus.ERRTOPR;
- Error = e.Message;
- goto FINISHED;
- }
- Status = Socks5ForwarderStatus.CONTOTA;
- try
- {
- LastActiveTime = DateTime.Now;
- //socks 5 connection
- var connect = new byte[] { 5, 1, 0, 3 }
- .Concat(new[] { (byte)Program.TargetHostBytes.Count })
- .Concat(Program.TargetHostBytes)
- .Concat(Program.TargetPortBytes).ToArray();
- outgoingStream.Write(connect);
- if (outgoingStream.ReadByte() != 5) throw new Exception("Server version error");
- var rep = outgoingStream.ReadByte();
- if (rep != 0) throw new Exception($"Server response error:{rep}");
- if (outgoingStream.ReadByte() != 0) throw new Exception($"Server response error");
- var aty = outgoingStream.ReadByte();
- var len = aty switch
- {
- 1 => 4,
- 3 => outgoingStream.ReadByte(),
- 4 => 16,
- _ => throw new Exception($"Server response AddressTYPE error")
- };
- for (var i = 0; i < len + 2; i++)
- {
- if (-1 == outgoingStream.ReadByte()) throw new Exception($"Server response padding error");
- }
- }
- catch (Exception e)
- {
- Status = Socks5ForwarderStatus.ERRTOTA;
- Error = e.Message;
- goto FINISHED;
- }
- NetworkStream incomingStream;
- try
- {
- incomingClient.NoDelay = true;
- outgoingClient.NoDelay = true;
- incomingStream = incomingClient.GetStream();
- }
- catch (Exception e)
- {
- Status = Socks5ForwarderStatus.ERRBERU;
- Error = e.Message;
- goto FINISHED;
- }
- Status = Socks5ForwarderStatus.RUNNING;
- LastActiveTime = DateTime.Now;
- //forward both
- var outgoingForward = Task.Run(() =>
- {
- var buf = new byte[6000];
- while (Program.IsRunning)
- {
- LastActiveTime = DateTime.Now;
- int read;
- try
- {
- read = incomingStream.Read(buf, 0, buf.Length);
- }
- catch (Exception e)
- {
- Status = Socks5ForwarderStatus.DISCONN;
- Error = $"Read Incoming: {e.Message}";
- break;
- }
- if (read == 0)
- {
- Status = Socks5ForwarderStatus.DISCONN;
- Error = "Incoming Closed";
- break;
- }
- OutgoingBytes += read;
- try
- {
- outgoingStream.Write(buf, 0, read);
- }
- catch (Exception e)
- {
- Status = Socks5ForwarderStatus.DISCONN;
- Error = $"Write Outgoing:{e.Message}";
- break;
- }
- }
- });
- var incomingForward = Task.Run(() =>
- {
- var buf = new byte[6000];
- while (Program.IsRunning)
- {
- LastActiveTime = DateTime.Now;
- int read;
- try
- {
- read = outgoingStream.Read(buf, 0, buf.Length);
- }
- catch (Exception e)
- {
- Status = Socks5ForwarderStatus.DISCONN;
- Error = $"Read Outgoing: {e.Message}";
- break;
- }
- if (read == 0)
- {
- Status = Socks5ForwarderStatus.DISCONN;
- Error = "Outgoing disconnected";
- break;
- }
- IncomingBytes += read;
- try
- {
- incomingStream.Write(buf, 0, read);
- }
- catch (Exception e)
- {
- Status = Socks5ForwarderStatus.DISCONN;
- Error = $"Write Incoming:{e.Message}";
- break;
- }
- }
- });
- Task.WaitAny(outgoingForward, incomingForward);
- FINISHED:
- try
- {
- outgoingClient?.Close();
- }
- catch
- {
- //EAT ERROR
- }
- try
- {
- incomingClient?.Close();
- }
- catch
- {
- //EAT ERROR
- }
- });
- }
- }
- internal enum Socks5ForwarderStatus
- {
- PENDING,
- CONTOPR,
- CONTOTA,
- RUNNING,
- ERRTOPR,
- ERRTOTA,
- ERRBERU,
- DISCONN,
- }
- internal static class OutputFormatter
- {
- public static string CompactFormat(this TimeSpan value)
- {
- var lst = new List<string>();
- if (Math.Abs(value.TotalDays) >= 1) lst.Add($"{value.TotalDays:00}d");
- if (Math.Abs(value.Hours) >= 1) lst.Add($"{value.Hours:00}h");
- if (Math.Abs(value.Minutes) >= 1 && lst.Count < 2) lst.Add($"{value.Minutes:00}m");
- if (Math.Abs(value.Seconds) >= 1 && lst.Count < 2) lst.Add($"{value.Seconds:00}s");
- if (lst.Count < 2) lst.Add($"{value.Milliseconds:000}");
- return string.Join("", lst);
- }
- public static string UnitFormat(this int value, int digit = 3)
- {
- const string unit = " KMGTPE";
- var val = (double)value;
- var index = 0;
- while (val > 1000 && index < unit.Length)
- {
- val /= 1000;
- ++index;
- }
- return val.ToString("N" + digit) + unit[index];
- }
- }
- }
|