diff --git a/AutoTunnel/AutoTunnel.csproj b/AutoTunnel/AutoTunnel.csproj index f73295a..1ba1b64 100644 --- a/AutoTunnel/AutoTunnel.csproj +++ b/AutoTunnel/AutoTunnel.csproj @@ -69,6 +69,7 @@ PreserveNewest + @@ -102,6 +103,7 @@ + diff --git a/AutoTunnel/BaseSender.cs b/AutoTunnel/BaseSender.cs index 051b100..1a5b0ab 100644 --- a/AutoTunnel/BaseSender.cs +++ b/AutoTunnel/BaseSender.cs @@ -20,9 +20,9 @@ public abstract class BaseSender : IDisposable protected readonly TunnelStorage Storage; - public TunnelStorage.Session Session { get; set; } + public TunnelSession Session { get; set; } - protected BaseSender(TunnelStorage.Session session, IPAddress watchAddr, TunnelStorage storage) + protected BaseSender(TunnelSession session, IPAddress watchAddr, TunnelStorage storage) { Storage = storage; Session = session; diff --git a/AutoTunnel/ClientSender.cs b/AutoTunnel/ClientSender.cs index 99844b7..40dff41 100644 --- a/AutoTunnel/ClientSender.cs +++ b/AutoTunnel/ClientSender.cs @@ -84,6 +84,16 @@ private void Init() Task.Factory.StartNew(InitInternal); } + private void CloseSocket() + { + if (_socket != null) + { + _socket.Dispose(); + } + + _socket = null; + } + private void InitInternal() { if (Interlocked.CompareExchange(ref _isIniting, 1, 0) == 1) @@ -126,8 +136,7 @@ private void InitInternal() var recLength = 0; // killing old socket - if (_socket != null) - _socket.Dispose(); + CloseSocket(); _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); _socket.Connect(destEP); @@ -170,7 +179,7 @@ private void InitInternal() LogHelper.Log.WriteLine("No response from server " + destEP); if (DateTime.UtcNow.Subtract(_lastInitRequest).TotalSeconds > 60) { - LogHelper.Log.WriteLine("Stopping connect atteptions to " + destEP + " until another request will occure"); + LogHelper.Log.WriteLine("Stopping connect atteptions to " + destEP + " until another request will occur"); } } @@ -205,36 +214,42 @@ private void InitInternal() } } + private void DropInit() + { + _isInited = false; + if (_config.KeepAlive) + Init(); + } + private void PingCycle() { - DateTime lastReceiveLast = DateTime.MinValue; + DateTime lastPingDate = DateTime.MinValue; + var pingSpan = TimeSpan.FromSeconds(_config.PingInterval); + while (!_disposed) { if (_isInited) { // problem with server? no answers, dropping connection - if (Session.LastActivity == lastReceiveLast) + if (Session.SendReceiveDifference > TimeSpan.FromSeconds(_config.PingInterval * 2)) { - _isInited = false; - lastReceiveLast = DateTime.MinValue; - if (_config.KeepAlive) - Init(); + DropInit(); } - if (_config.KeepAlive || _lastSend.Subtract(Session.LastActivity).TotalSeconds > _config.PingInterval) + if ((_config.KeepAlive && DateTime.UtcNow.Subtract(lastPingDate) > pingSpan) || Session.SendReceiveDifference > pingSpan) { - lastReceiveLast = Session.LastActivity; _socket.Send(new byte[] { (byte)StateFlags.Ping, 0, 0, 0 }, 4, SocketFlags.None); - _lastSend = DateTime.UtcNow; + lastPingDate = DateTime.UtcNow; } } else { - // force renew connection attepmt - _lastInitRequest = DateTime.UtcNow; + // force renew connection attempt + if (_config.KeepAlive) + _lastInitRequest = DateTime.UtcNow; } - Thread.Sleep(_config.PingInterval * 1000); + Thread.Sleep(1000); } } @@ -251,15 +266,13 @@ protected override void Send(byte[] packet, int packetLen) { var lenToSend = _encryptHelper.Encrypt(packet, packetLen); var packetToSend = _encryptHelper.InnerBuf; - _lastSend = DateTime.UtcNow; + Session.UpdateReceiveActivity(); _socket.Send(packetToSend, lenToSend, SocketFlags.None); } } private readonly byte[] _receiveBuffer = new byte[65536]; - private DateTime _lastSend; - private void ReceiveCycle() { byte[] buf = _receiveBuffer; @@ -276,18 +289,14 @@ private void ReceiveCycle() { len = _socket.Receive(buf); } - catch (Exception ex) + catch (Exception/* ex*/) { if (_isIniting == 1 && _isInited) { LogHelper.Log.WriteLine("Receive data error"); _isInited = false; // LogHelper.Log.WriteLine(ex); - if (_socket != null) - { - _socket.Dispose(); - _socket = null; - } + CloseSocket(); if (_config.KeepAlive) Init(); } @@ -303,14 +312,12 @@ private void ReceiveCycle() if (buf[0] != (byte)StateFlags.Pong) { LogHelper.Log.WriteLine("Received an error flag from " + _socket.RemoteEndPoint); - _isInited = false; - // failed data - Init(); + DropInit(); continue; } } - Session.UpdateLastActivity(); + Session.UpdateReceiveActivity(); var decryptHelper = Session.Decryptor; var decLen = decryptHelper.Decrypt(buf, 0); @@ -323,7 +330,7 @@ public override void Dispose() base.Dispose(); _disposed = true; _initingEvent.Set(); - _socket.Dispose(); + CloseSocket(); } } } diff --git a/AutoTunnel/Config/ConfigHelper.cs b/AutoTunnel/Config/ConfigHelper.cs new file mode 100644 index 0000000..79cc718 --- /dev/null +++ b/AutoTunnel/Config/ConfigHelper.cs @@ -0,0 +1,124 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +using Force.AutoTunnel.Logging; + +using Newtonsoft.Json; + +namespace Force.AutoTunnel.Config +{ + public static class ConfigHelper + { + public static MainConfig Config { get; private set; } + + private static FileSystemWatcher _fsw; + + public static bool LoadConfig(bool isFirstTime) + { + try + { + if (_fsw != null) + { + _fsw.Dispose(); + _fsw = null; + } + + if (!isFirstTime) + LogHelper.Log.WriteLine("Reloading config"); + + var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json"); + if (!File.Exists(configPath)) + { + if (isFirstTime) + Console.Error.WriteLine("Missing config file"); + else + LogHelper.Log.WriteLine("Missing config file"); + + return false; + } + + MainConfig config; + using (var f = File.OpenRead(configPath)) + config = new JsonSerializer().Deserialize(new JsonTextReader(new StreamReader(f))); + + if (config.RemoteClients == null) + config.RemoteClients = new RemoteClientConfig[0]; + if (config.RemoteClients.Length == 0) + config.EnableListening = false; + if (config.RemoteServers == null) + config.RemoteServers = new RemoteServerConfig[0]; + foreach (var remoteServerConfig in config.RemoteServers) + { + if (string.IsNullOrEmpty(remoteServerConfig.ConnectHost) && string.IsNullOrEmpty(remoteServerConfig.TunnelHost)) + throw new InvalidOperationException("Missing host info in config"); + + if (string.IsNullOrEmpty(remoteServerConfig.TunnelHost)) + remoteServerConfig.TunnelHost = remoteServerConfig.ConnectHost; + if (string.IsNullOrEmpty(remoteServerConfig.ConnectHost)) + remoteServerConfig.ConnectHost = remoteServerConfig.TunnelHost; + } + + if (!isFirstTime) + Starter.Stop(); + + var log = new AggregateLog(); + if (Environment.UserInteractive) log.AddLog(new ConsoleLog()); + if (!string.IsNullOrEmpty(config.LogFileName)) log.AddLog(new FileLog(config.LogFileName)); + LogHelper.SetLog(log); + + Config = config; + + if (!isFirstTime) + Starter.Start(); + + if (config.AutoReloadOnChange) + { + _fsw = new FileSystemWatcher(Path.GetDirectoryName(configPath) ?? string.Empty, Path.GetFileName(configPath) ?? string.Empty); + _fsw.Changed += FswOnChanged; + _fsw.Created += FswOnChanged; + _fsw.Deleted += FswOnChanged; + _fsw.EnableRaisingEvents = true; + } + } + catch (Exception ex) + { + if (isFirstTime) Console.Error.WriteLine("Error in parsing config: " + ex.Message); + else + { + LogHelper.Log.WriteLine("Error in parsing config. Leaving old config " + ex.Message); + } + + return false; + } + + return true; + } + + private static DateTime _reloadTime; + + private static Task _activatedTask; + + private static void FswOnChanged(object sender, FileSystemEventArgs fileSystemEventArgs) + { + _reloadTime = DateTime.UtcNow.AddSeconds(4); + if (_activatedTask != null) + return; + _activatedTask = Task.Factory.StartNew( + () => + { + while (true) + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + if (DateTime.UtcNow > _reloadTime) + { + _activatedTask = null; + LoadConfig(false); + break; + } + } + }); + } + } +} diff --git a/AutoTunnel/Config/MainConfig.cs b/AutoTunnel/Config/MainConfig.cs index 83a38dd..ddb520b 100644 --- a/AutoTunnel/Config/MainConfig.cs +++ b/AutoTunnel/Config/MainConfig.cs @@ -31,5 +31,9 @@ public class MainConfig [DefaultValue(15)] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] public int PingBackTime { get; set; } + + [DefaultValue(true)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public bool AutoReloadOnChange { get; set; } } } diff --git a/AutoTunnel/Encryption/DecryptHelper.cs b/AutoTunnel/Encryption/DecryptHelper.cs index ad08361..db200b5 100644 --- a/AutoTunnel/Encryption/DecryptHelper.cs +++ b/AutoTunnel/Encryption/DecryptHelper.cs @@ -6,7 +6,7 @@ public class DecryptHelper { private readonly byte[] _innerBuf = new byte[65536]; - private readonly byte[] _key; + private readonly Aes _aes; private readonly byte[] _headerBuf = new byte[16]; @@ -19,25 +19,30 @@ public byte[] InnerBuf } public DecryptHelper(byte[] key) - { - _key = key; - } - - public int Decrypt(byte[] data, int offset) { var aes = Aes.Create(); - aes.Key = _key; + aes.Key = key; aes.IV = new byte[16]; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.None; - var decryptor = aes.CreateDecryptor(); - decryptor.TransformBlock(data, offset, 16, _headerBuf, 0); - var len = _headerBuf[0] | (_headerBuf[1] << 8) | (_headerBuf[2] << 16) | (_headerBuf[3] << 24); + _aes = aes; + // _decryptor = aes.CreateDecryptor(); + } + + public int Decrypt(byte[] data, int offset) + { + var hb = _headerBuf; + // strange situation, decryptor cannot be used multiple times, problem with resetting cbc data, or my fault... + // but encrypting is work with extracted encryptor + var decryptor = _aes.CreateDecryptor(); + decryptor.TransformBlock(data, offset, 16, hb, 0); + var len = hb[0] | (hb[1] << 8) | (hb[2] << 16) | (hb[3] << 24); if (len > data.Length) return -1; - if (_headerBuf[4] != 1 || _headerBuf[5] != 0 || _headerBuf[6] != 'A' || _headerBuf[7] != 'T') return -1; + if (hb[4] != 1 || hb[5] != 0 || hb[6] != 'A' || hb[7] != 'T') return -1; var len16 = (len + 15) & ~15; if (len < 0 || len > _innerBuf.Length) return -1; decryptor.TransformBlock(data, offset + 16, len16, _innerBuf, 0); + // decryptor.TransformFinalBlock(new byte[0], 0, 0); return len; } } diff --git a/AutoTunnel/Encryption/EncryptHelper.cs b/AutoTunnel/Encryption/EncryptHelper.cs index 1283ca1..830b32d 100644 --- a/AutoTunnel/Encryption/EncryptHelper.cs +++ b/AutoTunnel/Encryption/EncryptHelper.cs @@ -1,18 +1,17 @@ using System.Security.Cryptography; -using System.Threading; namespace Force.AutoTunnel.Encryption { public class EncryptHelper { - private readonly byte[] _key; - private readonly RandomNumberGenerator _random = RandomNumberGenerator.Create(); private readonly byte[] _innerBuf = new byte[65536]; private readonly byte[] _headerBuf = new byte[16]; + private readonly ICryptoTransform _encryptor; + public byte[] InnerBuf { get @@ -22,36 +21,40 @@ public byte[] InnerBuf } public EncryptHelper(byte[] key) - { - _key = key; - } - - public int Encrypt(byte[] data, int len) { var aes = Aes.Create(); - aes.Key = _key; + aes.Key = key; aes.IV = new byte[16]; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.None; - var encryptor = aes.CreateEncryptor(); - _random.GetBytes(_headerBuf); - _headerBuf[0] = (byte)(len & 0xff); - _headerBuf[1] = (byte)((len >> 8) & 0xff); - _headerBuf[2] = (byte)((len >> 16) & 0xff); - _headerBuf[3] = (byte)((len >> 24) & 0xff); + _encryptor = aes.CreateEncryptor(); + } + + public int Encrypt(byte[] data, int len) + { + var hb = _headerBuf; + // _random.GetBytes(hb); + hb[0] = (byte)(len & 0xff); + hb[1] = (byte)((len >> 8) & 0xff); + hb[2] = (byte)((len >> 16) & 0xff); + hb[3] = (byte)((len >> 24) & 0xff); // var c = Interlocked.Increment(ref _counter); /*_headerBuf[4] = (byte)(c & 0xff); _headerBuf[5] = (byte)((c >> 8) & 0xff); _headerBuf[6] = (byte)((c >> 16) & 0xff); _headerBuf[7] = (byte)((c >> 24) & 0xff);*/ - _headerBuf[4] = 0x1; - _headerBuf[5] = 0x0; - _headerBuf[6] = (byte)'A'; - _headerBuf[7] = (byte)'T'; - - encryptor.TransformBlock(_headerBuf, 0, 16, _innerBuf, 0); - if (len == 0) return 16 + 4; - return encryptor.TransformBlock(data, 0, (len + 15) & ~15, _innerBuf, 16) + 16; + hb[4] = 0x1; + hb[5] = 0x0; + hb[6] = (byte)'A'; + hb[7] = (byte)'T'; + + _encryptor.TransformBlock(hb, 0, 16, _innerBuf, 0); + var tl = 0; + if (len > 0) + tl = _encryptor.TransformBlock(data, 0, (len + 15) & ~15, _innerBuf, 16); + + _encryptor.TransformFinalBlock(new byte[0], 0, 0); + return tl + 16; } } } diff --git a/AutoTunnel/Listener.cs b/AutoTunnel/Listener.cs index c8a4c5e..44e01d8 100644 --- a/AutoTunnel/Listener.cs +++ b/AutoTunnel/Listener.cs @@ -70,13 +70,13 @@ private void StartInternal() { int cnt = s.ReceiveFrom(inBuf, ref ep1); IPEndPoint ep = (IPEndPoint)ep1; - TunnelStorage.Session session; + TunnelSession session; if (cnt == 0) continue; byte[] decBuf = null; if (cnt % 16 != 0) { - if (inBuf[0] == 1) + if (inBuf[0] == (byte)StateFlags.Connecting) { LogHelper.Log.WriteLine("Estabilishing connection from " + ep); int dataLen = -1; @@ -120,13 +120,20 @@ private void StartInternal() continue; } - if (inBuf[0] == 0x5) // ping + if (inBuf[0] == (byte)StateFlags.Ping) // ping { session = _storage.GetSession(ep); - if (session != null) session.UpdateLastActivity(); + if (session != null) session.UpdateReceiveActivity(); s.SendTo(new byte[] { (byte)StateFlags.Pong, 0, 0, 0 }, 4, SocketFlags.None, ep); continue; } + // it is good idea, but attacker can cause close of our connection + // so, think in future about this + /*else if (inBuf[0] == (byte)StateFlags.ConnectionClosing) // ping + { + _storage.RemoveSession(ep); + continue; + }*/ else { // error @@ -155,7 +162,7 @@ private void StartInternal() decBuf = session.Decryptor.InnerBuf; cnt = len; - session.UpdateLastActivity(); + session.UpdateReceiveActivity(); } // var sourceIp = decBuf[12] + "." + decBuf[13] + "." + decBuf[14] + "." + decBuf[15]; @@ -169,7 +176,7 @@ private void StartInternal() // session was changed for client, killing it and update data if (!sender.Session.RemoteEP.Equals(ep)) { - Console.WriteLine("Client for " + sourceIp + " has changed endpoint from " + sender.Session.RemoteEP + " to " + ep); + LogHelper.Log.WriteLine("Client for " + sourceIp + " has changed endpoint from " + sender.Session.RemoteEP + " to " + ep); s.SendTo(new byte[] { 0x3, 0, 0, 0 }, 4, SocketFlags.None, sender.Session.RemoteEP); _storage.RemoveSession(sender.Session.RemoteEP); sender.Session = session; diff --git a/AutoTunnel/Program.cs b/AutoTunnel/Program.cs index 278e251..f648c51 100644 --- a/AutoTunnel/Program.cs +++ b/AutoTunnel/Program.cs @@ -1,6 +1,6 @@ using System; -using System.IO; using System.Linq; +using System.Reflection; using System.ServiceProcess; using System.Threading; @@ -8,65 +8,22 @@ using Force.AutoTunnel.Logging; using Force.AutoTunnel.Service; -using Newtonsoft.Json; - namespace Force.AutoTunnel { public class Program { - public static MainConfig Config { get; private set; } - public static void Main(string[] args) { if (ProcessArgs(args)) return; - var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json"); - if (!File.Exists(configPath)) - { - Console.Error.WriteLine("Missing config file"); - return; - } - - MainConfig config; - - try - { - config = new JsonSerializer().Deserialize(new JsonTextReader(new StreamReader(File.OpenRead(configPath)))); - if (config.RemoteClients == null) - config.RemoteClients = new RemoteClientConfig[0]; - if (config.RemoteClients.Length == 0) - config.EnableListening = false; - if (config.RemoteServers == null) - config.RemoteServers = new RemoteServerConfig[0]; - foreach (var remoteServerConfig in config.RemoteServers) - { - if (string.IsNullOrEmpty(remoteServerConfig.ConnectHost) && string.IsNullOrEmpty(remoteServerConfig.TunnelHost)) - throw new InvalidOperationException("Missing host info in config"); - - if (string.IsNullOrEmpty(remoteServerConfig.TunnelHost)) - remoteServerConfig.TunnelHost = remoteServerConfig.ConnectHost; - if (string.IsNullOrEmpty(remoteServerConfig.ConnectHost)) - remoteServerConfig.ConnectHost = remoteServerConfig.TunnelHost; - } - - var log = new AggregateLog(); - if (Environment.UserInteractive) log.AddLog(new ConsoleLog()); - if (!string.IsNullOrEmpty(config.LogFileName)) log.AddLog(new FileLog(config.LogFileName)); - LogHelper.SetLog(log); - } - catch (Exception ex) - { - Console.Error.WriteLine("Error in parsing config: " + ex.Message); - return; - } - if (!NativeHelper.IsNativeAvailable) { Console.Error.WriteLine("Cannot load WinDivert library"); return; } - Config = config; + if (!ConfigHelper.LoadConfig(true)) + return; if (Environment.UserInteractive) { @@ -93,9 +50,12 @@ private static bool ProcessArgs(string[] args) private static void RunInConsole() { + Console.WriteLine("Press Ctrl+C for exit"); + + var attr = typeof(Program).Assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false).Cast().First(); + LogHelper.Log.WriteLine("AutoTunnel by Force. Version: " + attr.Version); AppDomain.CurrentDomain.DomainUnload += (sender, args) => ConsoleHelper.SetActiveIcon(ConsoleHelper.IconStatus.Default); Console.CancelKeyPress += (sender, args) => ConsoleHelper.SetActiveIcon(ConsoleHelper.IconStatus.Default); - Console.WriteLine("Press Ctrl+C for exit"); LogHelper.Log.WriteLine("Starting interactive..."); Starter.Start(); Thread.Sleep(-1); diff --git a/AutoTunnel/Properties/AssemblyInfo.cs b/AutoTunnel/Properties/AssemblyInfo.cs index b160203..7c34637 100644 --- a/AutoTunnel/Properties/AssemblyInfo.cs +++ b/AutoTunnel/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/AutoTunnel/ReplySender.cs b/AutoTunnel/ReplySender.cs index 3c93613..03fdac5 100644 --- a/AutoTunnel/ReplySender.cs +++ b/AutoTunnel/ReplySender.cs @@ -12,7 +12,7 @@ public class ReplySender : BaseSender private readonly EncryptHelper _encryptHelper; - public ReplySender(TunnelStorage.Session session, IPAddress watchAddr, Socket socket, TunnelStorage storage) + public ReplySender(TunnelSession session, IPAddress watchAddr, Socket socket, TunnelStorage storage) : base(session, watchAddr, storage) { LogHelper.Log.WriteLine("Tunnel watcher was created for " + watchAddr); @@ -22,6 +22,7 @@ public ReplySender(TunnelStorage.Session session, IPAddress watchAddr, Socket so protected override void Send(byte[] packet, int packetLen) { + Session.UpdateSendActivity(); var len = _encryptHelper.Encrypt(packet, packetLen); _socket.SendTo(_encryptHelper.InnerBuf, len, SocketFlags.None, Session.RemoteEP); } diff --git a/AutoTunnel/Starter.cs b/AutoTunnel/Starter.cs index 51f6603..9e78daf 100644 --- a/AutoTunnel/Starter.cs +++ b/AutoTunnel/Starter.cs @@ -16,7 +16,7 @@ public class Starter public static void Start() { _storage = new TunnelStorage(); - var config = Program.Config; + var config = ConfigHelper.Config; if (config.EnableListening) { @@ -40,8 +40,8 @@ public static void Stop() _storage.RemoveAllSessions(); if (_listener != null) _listener.Dispose(); - if (Program.Config.AddFirewallRule) - FirewallHelper.DeleteFirewallRule(Program.Config.Port.ToString(CultureInfo.InvariantCulture)); + if (ConfigHelper.Config.AddFirewallRule) + FirewallHelper.DeleteFirewallRule(ConfigHelper.Config.Port.ToString(CultureInfo.InvariantCulture)); } } } diff --git a/AutoTunnel/StateFlags.cs b/AutoTunnel/StateFlags.cs index b2c8297..bab6a20 100644 --- a/AutoTunnel/StateFlags.cs +++ b/AutoTunnel/StateFlags.cs @@ -14,6 +14,9 @@ public enum StateFlags : byte ErrorFromProxy = 8, - Pong = 9 + Pong = 9, + + // todo : think about it + // ConnectionClosing = 10 } } diff --git a/AutoTunnel/TunnelSession.cs b/AutoTunnel/TunnelSession.cs new file mode 100644 index 0000000..82b51e6 --- /dev/null +++ b/AutoTunnel/TunnelSession.cs @@ -0,0 +1,47 @@ +using System; +using System.Net; + +using Force.AutoTunnel.Encryption; + +namespace Force.AutoTunnel +{ + public class TunnelSession + { + public TunnelSession(IPEndPoint remoteEP) + { + RemoteEP = remoteEP; + UpdateReceiveActivity(); + UpdateSendActivity(); + } + + public IPEndPoint RemoteEP { get; private set; } + + public byte[] Key { get; set; } + + public DecryptHelper Decryptor { get; set; } + + public DateTime LastReceiveActivity { get; private set; } + + public DateTime LastSendActivity { get; private set; } + + public bool IsClientSession { get; set; } + + public void UpdateReceiveActivity() + { + LastReceiveActivity = DateTime.UtcNow; + } + + public void UpdateSendActivity() + { + LastSendActivity = DateTime.UtcNow; + } + + public TimeSpan SendReceiveDifference + { + get + { + return LastSendActivity.Subtract(LastReceiveActivity); + } + } + } +} \ No newline at end of file diff --git a/AutoTunnel/TunnelStorage.cs b/AutoTunnel/TunnelStorage.cs index 63db284..6d5e24c 100644 --- a/AutoTunnel/TunnelStorage.cs +++ b/AutoTunnel/TunnelStorage.cs @@ -26,7 +26,7 @@ public BaseSender GetOrAddSender(IPAddress dstAddr, Func creatorFunc public IPEndPoint[] GetOldSessions(TimeSpan killTime) { var dt = DateTime.UtcNow; - return _sessions.Where(x => !x.Value.IsClientSession && dt.Subtract(x.Value.LastActivity) >= killTime) + return _sessions.Where(x => !x.Value.IsClientSession && dt.Subtract(x.Value.LastReceiveActivity) >= killTime) .Select(x => x.Key) .Select(x => new IPEndPoint((long)(x >> 16), (int)(x & 0xffff))) .ToArray(); @@ -43,7 +43,7 @@ public void RemoveAllSessions() public void RemoveSession(IPEndPoint endPoint) { var hostKey = GetHostKey(endPoint); - Session session; + TunnelSession session; if (_sessions.TryRemove(hostKey, out session)) { if (session.IsClientSession) @@ -59,36 +59,12 @@ public void RemoveSession(IPEndPoint endPoint) SetIcon(); } - public class Session - { - public Session(IPEndPoint remoteEP) - { - RemoteEP = remoteEP; - UpdateLastActivity(); - } - - public IPEndPoint RemoteEP { get; private set; } - - public byte[] Key { get; set; } - - public DecryptHelper Decryptor { get; set; } - - public DateTime LastActivity { get; private set; } - - public bool IsClientSession { get; set; } - - public void UpdateLastActivity() - { - LastActivity = DateTime.UtcNow; - } - } - - private readonly ConcurrentDictionary _sessions = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _sessions = new ConcurrentDictionary(); - public Session AddSession(byte[] sessionKey, IPEndPoint remoteEP) + public TunnelSession AddSession(byte[] sessionKey, IPEndPoint remoteEP) { var hostKey = GetHostKey(remoteEP); - var s = new Session(remoteEP) + var s = new TunnelSession(remoteEP) { Key = sessionKey, Decryptor = new DecryptHelper(sessionKey) @@ -98,9 +74,9 @@ public Session AddSession(byte[] sessionKey, IPEndPoint remoteEP) return s; } - public Session GetSession(IPEndPoint remoteHost) + public TunnelSession GetSession(IPEndPoint remoteHost) { - Session value; + TunnelSession value; if (_sessions.TryGetValue(GetHostKey(remoteHost), out value)) return value; return null; } diff --git a/README.md b/README.md index a7f9516..4f65257 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,140 @@ # AutoTunnel -Secured network tunnel for two computers connection +Secured network tunnel for connecting two computers. Main usage: connecting travelling computer to one server. + +## Features + +* Works for Windows 7 or greater +* Client-Server model +* Works on IP level and tunnels ICMP, TCP and UDP data +* Uses only one UDP port (12017 by default) +* Can connect by hostnames with periodic refreshing data +* Client can be NAT'ed +* Server can be NAT'ed, but you need to use a special proxy +* Creates transparent secured tunnel between two computers without allocation of additional IP-addresses, so you do not need for separate DNS-names for computers + +## Differences from VPN + +* No server needed +* No separate IP address or network interface +* Always active (if service running) + +## Differences from IPSec + +* No need to specify both endpoints to static addresses +* Only one UDP port for usage (insted of ESP, AH protocols and 500 and 4500 UDP ports) +* Just some settings to make tunnel works, you no need to specify lot of parameters in different configs and phases +* Currently, no IPv6 support + +## Limitations + +* Due usage existing IPs for computers there are can be collision with current IPs +* If network has problems with UDP fragmentation (or badly configured Jumbo Frames), there are can be strange errors and packet loss + +# Description + +AutoTunnel uses [WinDivert](https://reqrypt.org/windivert.html) library to catch outgoing network packets, transfers it to another computer and puts them back, as normal packets. +So, this packets looks like usual network packets, but you do not need to specify routing tables between computers. These computers are in virtual local network. +After estabilishing connection with another computer, it begin to catch packets to source computer and transfers it back to it through estabilished tunnel. + +## Encryption +Tunnel is encrypted with AES256 with PFS support. Currently, for auth you can use only preshared keys (I think, it simple and good variant for authentication, because one key can be changed to another in some seconds, you do not need to regenerate certificates or private keys). +This key is used only for initial handshake and has not transferred in open form. After handshake stage, another, temporary session key is used for data encryption. + +## Tunnel Handling + +There are two types of computers: + +* Server - listens incoming connections +* Client - connects to server + +Any computer can be server for others computers and client for anothers. There are no need to use separate program. +So, despite only two computers can use one tunnel, you can create a lot of tunnels between pairs of different computers. + +Also, you can estabilish connection between two computers in client mode. Two tunnels will be created, but packets will be passed correctly. + + +# Installation and running + +You need: + +* Windows 7/2008R2 or greater +* Enabled .NET Framework 4.0 +* [VC Redist 2012](https://www.microsoft.com/en-us/download/details.aspx?id=30679) + +Program can be runned in console or work as a service. Service can be installed in next way: +``` +AutoTunnel.exe service install +sc start AutoTunnel +``` +## Configuration + +Before starting, you need to configure config.json file. Example config: +``` +{ + "enableListening": true, + "addFirewallRule": true, + "port": 12017, + "logFileName": "somelog.log", + "autoReloadOnChange": true, + "remoteClients": [ + { + "key": "key1", + "description": "my key" + } + ], + "remoteServers": [ + { + "tunnelHost": "192.168.16.8", + "connectHost": "192.168.18.1:12017", + "proxyHost": "192.168.24.1:12018", + "key": "key1", + "keepAlive": true, + "connectOnStart": true + } + ] +} +``` + +Key | Default Value | Description +----|---------------|------------ +enableListening | true | Is Application listen incoming connections +addFirewallRule | true | Add opening rule to Windows Firewall +port | 12017 | Listening port +logFileName | null | Name of file to log program messages +autoReloadOnChange | true | Reload config automatically if it changed +remoteClients | null | Data of remote clients for server (if multiple clients are specified - any key can be used for connection) +remoteClients.key | null | Pre-shared Key for remote client +remoteClients.description | null | Description of remote client for logging and your notes +remoteServers | null | Servers for connecting as client +remoteServers.tunnelHost | null | IP address or host name of remote computer. If any packets will be send to this computer, it will be passed through tunnel +remoteServers.connectHost | null | IP address or host with port of remote computer to connect. If skipped - tunnel host data can be used. You can specify it, it target computer has different IP addresses and you want to connect to one of them, but pass data for another +remoteServers.proxyHost | null | IP address or host with port of proxy. Proxy can be used to connect to server which is not available from outer network +remoteServers.key | null | Pre-shared key to estabilish connection with remote server +remoteServers.keepAlive | false | Send special keep alive packets to keep connection alive. If you need permament connection, you can set it to true +remoteServers.connectOnStart | false | Connect to another computer on application start or only when first packet will be sent to it + + +# Proxy +If target computer is unavailabe from outer network but you have separate computer which can transfer packets from local network to outer network, you can use special proxy program. + +Full scheme of connection can be similar: + +``` +Client computer (gray ip, NAT) <-> Router <-> (Internet) <-> Router <-> Proxy <-> Server (NAT) +192.168.1.42 <-> 192.168.1.1|91.10.20.42 <-> (Internet) <-> 82.40.10.1 <-> 82.40.10.54 <-> 10.0.1.15 +``` + + +Current implementation does not encrypt data, it just transfer packets from one computer to another. For better compatibility, current version of proxy is written on node.js, so it can be run at lot of hardware. + +Also, when you use a proxy, just proxy resolves host name to ip. So, it can be useful for internal host names. + +By default, proxy is listening on 12018 port and can filter destination ip addresses or host names. + +# Licensing + +AutoTunnel has [MIT](https://github.com/force-net/AutTunnel/blob/develop/LICENSE) license, but it uses [WinDivert](https://reqrypt.org/windivert.html) library with [LGPL3](https://reqrypt.org/windivert-doc-v1.2.html#license) license. + +Application Icons made by Freepik from www.flaticon.com is licensed by CC 3.0 BY + +